1#!/usr/bin/env ruby 2# 3# This script implements the "ss" application. "ss" implements 4# a presentation slide-show based on HTML slides. 5# 6require 'tk' 7require 'tkextlib/tkHTML' 8 9file = ARGV[0] 10 11class TkHTML_File_Viewer 12 include TkComm 13 14# These are images to use with the actual image specified in a 15# "<img>" markup can't be found. 16# 17@@biggray = TkPhotoImage.new(:data=><<'EOD') 18 R0lGODdhPAA+APAAALi4uAAAACwAAAAAPAA+AAACQISPqcvtD6OctNqLs968+w+G4kiW5omm 19 6sq27gvH8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNFgsAO/// 20EOD 21 22@@smgray = TkPhotoImage.new(:data=><<'EOD') 23 R0lGODdhOAAYAPAAALi4uAAAACwAAAAAOAAYAAACI4SPqcvtD6OctNqLs968+w+G4kiW5omm 24 6sq27gvH8kzX9m0VADv/ 25EOD 26 27 def initialize(file = nil) 28 @root = TkRoot.new(:title=>'HTML File Viewer', :iconname=>'HV') 29 @fswin = nil 30 31 @html = nil 32 @html_fs = nil 33 34 @hotkey = {} 35 36 @applet_arg = TkVarAccess.new_hash('AppletArg') 37 38 @images = {} 39 @old_imgs = {} 40 @big_imgs = {} 41 42 @last_dir = Dir.pwd 43 44 @last_file = '' 45 46 @key_block = false 47 48 Tk::HTML_Widget::ClippingWindow.bind('1', 49 proc{|w, ksym| key_press(w, ksym)}, 50 '%W Down') 51 Tk::HTML_Widget::ClippingWindow.bind('3', 52 proc{|w, ksym| key_press(w, ksym)}, 53 '%W Up') 54 Tk::HTML_Widget::ClippingWindow.bind('2', 55 proc{|w, ksym| key_press(w, ksym)}, 56 '%W Down') 57 58 Tk::HTML_Widget::ClippingWindow.bind('KeyPress', 59 proc{|w, ksym| key_press(w, ksym)}, 60 '%W %K') 61 62 ############################################ 63 # 64 # Build the half-size view of the page 65 # 66 menu_spec = [ 67 [['File', 0], 68 ['Open', proc{sel_load()}, 0], 69 ['Full Screen', proc{fullscreen()}, 0], 70 ['Refresh', proc{refresh()}, 0], 71 '---', 72 ['Exit', proc{exit}, 1]] 73 ] 74 75 mbar = @root.add_menubar(menu_spec) 76 77 @html = Tk::HTML_Widget.new(:width=>512, :height=>384, 78 :padx=>5, :pady=>9, 79 :formcommand=>proc{|*args| form_cmd(*args)}, 80 :imagecommand=>proc{|*args| 81 image_cmd(1, *args) 82 }, 83 :scriptcommand=>proc{|*args| 84 script_cmd(*args) 85 }, 86 :appletcommand=>proc{|*args| 87 applet_cmd(*args) 88 }, 89 :hyperlinkcommand=>proc{|*args| 90 hyper_cmd(*args) 91 }, 92 :fontcommand=>proc{|*args| 93 pick_font(*args) 94 }, 95 :appletcommand=>proc{|*args| 96 run_applet('small', *args) 97 }, 98 :bg=>'white', :tablerelief=>:raised) 99 100 @html.token_handler('meta', proc{|*args| meta(@html, *args)}) 101 102 vscr = @html.yscrollbar(TkScrollbar.new) 103 hscr = @html.xscrollbar(TkScrollbar.new) 104 105 Tk.grid(@html, vscr, :sticky=>:news) 106 Tk.grid(hscr, :sticky=>:ew) 107 @root.grid_columnconfigure(0, :weight=>1) 108 @root.grid_columnconfigure(1, :weight=>0) 109 @root.grid_rowconfigure(0, :weight=>1) 110 @root.grid_rowconfigure(1, :weight=>0) 111 112 ############################################ 113 114 @html.clipwin.focus 115 116 # If an arguent was specified, read it into the HTML widget. 117 # 118 Tk.update 119 if file && file != "" 120 load_file(file) 121 end 122 end 123 124 # 125 # A font chooser routine. 126 # 127 # html[:fontcommand] = pick_font 128 def pick_font(size, attrs) 129 # puts "FontCmd: #{size} #{attrs}" 130 [ ((attrs =~ /fixed/)? 'courier': 'charter'), 131 (12 * (1.2**(size.to_f - 4.0))).to_i, 132 ((attrs =~ /italic/)? 'italic': 'roman'), 133 ((attrs =~ /bold/)? 'bold': 'normal') ].join(' ') 134 end 135 136 # This routine is called to pick fonts for the fullscreen view. 137 # 138 def pick_font_fs(size, attrs) 139 baseFontSize = 24 140 141 # puts "FontCmd: #{size} #{attrs}" 142 [ ((attrs =~ /fixed/)? 'courier': 'charter'), 143 (baseFontSize * (1.2**(size.to_f - 4.0))).to_i, 144 ((attrs =~ /italic/)? 'italic': 'roman'), 145 ((attrs =~ /bold/)? 'bold': 'normal') ].join(' ') 146 end 147 148 # 149 # 150 def hyper_cmd(*args) 151 puts "HyperlinkCommand: #{args.inspect}" 152 end 153 154 # This routine is called to run an applet 155 # 156 def run_applet(size, w, arglist) 157 applet_arg.value = Hash[*simplelist(arglist)] 158 159 return unless @applet_arg.key?('src') 160 161 src = @html.remove(@applet_arg['src']) 162 163 @applet_arg['window'] = w 164 @applet_arg['fontsize'] = size 165 166 begin 167 Tk.load_tclscript(src) 168 rescue => e 169 puts "Applet error: #{e.message}" 170 end 171 end 172 173 # 174 # 175 def form_cmd(n, cmd, *args) 176 # p [n, cmd, *args] 177 end 178 179 # 180 # 181 def move_big_image(b) 182 return unless @big_imgs.key?(b) 183 b.copy(@big_imgs[b]) 184 @big_imgs[b].delete 185 @big_imgs.delete(b) 186 end 187 188 def image_cmd(hs, *args) 189 fn = args[0] 190 191 if @old_imgs.key?(fn) 192 return (@images[fn] = @old_imgs.delete(fn)) 193 end 194 195 begin 196 img = TkPhotoImage.new(:file=>fn) 197 rescue 198 return ((hs)? @@smallgray: @@biggray) 199 end 200 201 if hs 202 img2 = TkPhotoImage.new 203 img2.copy(img, :subsample=>[2,2]) 204 img.delete 205 img = img2 206 end 207 208 if img.width * img.height > 20000 209 b = TkPhotoImage.new(:width=>img.width, :height=>img.height) 210 @big_imgs[b] = img 211 img = b 212 Tk.after_idle(proc{ move_big_image(b) }) 213 end 214 215 @images[fn] = img 216 217 img 218 end 219 220 # 221 # This routine is called for every <SCRIPT> markup 222 # 223 def script_cmd(*args) 224 # puts "ScriptCmd: #{args.inspect}" 225 end 226 227 # This routine is called for every <APPLET> markup 228 # 229 def applet_cmd(w, arglist) 230 # puts "AppletCmd: w=#{w} arglist=#{arglist}" 231 #TkLabel.new(w, :text=>"The Applet #{w}", :bd=>2, :relief=>raised) 232 end 233 234 # This binding fires when there is a click on a hyperlink 235 # 236 def href_binding(w, x, y) 237 lst = w.href(x, y) 238 unless lst.empty? 239 process_url(lst) 240 end 241 end 242 243 # 244 # 245 def sel_load 246 filetypes = [ 247 ['Html Files', ['.html', '.htm']], 248 ['All Files', '*'] 249 ] 250 251 f = Tk.getOpenFile(:initialdir=>@last_dir, :filetypes=>filetypes) 252 if f != '' 253 load_file(f) 254 @last_dir = File.dirname(f) 255 end 256 end 257 258 # Clear the screen. 259 # 260 def clear_screen 261 if @html_fs && @html_fs.exist? 262 w = @html_fs 263 else 264 w = @html 265 end 266 w.clear 267 @old_imgs.clear 268 @big_imgs.clear 269 @hotkey.clear 270 @images.each{|k, v| @old_imgs[k] = v } 271 @images.clear 272 end 273 274 # Read a file 275 # 276 def read_file(name) 277 begin 278 fp = open(name, 'r') 279 ret = fp.read(File.size(name)) 280 rescue 281 ret = nil 282 fp = nil 283 Tk.messageBox(:icon=>'error', :message=>"fail to open '#{name}'", 284 :type=>:ok) 285 ensure 286 fp.close if fp 287 end 288 ret 289 end 290 291 # Process the given URL 292 # 293 def process_url(url) 294 case url[0] 295 when /^file:/ 296 load_file(url[0][5..-1]) 297 when /^exec:/ 298 Tk.ip_eval(url[0][5..-1].tr('\\', ' ')) 299 else 300 load_file(url[0]) 301 end 302 end 303 304 # Load a file into the HTML widget 305 # 306 def load_file(name) 307 return unless (doc = read_file(name)) 308 clear_screen() 309 @last_file = name 310 if @html_fs && @html_fs.exist? 311 w = @html_fs 312 else 313 w = @html 314 end 315 w.configure(:base=>name) 316 w.parse(doc) 317 w.configure(:cursor=>'top_left_arrow') 318 @old_imgs.clear 319 end 320 321 # Refresh the current file. 322 # 323 def refresh(*args) 324 load_file(@last_file) if @last_file 325 end 326 327 # This routine is called whenever a "<meta>" markup is seen. 328 # 329 def meta(w, tag, alist) 330 v = Hash[*simplelist(alist)] 331 332 if v.key?('key') && v.key?('href') 333 @hotkey[v['key']] = w.resolve(v['href']) 334 end 335 336 if v.key?('next') 337 @hotkey['Down'] =v['next'] 338 end 339 340 if v.key?('prev') 341 @hotkey['Up'] =v['prev'] 342 end 343 344 if v.key?('other') 345 @hotkey['o'] =v['other'] 346 end 347 end 348 349 # Go from full-screen mode back to window mode. 350 # 351 def fullscreen_off 352 @fswin.destroy 353 @root.deiconify 354 Tk.update 355 @root.raise 356 @html.clipwin.focus 357 clear_screen() 358 @old_imgs.clear 359 refresh() 360 end 361 362 # Go from window mode to full-screen mode. 363 # 364 def fullscreen 365 if @fswin && @fswin.exist? 366 @fswin.deiconify 367 Tk.update 368 @fswin.raise 369 return 370 end 371 372 width = @root.winfo_screenwidth 373 height = @root.winfo_screenheight 374 @fswin = TkToplevel.new(:overrideredirect=>true, 375 :geometry=>"#{width}x#{height}+0+0") 376 377 @html_fs = Tk::HTML_Widget.new(@fswin, :padx=>5, :pady=>9, 378 :formcommand=>proc{|*args| 379 form_cmd(*args) 380 }, 381 :imagecommand=>proc{|*args| 382 image_cmd(0, *args) 383 }, 384 :scriptcommand=>proc{|*args| 385 script_cmd(*args) 386 }, 387 :appletcommand=>proc{|*args| 388 applet_cmd(*args) 389 }, 390 :hyperlinkcommand=>proc{|*args| 391 hyper_cmd(*args) 392 }, 393 :appletcommand=>proc{|*args| 394 run_applet('big', *args) 395 }, 396 :fontcommand=>proc{|*args| 397 pick_font_fs(*args) 398 }, 399 :bg=>'white', :tablerelief=>:raised, 400 :cursor=>:tcross) { 401 pack(:fill=>:both, :expand=>true) 402 token_handler('meta', proc{|*args| meta(self, *args)}) 403 } 404 405 clear_screen() 406 @old_imgs.clear 407 refresh() 408 Tk.update 409 @html_fs.clipwin.focus 410 end 411 412 # 413 # 414 def key_press(w, keysym) 415 return if @key_block 416 @key_block = true 417 Tk.after(250, proc{@key_block = false}) 418 419 if @hotkey.key?(keysym) 420 process_url(@hotkey[keysym]) 421 end 422 case keysym 423 when 'Escape' 424 if @fswin && @fswin.exist? 425 fullscreen_off() 426 else 427 fullscreen() 428 end 429 end 430 end 431end 432############################################ 433 434TkHTML_File_Viewer.new(file) 435 436Tk.mainloop 437