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