*** empty log message ***
[booh] / bin / booh-gui
1 #!/usr/bin/ruby
2 #
3 #                         *  BOOH  *
4 #
5 # A.k.a `Best web-album Of the world, Or your money back, Humerus'.
6 #
7 # The acronyn sucks, however this is a tribute to Dragon Ball by
8 # Akira Toriyama, where the last enemy beaten by heroes of Dragon
9 # Ball is named "Boo". But there was already a free software project
10 # called Boo, so this one will be it "Booh". Or whatever.
11 #
12 #
13 # Copyright (c) 2004 Guillaume Cottenceau <gc3 at bluewin.ch>
14 #
15 # This software may be freely redistributed under the terms of the GNU
16 # public license version 2.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 require 'getoptlong'
23 require 'tempfile'
24
25 require 'gtk2'
26 require 'booh/GtkAutoTable'
27
28 require 'gettext'
29 include GetText
30 bindtextdomain("booh")
31
32 require 'rexml/document'
33 include REXML
34
35 require 'booh/booh-lib'
36 include Booh
37
38
39 #- options
40 $options = [
41     [ '--help',          '-h', GetoptLong::NO_ARGUMENT,       _("Get help message") ],
42
43     [ '--verbose-level', '-v', GetoptLong::REQUIRED_ARGUMENT, _("Set max verbosity level (0: errors, 1: warnings, 2: important messages, 3: other messages)") ],
44 ]
45
46 def usage
47     puts _("Usage: %s [OPTION]...") % File.basename($0)
48     $options.each { |ary|
49         printf " %3s, %-15s %s\n", ary[1], ary[0], ary[3]
50     }
51 end
52
53 def handle_options
54     parser = GetoptLong.new
55     parser.set_options(*$options.collect { |ary| ary[0..2] })
56     begin
57         parser.each_option do |name, arg|
58             case name
59             when '--help'
60                 usage
61                 exit(0)
62
63             when '--verbose-level'
64                 $verbose_level = arg.to_i
65
66             end
67         end
68     rescue
69         puts $!
70         usage
71         exit(1)
72     end
73 end
74
75 def read_config
76     $config = {}
77     $config_file = File.expand_path('~/.booh-gui-rc')
78     if File.readable?($config_file)
79         $xmldoc = REXML::Document.new(File.new($config_file))
80         $xmldoc.root.elements.each { |element|
81             txt = element.get_text
82             $config[element.name] = txt ? txt.value : nil
83         }
84     end
85     if !FileTest.directory?(File.expand_path('~/.booh-gui-files'))
86         system("mkdir ~/.booh-gui-files")
87     end
88 end
89
90 def write_config
91     ios = File.open($config_file, "w")
92     $xmldoc = Document.new "<booh-gui-rc version='#{$VERSION}'/>"
93     $xmldoc << XMLDecl.new( XMLDecl::DEFAULT_VERSION, $CURRENT_CHARSET )
94     $config.each_pair { |key, value|
95     elem = $xmldoc.root.add_element key
96         elem.add_text value.to_s
97     }
98     $xmldoc.write(ios, 0)
99     ios.close
100 end
101
102 def set_mousecursor(window, what)
103     window.set_cursor(Gdk::Cursor.new(what))
104 end
105 def set_mousecursor_wait(window)
106     set_mousecursor(window, Gdk::Cursor::WATCH)
107     Gtk.main_iteration while Gtk.events_pending?
108 end
109 def set_mousecursor_normal(window)
110     set_mousecursor(window, Gdk::Cursor::LEFT_PTR)
111 end
112
113 def build_full_dest_filename(filename)
114     source = $xmldoc.root.attributes['source']
115     dest = $xmldoc.root.attributes['destination']
116     dest_dir = make_dest_filename(from_utf8($current_path).sub(/^#{Regexp.quote(source)}/, dest))
117     return dest_dir + '/' + make_dest_filename(from_utf8(filename))
118 end
119
120 def view_element(filename)
121     if entry2type(filename) == 'video'
122         system("mplayer #{$current_path + '/' + filename}")
123         return
124     end
125
126     w = Gtk::Window.new
127
128     msg 3, "filename: #{filename}"
129     dest_img = build_full_dest_filename(filename).sub(/\.[^\.]+$/, '') + "-#{$default_size['fullscreen']}.jpg"
130     #- typically this file won't exist in case of videos; try with the largest thumbnail around
131     if !File.exists?(dest_img)
132         alternatives = Dir[build_full_dest_filename(filename).sub(/\.[^\.]+$/, '') + '-*'].sort
133         if not alternatives.empty?
134             dest_img = alternatives[-1]
135         else
136             return
137         end
138     end
139     i = Gtk::Image.new(dest_img)
140     f = Gtk::Frame.new
141     f.add(i)
142     f.set_shadow_type(Gtk::SHADOW_ETCHED_OUT)
143     hb = Gtk::HBox.new
144     hb.pack_start(Gtk::Label.new, true, true)
145     hb.pack_start(f, false, false)
146     hb.pack_end(Gtk::Label.new, true, true)
147     evt = Gtk::EventBox.new
148     evt.add(hb)
149
150     tooltips = Gtk::Tooltips.new
151     tooltips.set_tip(evt, utf8(File.basename(filename).gsub(/\.jpg/, '')), nil)
152     
153     hb2 = Gtk::HBox.new
154     hb2.pack_start(Gtk::Label.new, true, true)
155     hb2.pack_start(b = Gtk::Button.new(Gtk::Stock::CLOSE), false, false)
156     b.signal_connect('clicked') { w.destroy }
157     hb2.pack_end(Gtk::Label.new, true, true)
158
159     vb = Gtk::VBox.new
160     vb.pack_start(evt, false, false)
161     vb.pack_end(hb2, false, false)
162
163     w.add(vb)
164     w.signal_connect('delete-event') { w.destroy }
165     w.window_position = Gtk::Window::POS_CENTER
166     w.show_all
167 end
168
169 def add_thumbnail(autotable, name, type, filename, caption)
170
171     frame1 = Gtk::Frame.new
172     frame1.add(img = Gtk::Image.new($rotated_pixbufs[filename] ? $rotated_pixbufs[filename][:pixbuf] : filename))
173     frame1.set_shadow_type(Gtk::SHADOW_ETCHED_OUT)
174     hbox1 = Gtk::HBox.new
175     hbox1.pack_start(Gtk::Label.new, true, true)
176     hbox1.pack_start(frame1, false, false)
177     hbox1.pack_end(Gtk::Label.new, true, true)
178     evtbox = Gtk::EventBox.new
179     evtbox.add(hbox1)
180
181     tooltips = Gtk::Tooltips.new
182     tipname = File.basename(filename).gsub(/-\d+x\d+\.jpg/, '')
183     tooltips.set_tip(evtbox, utf8(type == 'video' ? (_("%s (video)") % tipname) : tipname), nil)
184
185     frame2 = Gtk::Frame.new
186     frame2.add(textview = Gtk::TextView.new.set_wrap_mode(Gtk::TextTag::WRAP_WORD))
187     frame2.set_shadow_type(Gtk::SHADOW_IN)
188
189     textview.buffer.text = utf8(caption)
190     textview.set_justification(Gtk::Justification::CENTER)
191
192     vbox = Gtk::VBox.new(false, 5)
193     vbox.pack_start(evtbox, false, false)
194     vbox.pack_start(frame2, false, false)
195     autotable.append(vbox, name)
196
197     #- to be able to grab focus of textview when retrieving vbox's position from AutoTable
198     $vbox2textview[vbox] = textview
199
200     #- to be able to find widgets by name
201     $name2widgets[name] = { :textview => textview }
202
203     rotate = Proc.new { |angle|
204         #- update rotate attribute
205         xmldir = $xmldoc.elements["//dir[@path='#{$current_path}']"]
206         felem = xmldir.elements["[@filename='#{name}']"]
207         felem.add_attribute('rotate', current_angle = ( felem.attributes['rotate'].to_i + angle ) % 360)
208
209         #- remove out of sync images
210         if !$rotated_pixbufs[filename]
211             $rotated_pixbufs[filename] = { :orig => img.pixbuf, :angle_to_orig => angle % 360 }
212             dest_img_base = build_full_dest_filename(name).sub(/\.[^\.]+$/, '')
213             for sizeobj in $images_size
214                 system("rm -f #{dest_img_base}-#{sizeobj['fullscreen']}.jpg #{dest_img_base}-#{sizeobj['thumbnails']}.jpg")
215             end
216         else
217             $rotated_pixbufs[filename][:angle_to_orig] = ( $rotated_pixbufs[filename][:angle_to_orig] + angle ) % 360
218         end
219         msg 3, "angle: #{angle}, angle to orig: #{$rotated_pixbufs[filename][:angle_to_orig]}"
220
221         #- rotate shown thumbnail
222         pixbuf = $rotated_pixbufs[filename][:orig].rotate($rotated_pixbufs[filename][:angle_to_orig] == 90 ? Gdk::Pixbuf::ROTATE_CLOCKWISE :
223                                                               $rotated_pixbufs[filename][:angle_to_orig] == 180 ? Gdk::Pixbuf::ROTATE_UPSIDEDOWN :
224                                                               $rotated_pixbufs[filename][:angle_to_orig] == 270 ? Gdk::Pixbuf::ROTATE_COUNTERCLOCKWISE :
225                                                               Gdk::Pixbuf::ROTATE_NONE )
226         msg 3, "sizes: #{pixbuf.width} #{pixbuf.height} - desired #{$default_thumbnails[:x]}x#{$default_thumbnails[:y]}"
227         if pixbuf.height > $default_thumbnails[:y]
228             img.pixbuf = $rotated_pixbufs[filename][:pixbuf] = pixbuf.scale(pixbuf.width * ($default_thumbnails[:y].to_f/pixbuf.height), $default_thumbnails[:y],
229                                                                             Gdk::Pixbuf::INTERP_BILINEAR)
230         elsif pixbuf.width < $default_thumbnails[:x] && pixbuf.height < $default_thumbnails[:y]
231             img.pixbuf = $rotated_pixbufs[filename][:pixbuf] = pixbuf.scale($default_thumbnails[:x], pixbuf.height * ($default_thumbnails[:x].to_f/pixbuf.width),
232                                                                             Gdk::Pixbuf::INTERP_BILINEAR)
233         else
234             img.pixbuf = $rotated_pixbufs[filename][:pixbuf] = pixbuf
235         end
236     }
237
238     delete = Proc.new {
239         after = autotable.get_next_widget(vbox)
240         if !after
241             after = autotable.get_previous_widget(vbox)
242         end
243         autotable.remove(vbox)
244         if after
245             $vbox2textview[after].grab_focus
246         end
247     }
248
249     textview.signal_connect('key-press-event') { |w, event|
250         propagate = true
251         textview.set_editable(event.keyval != Gdk::Keyval::GDK_Tab)
252         if event.keyval == Gdk::Keyval::GDK_Page_Up || event.keyval == Gdk::Keyval::GDK_Page_Down
253             $autotable_sw.signal_emit('key-press-event', event)
254         end
255         if event.state != 0
256             x, y = autotable.get_current_pos(vbox)
257             control_pressed = event.state & Gdk::Window::CONTROL_MASK != 0
258             shift_pressed = event.state & Gdk::Window::SHIFT_MASK != 0
259             alt_pressed = event.state & Gdk::Window::MOD1_MASK != 0
260             if event.keyval == Gdk::Keyval::GDK_Up && y > 0
261                 if control_pressed
262                     $vbox2textview[autotable.get_widget_at_pos(x, y - 1)].grab_focus
263                 end
264                 if shift_pressed
265                     autotable.move_up(vbox)
266                     textview.grab_focus  #- because if moving, focus is stolen
267                 end
268             end
269             if event.keyval == Gdk::Keyval::GDK_Down && y < autotable.get_max_y
270                 if control_pressed
271                     $vbox2textview[autotable.get_widget_at_pos(x, y + 1)].grab_focus
272                 end
273                 if shift_pressed
274                     autotable.move_down(vbox)
275                     textview.grab_focus  #- because if moving, focus is stolen
276                 end
277             end
278             if event.keyval == Gdk::Keyval::GDK_Left
279                 previous = autotable.get_previous_widget(vbox)
280                 if previous && autotable.get_current_pos(previous)[0] < x
281                     if control_pressed
282                         $vbox2textview[previous].grab_focus
283                     end
284                     if shift_pressed
285                         autotable.move_left(vbox)
286                         textview.grab_focus  #- because if moving, focus is stolen
287                     end
288                 end
289                 if alt_pressed
290                     rotate.call(-90)
291                 end
292             end
293             if event.keyval == Gdk::Keyval::GDK_Right
294                 next_ = autotable.get_next_widget(vbox)
295                 if next_ && autotable.get_current_pos(next_)[0] > x
296                     if control_pressed
297                         $vbox2textview[next_].grab_focus
298                     end
299                     if shift_pressed
300                         autotable.move_right(vbox)
301                         textview.grab_focus  #- because if moving, focus is stolen
302                     end
303                 end
304                 if alt_pressed
305                     rotate.call(90)
306                 end
307             end
308             if event.keyval == Gdk::Keyval::GDK_Delete && control_pressed
309                 delete.call
310             end
311             if event.keyval == Gdk::Keyval::GDK_Return && control_pressed
312                 view_element(File.basename(name))
313                 propagate = false
314             end
315         end
316         !propagate  #- propagate if needed
317     }
318
319     textview.signal_connect('focus-in-event') { |w, event|
320         textview.buffer.select_range(textview.buffer.get_iter_at_offset(0), textview.buffer.get_iter_at_offset(-1))
321         false  #- propagate
322     }
323
324     evtbox.signal_connect('button-press-event') { |w, event|
325         retval = true
326         if event.event_type == Gdk::Event::BUTTON_PRESS
327             if $delete.active?
328                 delete.call
329             end
330             if $r90.active?
331                 rotate.call(90)
332             end
333             if $r270.active?
334                 rotate.call(270)
335             end
336         end
337         textview.grab_focus
338         if event.event_type == Gdk::Event::BUTTON2_PRESS
339             view_element(File.basename(name))
340         else
341             retval = false  #- propagate
342         end
343         retval
344     }
345
346     vbox.signal_connect('button-press-event') { |w, event|
347         $gesture_press = { :name => name, :x => event.x, :y => event.y }
348         false
349     }
350
351     vbox.signal_connect('drag-data-received') { |w, ctxt, x, y, selection_data, info, time|
352         if $gesture_press && $gesture_press[:name] == name
353             if (($gesture_press[:x]-x)/($gesture_press[:y]-y)).abs > 2 && ($gesture_press[:x]-x).abs > 5
354                 angle = x-$gesture_press[:x] > 0 ? 90 : -90
355                 msg 3, "gesture rotate: #{angle}"
356                 rotate.call(angle)
357             end
358         end
359         $gesture_press = nil
360     }
361
362
363     vbox.show_all
364 end
365
366 def create_auto_table
367
368     $autotable = Gtk::AutoTable.new(5)
369
370     $autotable_sw = Gtk::ScrolledWindow.new(nil, nil)
371     $autotable_sw.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS)
372     $autotable_sw.add_with_viewport($autotable)
373
374     return $autotable_sw
375 end
376
377 def try_quit
378     save_changes
379     if $filename
380         ios = File.open($filename, "w")
381         $xmldoc.write(ios, 0)
382         ios.close
383     end
384     Gtk.main_quit
385 end
386
387 def show_popup(parent, msg)
388     dialog = Gtk::Dialog.new
389     dialog.title = utf8(_("Booh message"))
390     lbl = Gtk::Label.new
391     lbl.markup = msg
392     dialog.vbox.add(lbl)
393     dialog.add_button(Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK)
394
395     dialog.transient_for = parent
396     dialog.set_default_size(200, 120)
397     dialog.window_position = Gtk::Window::POS_CENTER_ON_PARENT
398     dialog.show_all
399
400     dialog.run
401     dialog.destroy
402 end
403
404 def save_changes
405     if !$current_path
406         return
407     end
408     #- remove and reinsert elements to reflect new ordering
409     save_attributes = {}
410     save_types = {}
411     xmldir = $xmldoc.elements["//dir[@path='#{$current_path}']"]
412     xmldir.elements.each { |element|
413         if element.name == 'image' || element.name == 'video'
414             save_types[element.attributes['filename']] = element.name
415             save_attributes[element.attributes['filename']] = element.attributes
416             element.remove
417         end
418     }
419     $autotable.current_order.each { |path|
420         chld = xmldir.add_element save_types[path], save_attributes[path]
421         chld.add_attribute('caption', $name2widgets[File.basename(path)][:textview].buffer.text)
422     }
423 end
424
425 def show_thumbnails
426     $autotable.clear
427     $vbox2textview = {}
428     $name2widgets = {}
429
430     xmldir = $xmldoc.elements["//dir[@path='#{$current_path}']"]
431     xmldir.elements.each { |element|
432         if element.name == 'image' || element.name == 'video'
433             dest_img = build_full_dest_filename(element.attributes['filename']).sub(/\.[^\.]+$/, '') + "-#{$default_size['thumbnails']}.jpg"
434             msg 3, "dest_img: #{dest_img}"
435             add_thumbnail($autotable, element.attributes['filename'], element.name, dest_img, from_utf8(element.attributes['caption']))
436         end
437     }
438 end
439
440 def open_file(filename)
441     $filename = nil
442     $current_path = nil   #- invalidate
443     $rotated_pixbufs = {}
444
445     begin
446         $xmldoc = REXML::Document.new File.new(filename)
447     rescue
448         $xmldoc = nil
449     end
450
451     if !$xmldoc || !$xmldoc.root || $xmldoc.root.name != 'booh'
452         return utf8(_("Not a booh file!"))
453     end
454
455     if !source = $xmldoc.root.attributes['source']
456         return utf8(_("Corrupted booh file..."))
457     end
458
459     if !dest = $xmldoc.root.attributes['destination']
460         return utf8(_("Corrupted booh file..."))
461     end
462
463     if !theme = $xmldoc.root.attributes['theme']
464         return utf8(_("Corrupted booh file..."))
465     end
466
467     $filename = filename
468     select_theme(theme)
469     $default_size = $images_size.detect { |sizeobj| sizeobj['default'] }
470     $default_size['thumbnails'] =~ /(.*)x(.*)/
471     $default_thumbnails = { :x => $1.to_i, :y => $2.to_i }
472
473     msg 3, "source: #{source}"
474
475     xmldir = $xmldoc.elements["//dir[@path='#{source}']"]
476     if !xmldir
477         return utf8(_("Corrupted booh file..."))
478     end
479
480     $albums_ts.clear
481
482     append_dir_elem = Proc.new { |parent_iter, location|
483         child_iter = $albums_ts.append(parent_iter)
484         child_iter[0] = File.basename(location)
485         child_iter[1] = location
486         msg 3, "puttin location: #{location}"
487         $xmldoc.elements.each("//dir[@path='#{location}']/dir") { |elem|
488             append_dir_elem.call(child_iter, elem.attributes['path'])
489         }
490     }
491     append_dir_elem.call(nil, source)
492
493     $albums_tv.expand_all
494     $albums_tv.selection.select_iter($albums_ts.iter_first)
495
496     return nil
497 end
498
499 def open_file_popup
500     fc = Gtk::FileChooserDialog.new(utf8(_("Open file")),
501                                     nil,
502                                     Gtk::FileChooser::ACTION_OPEN,
503                                     nil,
504                                     [Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT], [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL])
505     fc.add_shortcut_folder(File.expand_path("~/.booh-gui-files"))
506     fc.set_current_folder(File.expand_path("~/.booh-gui-files"))
507     fc.transient_for = $main_window
508     ok = false
509     while !ok
510         if fc.run == Gtk::Dialog::RESPONSE_ACCEPT
511             set_mousecursor_wait(fc.window)
512             msg = open_file(fc.filename)
513             set_mousecursor_normal(fc.window)
514             if msg
515                 show_popup(fc, msg)
516                 ok = false
517             else
518                 ok = true
519             end
520         else
521             ok = true
522         end
523     end
524     fc.destroy
525 end
526
527 def create_menu
528     mb = Gtk::MenuBar.new
529
530     filemenu = Gtk::MenuItem.new(utf8(_("_File")))
531     filesubmenu = Gtk::Menu.new
532     filesubmenu.append(new     = Gtk::MenuItem.new(utf8(_("_New (do nothing)"))))
533     filesubmenu.append(open    = Gtk::MenuItem.new(utf8(_("_Open"))))
534     filesubmenu.append(          Gtk::SeparatorMenuItem.new)
535     filesubmenu.append(save    = Gtk::MenuItem.new(utf8(_("_Save (do nothing - autosave on quit)"))))
536     filesubmenu.append(save_as = Gtk::MenuItem.new(utf8(_("Save _as... (do nothing)"))))
537     filesubmenu.append(          Gtk::SeparatorMenuItem.new)
538     filesubmenu.append(quit    = Gtk::MenuItem.new(utf8(_("_Quit"))))
539     filemenu.set_submenu(filesubmenu)
540     mb.append(filemenu)
541
542     open.signal_connect('activate') { open_file_popup }
543     quit.signal_connect('activate') { try_quit }
544
545     return mb
546 end
547
548 def create_toolbar
549     tb = Gtk::Toolbar.new
550
551     tb.insert(-1, open = Gtk::ToolButton.new(Gtk::Stock::OPEN))
552     open.label = utf8(_("Open"))
553
554     tb.insert(-1, Gtk::SeparatorToolItem.new)
555
556     tb.insert(-1, $r90 = Gtk::ToggleToolButton.new)
557     $r90.icon_widget = Gtk::Image.new("#{$FPATH}/images/stock-rotate-90-16.png")
558     $r90.label = utf8(_("Rotate"))
559     tb.insert(-1, $r270 = Gtk::ToggleToolButton.new)
560     $r270.icon_widget = Gtk::Image.new("#{$FPATH}/images/stock-rotate-270-16.png")
561     $r270.label = utf8(_("Rotate"))
562     tb.insert(-1, $delete = Gtk::ToggleToolButton.new(Gtk::Stock::DELETE))
563     $delete.label = utf8(_("Delete"))
564     tb.insert(-1, nothing = Gtk::ToolButton.new(''))
565     nothing.icon_widget = Gtk::Image.new("#{$FPATH}/images/stock-none-16.png")
566     nothing.label = utf8(_("None"))
567
568     open.signal_connect('clicked') { open_file_popup }
569
570     one_click_explain_try = Proc.new {
571         if !$config['one-click-explained']
572             show_popup($main_window, utf8(_("<b>One-Click tools.</b>
573
574 You have just clicked on a One-Click tool. When such a tool is activated
575 (<span foreground=\"darkblue\">Rotate clockwise</span>, <span foreground=\"darkblue\">Rotate counter-clockwise</span>, or <span foreground=\"darkblue\">Delete</span>), clicking on a
576 thumbnail will immediately apply the desired action.
577
578 Click the <span foreground=\"darkblue\">None</span> icon when you're finished with One-Click tools.
579 ")))
580             $config['one-click-explained'] = true
581         end
582     }
583
584     $r90.signal_connect('toggled') {
585         if $r90.active?
586             set_mousecursor($autotable.window, Gdk::Cursor::SB_RIGHT_ARROW)
587             one_click_explain_try.call
588             $r270.active = false
589             $delete.active = false
590         else
591             if !$r270.active? && !$delete.active?
592                 set_mousecursor_normal($autotable.window)
593             end
594         end
595     }
596     $r270.signal_connect('toggled') {
597         if $r270.active?
598             set_mousecursor($autotable.window, Gdk::Cursor::SB_LEFT_ARROW)
599             one_click_explain_try.call
600             $r90.active = false
601             $delete.active = false
602         else
603             if !$r90.active? && !$delete.active?
604                 set_mousecursor_normal($autotable.window)
605             end
606         end
607     }
608     $delete.signal_connect('toggled') {
609         if $delete.active?
610             set_mousecursor($autotable.window, Gdk::Cursor::PIRATE)
611             one_click_explain_try.call
612             $r90.active = false
613             $r270.active = false
614         else
615             if !$r90.active? && !$r270.active?
616                 set_mousecursor_normal($autotable.window)
617             end
618         end
619     }
620     nothing.signal_connect('clicked') {
621         $r90.active = $r270.active = $delete.active = false
622         set_mousecursor_normal($autotable.window)
623     }
624
625     return tb
626 end
627
628 def create_main_window
629
630     mb = create_menu
631
632     tb = create_toolbar
633
634     tbl = create_auto_table
635 #    open_file('/home/gc/booh/foo')
636
637     $albums_tv = Gtk::TreeView.new
638     $albums_tv.set_size_request(120, -1)
639     renderer = Gtk::CellRendererText.new
640     column = Gtk::TreeViewColumn.new('', renderer, { :text => 0 })
641     $albums_tv.append_column(column)
642     $albums_tv.set_headers_visible(false)
643     $albums_tv.selection.signal_connect('changed') { |w|
644         set_mousecursor_wait($main_window.window)
645         save_changes
646         iter = w.selected
647         if !iter
648             msg 3, "no selection"
649         else
650             $current_path = $albums_ts.get_value(iter, 1)
651             show_thumbnails
652         end
653         set_mousecursor_normal($main_window.window)
654     }
655     $albums_ts = Gtk::TreeStore.new(String, String)
656     $albums_tv.set_model($albums_ts)
657     $albums_tv.signal_connect('realize') { $albums_tv.grab_focus }
658
659     paned = Gtk::HPaned.new
660     paned.pack1($albums_tv, false, false)
661     paned.pack2(tbl, true, true)
662
663     main_vbox = Gtk::VBox.new(false, 0)
664     main_vbox.pack_start(mb, false, false)
665     main_vbox.pack_start(tb, false, false)
666     main_vbox.pack_start(paned, true, true)
667     main_vbox.pack_end($statusbar = Gtk::Statusbar.new, false, false)
668
669     $main_window = Gtk::Window.new
670     $main_window.add(main_vbox)
671     $main_window.signal_connect('delete-event') {
672         try_quit
673     }
674
675     #- read/save size and position of window
676     if $config['pos-x'] && $config['pos-y']
677         $main_window.move($config['pos-x'].to_i, $config['pos-y'].to_i)
678     else
679         $main_window.window_position = Gtk::Window::POS_CENTER
680     end
681     msg 3, "width: #{$config['width']}\n\n"
682     $main_window.set_default_size(($config['width'] || 600).to_i, ($config['height'] || 400).to_i)
683     $main_window.signal_connect('configure-event') {
684         msg 3, "configure: pos: #{$main_window.window.root_origin.inspect} size: #{$main_window.window.size.inspect}"
685         x, y = $main_window.window.root_origin
686         width, height = $main_window.window.size
687         $config['pos-x'] = x
688         $config['pos-y'] = y
689         $config['width'] = width
690         $config['height'] = height
691         false
692     }
693
694     $statusbar.push(0, utf8(_("Ready.")))
695     $main_window.show_all
696 end
697
698
699 handle_options
700 read_config
701
702 Gtk.init
703 create_main_window
704 if ARGV[0]
705     open_file(ARGV[0])
706 end
707 Gtk.main
708
709 write_config