2004-07-01 05:38:48 -04:00
|
|
|
#!/usr/bin/env ruby
|
|
|
|
#
|
|
|
|
# This script implements the "ss" application. "ss" implements
|
|
|
|
# a presentation slide-show based on HTML slides.
|
2009-03-05 22:56:38 -05:00
|
|
|
#
|
2004-07-01 05:38:48 -04:00
|
|
|
require 'tk'
|
|
|
|
require 'tkextlib/tkHTML'
|
|
|
|
|
|
|
|
file = ARGV[0]
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
class TkHTML_File_Viewer
|
|
|
|
include TkComm
|
2004-07-01 05:38:48 -04:00
|
|
|
|
|
|
|
# These are images to use with the actual image specified in a
|
|
|
|
# "<img>" markup can't be found.
|
|
|
|
#
|
2005-03-30 03:44:19 -05:00
|
|
|
@@biggray = TkPhotoImage.new(:data=><<'EOD')
|
2004-07-01 05:38:48 -04:00
|
|
|
R0lGODdhPAA+APAAALi4uAAAACwAAAAAPAA+AAACQISPqcvtD6OctNqLs968+w+G4kiW5omm
|
|
|
|
6sq27gvH8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNFgsAO///
|
|
|
|
EOD
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
@@smgray = TkPhotoImage.new(:data=><<'EOD')
|
2004-07-01 05:38:48 -04:00
|
|
|
R0lGODdhOAAYAPAAALi4uAAAACwAAAAAOAAYAAACI4SPqcvtD6OctNqLs968+w+G4kiW5omm
|
|
|
|
6sq27gvH8kzX9m0VADv/
|
|
|
|
EOD
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
def initialize(file = nil)
|
|
|
|
@root = TkRoot.new(:title=>'HTML File Viewer', :iconname=>'HV')
|
|
|
|
@fswin = nil
|
|
|
|
|
|
|
|
@html = nil
|
|
|
|
@html_fs = nil
|
|
|
|
|
|
|
|
@hotkey = {}
|
|
|
|
|
|
|
|
@applet_arg = TkVarAccess.new_hash('AppletArg')
|
|
|
|
|
|
|
|
@images = {}
|
|
|
|
@old_imgs = {}
|
|
|
|
@big_imgs = {}
|
|
|
|
|
|
|
|
@last_dir = Dir.pwd
|
|
|
|
|
|
|
|
@last_file = ''
|
|
|
|
|
|
|
|
@key_block = false
|
|
|
|
|
2009-03-05 22:56:38 -05:00
|
|
|
Tk::HTML_Widget::ClippingWindow.bind('1',
|
|
|
|
proc{|w, ksym| key_press(w, ksym)},
|
2005-03-30 03:44:19 -05:00
|
|
|
'%W Down')
|
2009-03-05 22:56:38 -05:00
|
|
|
Tk::HTML_Widget::ClippingWindow.bind('3',
|
|
|
|
proc{|w, ksym| key_press(w, ksym)},
|
2005-03-30 03:44:19 -05:00
|
|
|
'%W Up')
|
2009-03-05 22:56:38 -05:00
|
|
|
Tk::HTML_Widget::ClippingWindow.bind('2',
|
|
|
|
proc{|w, ksym| key_press(w, ksym)},
|
2005-03-30 03:44:19 -05:00
|
|
|
'%W Down')
|
|
|
|
|
2009-03-05 22:56:38 -05:00
|
|
|
Tk::HTML_Widget::ClippingWindow.bind('KeyPress',
|
|
|
|
proc{|w, ksym| key_press(w, ksym)},
|
2005-03-30 03:44:19 -05:00
|
|
|
'%W %K')
|
|
|
|
|
|
|
|
############################################
|
|
|
|
#
|
|
|
|
# Build the half-size view of the page
|
|
|
|
#
|
|
|
|
menu_spec = [
|
2009-03-05 22:56:38 -05:00
|
|
|
[['File', 0],
|
|
|
|
['Open', proc{sel_load()}, 0],
|
|
|
|
['Full Screen', proc{fullscreen()}, 0],
|
|
|
|
['Refresh', proc{refresh()}, 0],
|
2005-03-30 03:44:19 -05:00
|
|
|
'---',
|
|
|
|
['Exit', proc{exit}, 1]]
|
|
|
|
]
|
|
|
|
|
|
|
|
mbar = @root.add_menubar(menu_spec)
|
|
|
|
|
2009-03-05 22:56:38 -05:00
|
|
|
@html = Tk::HTML_Widget.new(:width=>512, :height=>384,
|
|
|
|
:padx=>5, :pady=>9,
|
2005-03-30 03:44:19 -05:00
|
|
|
:formcommand=>proc{|*args| form_cmd(*args)},
|
|
|
|
:imagecommand=>proc{|*args|
|
|
|
|
image_cmd(1, *args)
|
2009-03-05 22:56:38 -05:00
|
|
|
},
|
2005-03-30 03:44:19 -05:00
|
|
|
:scriptcommand=>proc{|*args|
|
|
|
|
script_cmd(*args)
|
2009-03-05 22:56:38 -05:00
|
|
|
},
|
2005-03-30 03:44:19 -05:00
|
|
|
:appletcommand=>proc{|*args|
|
|
|
|
applet_cmd(*args)
|
2009-03-05 22:56:38 -05:00
|
|
|
},
|
|
|
|
:hyperlinkcommand=>proc{|*args|
|
2005-03-30 03:44:19 -05:00
|
|
|
hyper_cmd(*args)
|
2009-03-05 22:56:38 -05:00
|
|
|
},
|
2005-03-30 03:44:19 -05:00
|
|
|
:fontcommand=>proc{|*args|
|
|
|
|
pick_font(*args)
|
2009-03-05 22:56:38 -05:00
|
|
|
},
|
2005-03-30 03:44:19 -05:00
|
|
|
:appletcommand=>proc{|*args|
|
|
|
|
run_applet('small', *args)
|
2009-03-05 22:56:38 -05:00
|
|
|
},
|
2005-03-30 03:44:19 -05:00
|
|
|
:bg=>'white', :tablerelief=>:raised)
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
@html.token_handler('meta', proc{|*args| meta(@html, *args)})
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
vscr = @html.yscrollbar(TkScrollbar.new)
|
|
|
|
hscr = @html.xscrollbar(TkScrollbar.new)
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
Tk.grid(@html, vscr, :sticky=>:news)
|
|
|
|
Tk.grid(hscr, :sticky=>:ew)
|
|
|
|
@root.grid_columnconfigure(0, :weight=>1)
|
|
|
|
@root.grid_columnconfigure(1, :weight=>0)
|
|
|
|
@root.grid_rowconfigure(0, :weight=>1)
|
|
|
|
@root.grid_rowconfigure(1, :weight=>0)
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
############################################
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
@html.clipwin.focus
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# If an arguent was specified, read it into the HTML widget.
|
|
|
|
#
|
|
|
|
Tk.update
|
|
|
|
if file && file != ""
|
|
|
|
load_file(file)
|
|
|
|
end
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
#
|
|
|
|
# A font chooser routine.
|
|
|
|
#
|
|
|
|
# html[:fontcommand] = pick_font
|
|
|
|
def pick_font(size, attrs)
|
|
|
|
# puts "FontCmd: #{size} #{attrs}"
|
2009-03-05 22:56:38 -05:00
|
|
|
[ ((attrs =~ /fixed/)? 'courier': 'charter'),
|
|
|
|
(12 * (1.2**(size.to_f - 4.0))).to_i,
|
|
|
|
((attrs =~ /italic/)? 'italic': 'roman'),
|
2005-03-30 03:44:19 -05:00
|
|
|
((attrs =~ /bold/)? 'bold': 'normal') ].join(' ')
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# This routine is called to pick fonts for the fullscreen view.
|
|
|
|
#
|
|
|
|
def pick_font_fs(size, attrs)
|
|
|
|
baseFontSize = 24
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# puts "FontCmd: #{size} #{attrs}"
|
2009-03-05 22:56:38 -05:00
|
|
|
[ ((attrs =~ /fixed/)? 'courier': 'charter'),
|
|
|
|
(baseFontSize * (1.2**(size.to_f - 4.0))).to_i,
|
|
|
|
((attrs =~ /italic/)? 'italic': 'roman'),
|
2005-03-30 03:44:19 -05:00
|
|
|
((attrs =~ /bold/)? 'bold': 'normal') ].join(' ')
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
#
|
|
|
|
#
|
|
|
|
def hyper_cmd(*args)
|
|
|
|
puts "HyperlinkCommand: #{args.inspect}"
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# This routine is called to run an applet
|
|
|
|
#
|
|
|
|
def run_applet(size, w, arglist)
|
|
|
|
applet_arg.value = Hash[*simplelist(arglist)]
|
|
|
|
|
|
|
|
return unless @applet_arg.key?('src')
|
|
|
|
|
|
|
|
src = @html.remove(@applet_arg['src'])
|
|
|
|
|
|
|
|
@applet_arg['window'] = w
|
|
|
|
@applet_arg['fontsize'] = size
|
|
|
|
|
|
|
|
begin
|
|
|
|
Tk.load_tclscript(src)
|
|
|
|
rescue => e
|
|
|
|
puts "Applet error: #{e.message}"
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
#
|
|
|
|
#
|
|
|
|
def form_cmd(n, cmd, *args)
|
|
|
|
# p [n, cmd, *args]
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
#
|
|
|
|
#
|
|
|
|
def move_big_image(b)
|
|
|
|
return unless @big_imgs.key?(b)
|
|
|
|
b.copy(@big_imgs[b])
|
|
|
|
@big_imgs[b].delete
|
|
|
|
@big_imgs.delete(b)
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
def image_cmd(hs, *args)
|
|
|
|
fn = args[0]
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
if @old_imgs.key?(fn)
|
|
|
|
return (@images[fn] = @old_imgs.delete(fn))
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
begin
|
|
|
|
img = TkPhotoImage.new(:file=>fn)
|
|
|
|
rescue
|
|
|
|
return ((hs)? @@smallgray: @@biggray)
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
if hs
|
|
|
|
img2 = TkPhotoImage.new
|
|
|
|
img2.copy(img, :subsample=>[2,2])
|
|
|
|
img.delete
|
|
|
|
img = img2
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
if img.width * img.height > 20000
|
|
|
|
b = TkPhotoImage.new(:width=>img.width, :height=>img.height)
|
|
|
|
@big_imgs[b] = img
|
|
|
|
img = b
|
|
|
|
Tk.after_idle(proc{ move_big_image(b) })
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
@images[fn] = img
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
img
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
#
|
|
|
|
# This routine is called for every <SCRIPT> markup
|
|
|
|
#
|
|
|
|
def script_cmd(*args)
|
|
|
|
# puts "ScriptCmd: #{args.inspect}"
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# This routine is called for every <APPLET> markup
|
|
|
|
#
|
|
|
|
def applet_cmd(w, arglist)
|
|
|
|
# puts "AppletCmd: w=#{w} arglist=#{arglist}"
|
|
|
|
#TkLabel.new(w, :text=>"The Applet #{w}", :bd=>2, :relief=>raised)
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# This binding fires when there is a click on a hyperlink
|
|
|
|
#
|
|
|
|
def href_binding(w, x, y)
|
|
|
|
lst = w.href(x, y)
|
|
|
|
unless lst.empty?
|
|
|
|
process_url(lst)
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
#
|
|
|
|
#
|
|
|
|
def sel_load
|
|
|
|
filetypes = [
|
2009-03-05 22:56:38 -05:00
|
|
|
['Html Files', ['.html', '.htm']],
|
2005-03-30 03:44:19 -05:00
|
|
|
['All Files', '*']
|
|
|
|
]
|
|
|
|
|
|
|
|
f = Tk.getOpenFile(:initialdir=>@last_dir, :filetypes=>filetypes)
|
|
|
|
if f != ''
|
|
|
|
load_file(f)
|
|
|
|
@last_dir = File.dirname(f)
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# Clear the screen.
|
|
|
|
#
|
|
|
|
def clear_screen
|
|
|
|
if @html_fs && @html_fs.exist?
|
|
|
|
w = @html_fs
|
|
|
|
else
|
|
|
|
w = @html
|
|
|
|
end
|
|
|
|
w.clear
|
|
|
|
@old_imgs.clear
|
|
|
|
@big_imgs.clear
|
|
|
|
@hotkey.clear
|
|
|
|
@images.each{|k, v| @old_imgs[k] = v }
|
|
|
|
@images.clear
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# Read a file
|
|
|
|
#
|
|
|
|
def read_file(name)
|
|
|
|
begin
|
|
|
|
fp = open(name, 'r')
|
|
|
|
ret = fp.read(File.size(name))
|
|
|
|
rescue
|
|
|
|
ret = nil
|
|
|
|
fp = nil
|
2009-03-05 22:56:38 -05:00
|
|
|
Tk.messageBox(:icon=>'error', :message=>"fail to open '#{name}'",
|
2005-03-30 03:44:19 -05:00
|
|
|
:type=>:ok)
|
|
|
|
ensure
|
|
|
|
fp.close if fp
|
|
|
|
end
|
|
|
|
ret
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# Process the given URL
|
|
|
|
#
|
|
|
|
def process_url(url)
|
|
|
|
case url[0]
|
|
|
|
when /^file:/
|
|
|
|
load_file(url[0][5..-1])
|
|
|
|
when /^exec:/
|
|
|
|
Tk.ip_eval(url[0][5..-1].tr('\\', ' '))
|
|
|
|
else
|
|
|
|
load_file(url[0])
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# Load a file into the HTML widget
|
|
|
|
#
|
|
|
|
def load_file(name)
|
|
|
|
return unless (doc = read_file(name))
|
|
|
|
clear_screen()
|
|
|
|
@last_file = name
|
|
|
|
if @html_fs && @html_fs.exist?
|
|
|
|
w = @html_fs
|
|
|
|
else
|
|
|
|
w = @html
|
|
|
|
end
|
|
|
|
w.configure(:base=>name)
|
|
|
|
w.parse(doc)
|
|
|
|
w.configure(:cursor=>'top_left_arrow')
|
|
|
|
@old_imgs.clear
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# Refresh the current file.
|
|
|
|
#
|
|
|
|
def refresh(*args)
|
|
|
|
load_file(@last_file) if @last_file
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# This routine is called whenever a "<meta>" markup is seen.
|
|
|
|
#
|
|
|
|
def meta(w, tag, alist)
|
|
|
|
v = Hash[*simplelist(alist)]
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
if v.key?('key') && v.key?('href')
|
|
|
|
@hotkey[v['key']] = w.resolve(v['href'])
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
if v.key?('next')
|
|
|
|
@hotkey['Down'] =v['next']
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
if v.key?('prev')
|
|
|
|
@hotkey['Up'] =v['prev']
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
if v.key?('other')
|
|
|
|
@hotkey['o'] =v['other']
|
|
|
|
end
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# Go from full-screen mode back to window mode.
|
|
|
|
#
|
|
|
|
def fullscreen_off
|
|
|
|
@fswin.destroy
|
|
|
|
@root.deiconify
|
|
|
|
Tk.update
|
|
|
|
@root.raise
|
|
|
|
@html.clipwin.focus
|
|
|
|
clear_screen()
|
|
|
|
@old_imgs.clear
|
|
|
|
refresh()
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
# Go from window mode to full-screen mode.
|
|
|
|
#
|
|
|
|
def fullscreen
|
|
|
|
if @fswin && @fswin.exist?
|
|
|
|
@fswin.deiconify
|
|
|
|
Tk.update
|
|
|
|
@fswin.raise
|
|
|
|
return
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
width = @root.winfo_screenwidth
|
|
|
|
height = @root.winfo_screenheight
|
2009-03-05 22:56:38 -05:00
|
|
|
@fswin = TkToplevel.new(:overrideredirect=>true,
|
2005-03-30 03:44:19 -05:00
|
|
|
:geometry=>"#{width}x#{height}+0+0")
|
|
|
|
|
2009-03-05 22:56:38 -05:00
|
|
|
@html_fs = Tk::HTML_Widget.new(@fswin, :padx=>5, :pady=>9,
|
2005-03-30 03:44:19 -05:00
|
|
|
:formcommand=>proc{|*args|
|
|
|
|
form_cmd(*args)
|
|
|
|
},
|
2009-03-05 22:56:38 -05:00
|
|
|
:imagecommand=>proc{|*args|
|
2005-03-30 03:44:19 -05:00
|
|
|
image_cmd(0, *args)
|
2009-03-05 22:56:38 -05:00
|
|
|
},
|
2005-03-30 03:44:19 -05:00
|
|
|
:scriptcommand=>proc{|*args|
|
|
|
|
script_cmd(*args)
|
2009-03-05 22:56:38 -05:00
|
|
|
},
|
2005-03-30 03:44:19 -05:00
|
|
|
:appletcommand=>proc{|*args|
|
|
|
|
applet_cmd(*args)
|
2009-03-05 22:56:38 -05:00
|
|
|
},
|
|
|
|
:hyperlinkcommand=>proc{|*args|
|
2005-03-30 03:44:19 -05:00
|
|
|
hyper_cmd(*args)
|
2009-03-05 22:56:38 -05:00
|
|
|
},
|
2005-03-30 03:44:19 -05:00
|
|
|
:appletcommand=>proc{|*args|
|
|
|
|
run_applet('big', *args)
|
2009-03-05 22:56:38 -05:00
|
|
|
},
|
2005-03-30 03:44:19 -05:00
|
|
|
:fontcommand=>proc{|*args|
|
|
|
|
pick_font_fs(*args)
|
2009-03-05 22:56:38 -05:00
|
|
|
},
|
|
|
|
:bg=>'white', :tablerelief=>:raised,
|
2005-03-30 03:44:19 -05:00
|
|
|
:cursor=>:tcross) {
|
|
|
|
pack(:fill=>:both, :expand=>true)
|
|
|
|
token_handler('meta', proc{|*args| meta(self, *args)})
|
|
|
|
}
|
|
|
|
|
|
|
|
clear_screen()
|
|
|
|
@old_imgs.clear
|
|
|
|
refresh()
|
|
|
|
Tk.update
|
|
|
|
@html_fs.clipwin.focus
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
#
|
|
|
|
#
|
|
|
|
def key_press(w, keysym)
|
|
|
|
return if @key_block
|
|
|
|
@key_block = true
|
|
|
|
Tk.after(250, proc{@key_block = false})
|
2004-07-01 05:38:48 -04:00
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
if @hotkey.key?(keysym)
|
|
|
|
process_url(@hotkey[keysym])
|
|
|
|
end
|
|
|
|
case keysym
|
|
|
|
when 'Escape'
|
|
|
|
if @fswin && @fswin.exist?
|
|
|
|
fullscreen_off()
|
|
|
|
else
|
|
|
|
fullscreen()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2004-07-01 05:38:48 -04:00
|
|
|
end
|
|
|
|
############################################
|
|
|
|
|
2005-03-30 03:44:19 -05:00
|
|
|
TkHTML_File_Viewer.new(file)
|
|
|
|
|
2004-07-01 05:38:48 -04:00
|
|
|
Tk.mainloop
|