booh-gui and distributable
authorgc <gc>
Mon, 28 Mar 2005 00:33:35 +0000 (00:33 +0000)
committergc <gc>
Mon, 28 Mar 2005 00:33:35 +0000 (00:33 +0000)
18 files changed:
README
VERSION [new file with mode: 0644]
bin/booh [moved from booh with 99% similarity]
bin/booh-gui [new file with mode: 0755]
booh-gui [deleted file]
lib/booh/GtkAutoTable.rb [new file with mode: 0644]
lib/booh/booh-lib.rb [new file with mode: 0644]
lib/booh/html-merges.rb [moved from html_merges.rb with 97% similarity]
lib/booh/pre-setup.rb [new file with mode: 0644]
po/Makefile
po/fr.po
pre-setup.rb [new file with mode: 0644]
setup.rb [new file with mode: 0644]
themes/simple/parameters.rb [deleted file]
themes/simple/skeleton_image.html [deleted file]
themes/simple/skeleton_index.html [deleted file]
themes/simple/skeleton_thumbnails.html [deleted file]
themes/simple/video.png [deleted file]

diff --git a/README b/README
index 11e0eb3..b864e6d 100644 (file)
--- a/README
+++ b/README
@@ -1,3 +1,5 @@
+        backend
+
 - automatically rotates portrait images thanks to exif info
 - great themability
 - immediate display of next image (preloading of next images)
 - intelligent use of the whole space of a typical browser window
 - subalbums support (images in any subdirectory depth)
 
+        frontend
+
+- very advanced capabilities by keyboard shortcuts
+
+   
 
         P4 HT 2.8 GHz, Linux 2.6.6 SMP 4G
 
diff --git a/VERSION b/VERSION
new file mode 100644 (file)
index 0000000..6e8bf73
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.1.0
diff --git a/booh b/bin/booh
similarity index 99%
rename from booh
rename to bin/booh
index bea0812..86d7bce 100755 (executable)
--- a/booh
+++ b/bin/booh
@@ -26,8 +26,8 @@ require 'rexml/document'
 include REXML
 require 'timeout'
 
-require 'booh-lib'
-require 'html_merges'
+require 'booh/booh-lib'
+require 'booh/html-merges'
 
 #- bind text domain as soon as possible because some _() functions are called early to build data structures
 bindtextdomain("booh")
diff --git a/bin/booh-gui b/bin/booh-gui
new file mode 100755 (executable)
index 0000000..fe690e4
--- /dev/null
@@ -0,0 +1,493 @@
+#!/usr/bin/ruby
+#
+#                         *  BOOH  *
+#
+# A.k.a `Best web-album Of the world, Or your money back, Humerus'.
+#
+# The acronyn sucks, however this is a tribute to Dragon Ball by
+# Akira Toriyama, where the last enemy beaten by heroes of Dragon
+# Ball is named "Boo". But there was already a free software project
+# called Boo, so this one will be it "Booh". Or whatever.
+#
+#
+# Copyright (c) 2004 Guillaume Cottenceau <gc3 at bluewin.ch>
+#
+# This software may be freely redistributed under the terms of the GNU
+# public license version 2.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+
+require 'gtk2'
+class Gtk::Allocation
+    def to_s; "x:#{x} y:#{y} width:#{width} height:#{height}"; end
+end
+require 'booh/GtkAutoTable'
+
+require 'gettext'
+include GetText
+bindtextdomain("booh")
+
+require 'rexml/document'
+include REXML
+
+require 'booh/booh-lib'
+include Booh
+
+
+def read_config
+    $config = {}
+    $config_file = File.expand_path('~/.booh-gui-rc')
+    if File.readable?($config_file)
+        $xmldoc = REXML::Document.new(File.new($config_file))
+        $xmldoc.root.elements.each { |element|
+            txt = element.get_text
+            $config[element.name] = txt ? txt.value : nil
+        }
+    end
+    if !FileTest.directory?(File.expand_path('~/.booh-gui-files'))
+        system("mkdir ~/.booh-gui-files")
+    end
+end
+
+def write_config
+    ios = File.open($config_file, "w")
+    $xmldoc = Document.new "<booh-gui-rc version='#{$VERSION}'/>"
+    $xmldoc << XMLDecl.new( XMLDecl::DEFAULT_VERSION, $CURRENT_CHARSET )
+    $config.each_pair { |key, value|
+    elem = $xmldoc.root.add_element key
+        elem.add_text value.to_s
+    }
+    $xmldoc.write(ios, 0)
+    ios.close
+end
+
+def set_mousecursor_wait(window)
+    window.set_cursor(Gdk::Cursor.new(Gdk::Cursor::WATCH))
+    Gtk.main_iteration while Gtk.events_pending?
+end
+def set_mousecursor_normal(window)
+    window.set_cursor(Gdk::Cursor.new(Gdk::Cursor::LEFT_PTR))
+end
+
+def build_full_dest_filename(filename)
+    source = $xmldoc.root.attributes['source']
+    dest = $xmldoc.root.attributes['destination']
+    dest_dir = make_dest_filename(from_utf8($current_path).sub(/^#{Regexp.quote(source)}/, dest))
+    return dest_dir + '/' + make_dest_filename(from_utf8(filename))
+end
+
+def view_image(filename)
+    w = Gtk::Window.new
+
+    default_size = $images_size.detect { |sizeobj| sizeobj['default'] }
+    dest_img = build_full_dest_filename(filename).sub(/\.[^\.]+$/, '') + "-#{default_size['fullscreen']}.jpg"
+    #- typically this file won't exist in case of videos; try with the largest thumbnail around
+    if !File.exists?(dest_img)
+        alternatives = Dir[build_full_dest_filename(filename).sub(/\.[^\.]+$/, '') + '-*'].sort
+        if alternatives
+            dest_img = alternatives[-1]
+        else
+            return
+        end
+    end
+    i = Gtk::Image.new(dest_img)
+    f = Gtk::Frame.new
+    f.add(i)
+    f.set_shadow_type(Gtk::SHADOW_ETCHED_OUT)
+    hb = Gtk::HBox.new
+    hb.pack_start(Gtk::Label.new, true, true)
+    hb.pack_start(f, false, false)
+    hb.pack_end(Gtk::Label.new, true, true)
+    evt = Gtk::EventBox.new
+    evt.add(hb)
+
+    tooltips = Gtk::Tooltips.new
+    tooltips.set_tip(evt, utf8(File.basename(filename).gsub(/\.jpg/, '')), nil)
+    
+    hb2 = Gtk::HBox.new
+    hb2.pack_start(Gtk::Label.new, true, true)
+    hb2.pack_start(b = Gtk::Button.new(Gtk::Stock::CLOSE), false, false)
+    b.signal_connect('clicked') { w.destroy }
+    hb2.pack_end(Gtk::Label.new, true, true)
+
+    vb = Gtk::VBox.new
+    vb.pack_start(evt, false, false)
+    vb.pack_end(hb2, false, false)
+
+    w.add(vb)
+    w.signal_connect('delete-event') { w.destroy }
+    w.window_position = Gtk::Window::POS_CENTER
+    w.show_all
+end
+
+def add_thumbnail(autotable, name, type, filename, caption)
+
+    frame1 = Gtk::Frame.new
+    frame1.add(img = Gtk::Image.new(filename))
+    frame1.set_shadow_type(Gtk::SHADOW_ETCHED_OUT)
+    hbox1 = Gtk::HBox.new
+    hbox1.pack_start(Gtk::Label.new, true, true)
+    hbox1.pack_start(frame1, false, false)
+    hbox1.pack_end(Gtk::Label.new, true, true)
+    evtbox = Gtk::EventBox.new
+    evtbox.add(hbox1)
+
+    tooltips = Gtk::Tooltips.new
+    tipname = utf8(File.basename(filename).gsub(/-\d+x\d+\.jpg/, ''))
+    tooltips.set_tip(evtbox, type == 'video' ? __("%s (video)", tipname) : tipname, nil)
+
+    frame2 = Gtk::Frame.new
+    frame2.add(textview = Gtk::TextView.new.set_wrap_mode(Gtk::TextTag::WRAP_WORD))
+    frame2.set_shadow_type(Gtk::SHADOW_IN)
+
+    textview.buffer.text = utf8(caption)
+    textview.set_justification(Gtk::Justification::CENTER)
+
+    vbox = Gtk::VBox.new(false, 5)
+    vbox.pack_start(evtbox, false, false)
+    vbox.pack_start(frame2, false, false)
+    autotable.append(vbox, name)
+
+    #- to be able to grab focus of textview when retrieving vbox's position from AutoTable
+    $vbox2textview[vbox] = textview
+
+    #- to be able to find widgets by name
+    $name2widgets[name] = { :textview => textview }
+
+    textview.signal_connect('key-press-event') { |w, event|
+        propagate = true
+        textview.set_editable(event.keyval != Gdk::Keyval::GDK_Tab)
+        if event.keyval == Gdk::Keyval::GDK_Page_Up || event.keyval == Gdk::Keyval::GDK_Page_Down
+            $autotable_sw.signal_emit('key-press-event', event)
+        end
+        if event.state != 0
+            x, y = autotable.get_current_pos(vbox)
+            control_pressed = event.state & Gdk::Window::CONTROL_MASK != 0
+            shift_pressed = event.state & Gdk::Window::SHIFT_MASK != 0
+            if event.keyval == Gdk::Keyval::GDK_Up && y > 0
+                if control_pressed
+                    $vbox2textview[autotable.get_widget_at_pos(x, y - 1)].grab_focus
+                end
+                if shift_pressed
+                    autotable.move_up(vbox)
+                    textview.grab_focus  #- because if moving, focus is stolen
+                end
+            end
+            if event.keyval == Gdk::Keyval::GDK_Down && y < autotable.get_max_y
+                if control_pressed
+                    $vbox2textview[autotable.get_widget_at_pos(x, y + 1)].grab_focus
+                end
+                if shift_pressed
+                    autotable.move_down(vbox)
+                    textview.grab_focus  #- because if moving, focus is stolen
+                end
+            end
+            if event.keyval == Gdk::Keyval::GDK_Left
+                previous = autotable.get_previous_widget(vbox)
+                if autotable.get_current_pos(previous)[0] < x
+                    if control_pressed
+                        $vbox2textview[previous].grab_focus
+                    end
+                    if shift_pressed
+                        autotable.move_left(vbox)
+                        textview.grab_focus  #- because if moving, focus is stolen
+                    end
+                end
+            end
+            if event.keyval == Gdk::Keyval::GDK_Right
+                next_ = autotable.get_next_widget(vbox)
+                if autotable.get_current_pos(next_)[0] > x
+                    if control_pressed
+                        $vbox2textview[next_].grab_focus
+                    end
+                    if shift_pressed
+                        autotable.move_right(vbox)
+                        textview.grab_focus  #- because if moving, focus is stolen
+                    end
+                end
+            end
+            if event.keyval == Gdk::Keyval::GDK_Delete && shift_pressed
+                after = autotable.get_next_widget(vbox)
+                if !after
+                    after = autotable.get_previous_widget(vbox)
+                end
+                autotable.remove(vbox)
+                if after
+                    $vbox2textview[after].grab_focus
+                end
+            end
+            if event.keyval == Gdk::Keyval::GDK_Return && control_pressed
+                view_image(File.basename(name))
+                propagate = false
+            end
+        end
+        !propagate  #- propagate if needed
+    }
+
+    textview.signal_connect('focus-in-event') { |w, event|
+        textview.buffer.select_range(textview.buffer.get_iter_at_offset(0), textview.buffer.get_iter_at_offset(-1))
+        false  #- propagate
+    }
+
+    evtbox.signal_connect('button-press-event') { |w, event|
+        textview.grab_focus
+        if event.event_type == Gdk::Event::BUTTON2_PRESS
+            view_image(File.basename(name))
+            true
+        else
+            false  #- propagate
+        end
+    }
+
+    vbox.show_all
+end
+
+def create_auto_table
+
+    $autotable = Gtk::AutoTable.new(5)
+
+    $autotable_sw = Gtk::ScrolledWindow.new(nil, nil)
+    $autotable_sw.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS)
+    $autotable_sw.add_with_viewport($autotable)
+
+    return $autotable_sw
+end
+
+def try_quit
+    save_changes
+    if $filename
+        ios = File.open($filename, "w")
+        $xmldoc.write(ios, 0)
+        ios.close
+    end
+    Gtk.main_quit
+end
+
+def show_popup(parent, msg)
+    dialog = Gtk::Dialog.new
+    dialog.title = utf8(_("Booh message"))
+    dialog.transient_for = parent
+    dialog.set_default_size(200, 120)
+    dialog.vbox.add(Gtk::Label.new(msg))
+
+    dialog.add_button(Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK)
+    dialog.show_all
+    dialog.run
+    dialog.destroy
+end
+
+def save_changes
+    if !$current_path
+        return
+    end
+    #- remove and reinsert elements to reflect new ordering
+    save_attributes = {}
+    save_types = {}
+    xmldir = $xmldoc.elements["//dir[@path='#{$current_path}']"]
+    xmldir.elements.each { |element|
+        if element.name == 'image' || element.name == 'video'
+            save_types[element.attributes['filename']] = element.name
+            save_attributes[element.attributes['filename']] = element.attributes
+            element.remove
+        end
+    }
+    $autotable.current_order.each { |path|
+        chld = xmldir.add_element save_types[path], save_attributes[path]
+        chld.add_attribute('caption', $name2widgets[File.basename(path)][:textview].buffer.text)
+    }
+end
+
+def show_thumbnails
+    default_size = $images_size.detect { |sizeobj| sizeobj['default'] }
+    $autotable.clear
+    $vbox2textview = {}
+    $name2widgets = {}
+
+    xmldir = $xmldoc.elements["//dir[@path='#{$current_path}']"]
+    xmldir.elements.each { |element|
+        if element.name == 'image' || element.name == 'video'
+            dest_img = build_full_dest_filename(element.attributes['filename']).sub(/\.[^\.]+$/, '') + "-#{default_size['thumbnails']}.jpg"
+            puts "dest_img: #{dest_img}"
+            add_thumbnail($autotable, element.attributes['filename'], element.name, dest_img, from_utf8(element.attributes['caption']))
+        end
+    }
+end
+
+def open_file(filename)
+    $filename = nil
+    $current_path = nil   #- invalidate
+
+    begin
+        $xmldoc = REXML::Document.new File.new(filename)
+    rescue
+        $xmldoc = nil
+    end
+
+    if !$xmldoc || !$xmldoc.root || $xmldoc.root.name != 'booh'
+        return utf8(_("Not a booh file!"))
+    end
+
+    if !source = $xmldoc.root.attributes['source']
+        return utf8(_("Corrupted booh file..."))
+    end
+
+    if !dest = $xmldoc.root.attributes['destination']
+        return utf8(_("Corrupted booh file..."))
+    end
+
+    $filename = filename
+    select_theme('simple')
+    puts "source: #{source}"
+
+    xmldir = $xmldoc.elements["//dir[@path='#{source}']"]
+    if !xmldir
+        return utf8(_("Corrupted booh file..."))
+    end
+
+    $albums_ts.clear
+
+    append_dir_elem = Proc.new { |parent_iter, location|
+        child_iter = $albums_ts.append(parent_iter)
+        child_iter[0] = File.basename(location)
+        child_iter[1] = location
+        puts "puttin location: #{location}"
+        $xmldoc.elements.each("//dir[@path='#{location}']/dir") { |elem|
+            append_dir_elem.call(child_iter, elem.attributes['path'])
+        }
+    }
+    append_dir_elem.call(nil, source)
+
+    $albums_tv.expand_all
+    $albums_tv.selection.select_iter($albums_ts.iter_first)
+
+    return nil
+end
+
+def create_menu
+    mb = Gtk::MenuBar.new
+
+    filemenu = Gtk::MenuItem.new(utf8(_("_File")))
+    filesubmenu = Gtk::Menu.new
+    filesubmenu.append(new     = Gtk::MenuItem.new(utf8(_("_New (do nothing)"))))
+    filesubmenu.append(open    = Gtk::MenuItem.new(utf8(_("_Open"))))
+    filesubmenu.append(          Gtk::SeparatorMenuItem.new)
+    filesubmenu.append(save    = Gtk::MenuItem.new(utf8(_("_Save (do nothing - autosave on quit)"))))
+    filesubmenu.append(save_as = Gtk::MenuItem.new(utf8(_("Save _as... (do nothing)"))))
+    filesubmenu.append(          Gtk::SeparatorMenuItem.new)
+    filesubmenu.append(quit    = Gtk::MenuItem.new(utf8(_("_Quit"))))
+    filemenu.set_submenu(filesubmenu)
+    mb.append(filemenu)
+
+    open.signal_connect('activate') { |w|
+        fc = Gtk::FileChooserDialog.new(utf8(_("Open file")),
+                                        nil,
+                                        Gtk::FileChooser::ACTION_OPEN,
+                                        nil,
+                                        [Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT], [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL])
+        fc.add_shortcut_folder(File.expand_path("~/.booh-gui-files"))
+        fc.set_current_folder(File.expand_path("~/.booh-gui-files"))
+        fc.transient_for = $main_window
+        ok = false
+        while !ok
+            if fc.run == Gtk::Dialog::RESPONSE_ACCEPT
+                set_mousecursor_wait(fc.window)
+                msg = open_file(fc.filename)
+                set_mousecursor_normal(fc.window)
+                if msg
+                    show_popup(fc, msg)
+                    ok = false
+                else
+                    ok = true
+                end
+            else
+                ok = true
+            end
+        end
+        fc.destroy
+    }
+
+    quit.signal_connect('activate') { |w|
+        try_quit
+    }
+
+    return mb
+end
+
+def create_main_window
+
+    mb = create_menu
+
+    tbl = create_auto_table
+#    open_file('/home/gc/booh/foo')
+
+    $albums_tv = Gtk::TreeView.new
+    $albums_tv.set_size_request(120, -1)
+    renderer = Gtk::CellRendererText.new
+    column = Gtk::TreeViewColumn.new('', renderer, { :text => 0 })
+    $albums_tv.append_column(column)
+    $albums_tv.set_headers_visible(false)
+    $albums_tv.selection.signal_connect('changed') { |w|
+        save_changes
+       iter = w.selected
+        if !iter
+            puts "no selection"
+        else
+            $current_path = $albums_ts.get_value(iter, 1)
+            show_thumbnails
+        end
+    }
+    $albums_ts = Gtk::TreeStore.new(String, String)
+    $albums_tv.set_model($albums_ts)
+
+    paned = Gtk::HPaned.new
+    paned.pack1($albums_tv, false, false)
+    paned.pack2(tbl, true, true)
+
+    main_vbox = Gtk::VBox.new(false, 5)
+    main_vbox.pack_start(mb, false, false)
+    main_vbox.pack_start(paned, true, true)
+    main_vbox.pack_end($statusbar = Gtk::Statusbar.new, false, false)
+
+    $main_window = Gtk::Window.new
+    $main_window.add(main_vbox)
+    $main_window.signal_connect('delete-event') {
+        try_quit
+    }
+
+    #- read/save size and position of window
+    if $config['pos-x'] && $config['pos-y']
+        $main_window.move($config['pos-x'].to_i, $config['pos-y'].to_i)
+    else
+        $main_window.window_position = Gtk::Window::POS_CENTER
+    end
+    puts "width: #{$config['width']}\n\n"
+    $main_window.set_default_size(($config['width'] || 600).to_i, ($config['height'] || 400).to_i)
+    $main_window.signal_connect('configure-event') {
+        puts "configure: pos: #{$main_window.window.root_origin.inspect} size: #{$main_window.window.size.inspect}"
+        x, y = $main_window.window.root_origin
+        width, height = $main_window.window.size
+        $config['pos-x'] = x
+        $config['pos-y'] = y
+        $config['width'] = width
+        $config['height'] = height
+        false
+    }
+
+    $statusbar.push(0, utf8(_("Ready.")))
+    $main_window.show_all
+end
+
+
+read_config
+
+Gtk.init
+create_main_window
+if ARGV[0]
+    open_file(ARGV[0])
+end
+Gtk.main
+
+write_config
diff --git a/booh-gui b/booh-gui
deleted file mode 100755 (executable)
index 0cc0ce8..0000000
--- a/booh-gui
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/ruby
-#
-#                         *  BOOH  *
-#
-# A.k.a `Best web-album Of the world, Or your money back, Humerus'.
-#
-# The acronyn sucks, however this is a tribute to Dragon Ball by
-# Akira Toriyama, where the last enemy beaten by heroes of Dragon
-# Ball is named "Boo". But there was already a free software project
-# called Boo, so this one will be it "Booh". Or whatever.
-#
-#
-# Copyright (c) 2004 Guillaume Cottenceau <gc3 at bluewin.ch>
-#
-# This software may be freely redistributed under the terms of the GNU
-# public license version 2.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-
-require 'gtk2'
-
-Gtk.init
-
-vbox = Gtk::VBox.new
-
-vbox.pack_start(Gtk::Label.new("booh!"))
-vbox.pack_start(b = Gtk::Button.new("booh :("))
-
-b.signal_connect('clicked') do
-    Gtk.main_quit
-end
-
-w = Gtk::Window.new.add(vbox)
-w.signal_connect('delete-event') do
-  Gtk.main_quit
-end
-
-#w.set_position(Gtk::Window::CENTER)
-
-w.show_all
-
-Gtk.main
diff --git a/lib/booh/GtkAutoTable.rb b/lib/booh/GtkAutoTable.rb
new file mode 100644 (file)
index 0000000..9c2e1f9
--- /dev/null
@@ -0,0 +1,319 @@
+require 'gtk2'
+
+class Gtk::AutoTable < Gtk::VBox
+
+    def initialize(row_spacings)
+        @children = []
+        @containers = []
+        @width = -1
+        @old_widths = []
+        @row_spacings = row_spacings
+        @table = nil
+        super
+        recreate_table
+        signal_connect('size-allocate') { |w, allocation|
+            puts "got self allocation: #{allocation}"
+            if @width != allocation.width
+                if !@old_widths.include?(allocation.width)
+                    @width = allocation.width
+                    @old_widths.unshift(@width)
+                    @old_widths = @old_widths[0..2]
+                    redistribute(false)
+                else
+                    puts "\tDISABLING: #{allocation.width} - #{@old_widths.join(',')}"
+                end
+            end
+        }
+        @timeout = Gtk.timeout_add(100) {
+            if @queue_draw
+                queue_draw
+                @queue_draw = false
+            end
+            true
+        }
+    end
+
+
+    def destroy
+        Gtk.timeout_remove(@timeout)
+        super.destroy
+    end
+
+    #- add (append) a widget to the list of automatically handled widgets
+    def append(widget, name)
+        #- create my child hash
+        child = { :widget => widget, :name => name }
+        #- put it in the table if last container's widget has been allocated
+        last_container = @containers[-1]
+        if !last_container || last_container[:contained_element] && last_container[:contained_element][:allocation]
+            put(child, (last_container ? last_container[:x] : -1) + 1, last_container ? last_container[:y] : 0)
+        end
+        #- add it to the internal children array
+        @children << child
+
+        #- connect 'size-allocate' signal to be sure to update allocation when received
+        widget.signal_connect('size-allocate') { |w, allocation|
+            puts "got allocation for #{w.hash}: #{allocation} (#{allocation.hash})"
+            chld = @children.find { |e| e[:widget] == w }
+            if chld
+                old = chld[:allocation]
+                #- need to copy values because the object will be magically exploded when widget is removed
+                chld[:allocation] = { :width => allocation.width, :height => allocation.height }
+                if !old #|| old[:width] != allocation.width # || old[:height] != allocation.height
+                    puts "redistribute!"
+                    chld == @children[0] && old and puts "************ old was #{old[:width]} #{old[:height]}"
+                    redistribute(true)
+                end
+            else
+                warn "Critical: child not found!"
+            end
+        }
+
+        #- handle reordering with drag and drop
+        Gtk::Drag.source_set(widget, Gdk::Window::BUTTON1_MASK, [['reorder-elements', Gtk::Drag::TARGET_SAME_APP, 1]], Gdk::DragContext::ACTION_MOVE)
+        Gtk::Drag.dest_set(widget, Gtk::Drag::DEST_DEFAULT_ALL, [['reorder-elements', Gtk::Drag::TARGET_SAME_APP, 1]], Gdk::DragContext::ACTION_MOVE)
+        widget.signal_connect('drag-data-get') { |w, ctxt, selection_data, info, time|
+            selection_data.set(Gdk::Selection::TYPE_STRING, get_current_number(widget).to_s)
+        }
+        widget.signal_connect('drag-data-received') { |w, ctxt, x, y, selection_data, info, time|
+            ctxt.targets.each { |target|
+                if target.name == 'reorder-elements'
+                    insert(selection_data.data.to_i, get_current_number(widget))
+                end
+            }
+        }
+    end
+
+    #- remove a widget from the list of automatically handled widgets
+    def remove(widget)
+        @children.each_with_index { |chld, index|
+            if chld[:widget] == widget
+                @children.delete_at(index)
+                redistribute(true)
+                return true
+            end
+        }
+        return false
+    end
+
+    #- remove all widgets
+    def clear
+        @children = []
+        redistribute(false)
+    end
+
+    #- get current order of widget
+    def current_order
+        return @children.collect { |chld| chld[:name] }
+    end
+
+    #- get current [x, y] position of widget within automatically handled table
+    def get_current_pos(widget)
+        chld = @children.find { |e| e[:widget] == widget }
+        if chld
+            return [ chld[:x], chld[:y] ]
+        else
+            return nil
+        end
+    end
+
+    #- get current number (rank in current ordering) of widget within automatically handled table
+    def get_current_number(widget)
+        @children.each_with_index { |chld, index|
+            if chld[:widget] == widget
+                return index
+            end
+        }
+        return -1
+    end
+
+    #- insert a widget before another by numbers
+    def insert(src, dst)
+        chld = @children.delete_at(src)
+        @children[dst > src ? dst - 1 : dst, 0] = chld
+        redistribute(true)
+    end
+
+    #- get widget at [x, y] position of automatically handled table
+    def get_widget_at_pos(x, y)
+        @children.each { |chld|
+            if chld[:x] == x && chld[:y] == y
+                return chld[:widget]
+            end
+        }
+        return nil
+    end
+
+    #- get maximum `y' position within the automatically handled table
+    def get_max_y
+        return @children[-1][:y]
+    end
+
+    #- get the current `previous' widget (table-wise); important since widgets can be reordered with mouse drags
+    def get_previous_widget(widget)
+        @children.each_with_index { |chld, index|
+            if chld[:widget] == widget
+                if index == 0
+                    return nil
+                else
+                    return @children[index - 1][:widget]
+                end
+            end
+        }
+        return nil
+    end
+
+    #- get the current `next' widget (table-wise); important since widgets can be reordered with mouse drags
+    def get_next_widget(widget)
+        @children.each_with_index { |chld, index|
+            if chld[:widget] == widget
+                if index == @children.size - 1
+                    return nil
+                else
+                    return @children[index + 1][:widget]
+                end
+            end
+        }
+        return nil
+    end
+
+    #- move specified widget `up' in the table
+    def move_up(widget)
+        @children.each_with_index { |chld, index|
+            if chld[:widget] == widget && chld[:y] > 0
+                @children.each_with_index { |chld2, index2|
+                    if chld2[:x] == chld[:x] && chld[:y] == chld2[:y] + 1
+                        @children[index], @children[index2] = chld2, chld
+                        redistribute(true)
+                        return true
+                    end
+                }
+            end
+        }
+        return false
+    end
+
+    #- move specified widget `down' in the table
+    def move_down(widget)
+        @children.each_with_index { |chld, index|
+            if chld[:widget] == widget && chld[:y] < get_max_y
+                @children.each_with_index { |chld2, index2|
+                    if chld2[:x] == chld[:x] && chld[:y] == chld2[:y] - 1
+                        @children[index], @children[index2] = chld2, chld
+                        redistribute(true)
+                        return true
+                    end
+                }
+            end
+        }
+        return false
+    end
+
+    #- move specified widget `left' in the table
+    def move_left(widget)
+        @children.each_with_index { |chld, index|
+            if chld[:widget] == widget && chld[:x] > 0
+                @children[index], @children[index - 1] = @children[index - 1], @children[index]
+                redistribute(true)
+                return true
+            end
+        }
+        return false
+    end
+
+    #- move specified widget `right' in the table
+    def move_right(widget)
+        @children.each_with_index { |chld, index|
+            if chld[:widget] == widget && @children[index + 1] && chld[:x] < @children[index + 1][:x]
+                @children[index], @children[index + 1] = @children[index + 1], @children[index]
+                redistribute(true)
+                return true
+            end
+        }
+        return false
+    end
+
+
+    private
+
+    def put(element, x, y)
+        puts "putting #{element[:widget].hash} at #{x},#{y}"
+        element[:x] = x
+        element[:y] = y
+        container = @containers.find { |e| e[:x] == x && e[:y] == y }
+        if !container
+            container = { :x => x, :y => y, :widget => Gtk::VBox.new, :fake => Gtk::Label.new('fake') }
+            puts "attaching at #{x},#{y}"
+            @table.attach(container[:widget], x, x + 1, y, y + 1, Gtk::FILL, Gtk::FILL, 5, 0)
+            @containers << container
+        end
+        if container[:contained_element]
+            container[:widget].remove(container[:contained_element][:widget])
+        end
+        container[:contained_element] = element
+        container[:widget].add(element[:widget])
+        @table.show_all
+    end
+
+    def recreate_table
+        @containers.each { |e|
+            if e[:contained_element]
+                if e[:contained_element][:widget].focus?
+                    puts "I HAVE A FOCUS!"
+                end
+                e[:widget].remove(e[:contained_element][:widget])
+            end
+        }
+        @containers = []
+        if @table
+            remove(@table)
+            @table.destroy
+        end
+        add(@table = Gtk::Table.new(0, 0, true))
+        @table.set_row_spacings(@row_spacings)
+    end
+
+    def redistribute(force)
+        puts "redistribute: "
+        @children.each { |e| print e[:allocation] ? 'O' : '.' }; puts
+        if unallocated = @children.find { |e| !e[:allocation] }
+            #- waiting for allocations. replace last displayed widget with first unallocated.
+            last_container = @containers[-1]
+            put(unallocated, last_container[:x], last_container[:y])
+
+        else
+            if @children.size == 0
+                recreate_table
+                return
+
+            else
+                maxwidth = allocation.width
+                xpix = 5 + @children[0][:allocation][:width] + 5
+                x = 1
+                y = 0
+                @children[1..-1].each { |e|
+                    if xpix + 5 + e[:allocation][:width] + 5 > maxwidth - 1
+                        x = 0
+                        y += 1
+                        xpix = 0
+                    end
+                    e[:xnew] = x
+                    e[:ynew] = y
+                    x += 1
+                    xpix += 5 + e[:allocation][:width] + 5
+                }
+                if @children[1..-1].find { |e| e[:xnew] != e[:x] || e[:ynew] != e[:y] } || force
+                    puts "I can proceed with #{allocation}"
+                    recreate_table
+                    put(@children[0], 0, 0)
+                    @children[1..-1].each { |e|
+                        put(e, e[:xnew], e[:ynew])
+                    }
+                    show_all
+                    @queue_draw = true
+                end
+            end
+        end
+    end
+
+end
diff --git a/lib/booh/booh-lib.rb b/lib/booh/booh-lib.rb
new file mode 100644 (file)
index 0000000..94c18b7
--- /dev/null
@@ -0,0 +1,99 @@
+#!/usr/bin/ruby
+#
+#                         *  BOOH  *
+#
+# A.k.a `Best web-album Of the world, Or your money back, Humerus'.
+#
+# The acronyn sucks, however this is a tribute to Dragon Ball by
+# Akira Toriyama, where the last enemy beaten by heroes of Dragon
+# Ball is named "Boo". But there was already a free software project
+# called Boo, so this one will be it "Booh". Or whatever.
+#
+#
+# Copyright (c) 2004 Guillaume Cottenceau <gc3 at bluewin.ch>
+#
+# This software may be freely redistributed under the terms of the GNU
+# public license version 2.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+require 'iconv'
+
+require 'gettext'
+include GetText
+bindtextdomain("booh")
+
+require 'booh/config.rb'
+require 'booh/version.rb'
+
+module Booh
+    $verbose_level = 2
+    $CURRENT_CHARSET = `locale charmap`.chomp
+
+    def utf8(string)
+        return Iconv::iconv("UTF-8", $CURRENT_CHARSET, string).to_s
+    end
+    
+    def from_utf8(string)
+        return Iconv::iconv($CURRENT_CHARSET, "UTF-8", string).to_s
+    end
+
+    def make_dest_filename(orig_filename)
+        #- we remove non alphanumeric characters but need to do that
+        #- cleverly to not end up with two similar dest filenames. we won't
+        #- urlencode because urldecode might happen in the browser.
+        return orig_filename.unpack("C*").collect { |v| v.chr =~ /[a-zA-Z\-_0-9\.\/]/ ? v.chr : sprintf("%2X", v) }.to_s
+    end
+
+    def __(string, *args)
+        if args.size == 0
+            _(string)
+        elsif args.size == 1
+            sprintf(_(string), args[0])
+        elsif args.size == 2
+            sprintf(_(string), args[0], args[1])
+        end
+    end
+
+    def msg(verbose_level, msg)
+        if verbose_level <= $verbose_level
+            if verbose_level == 0
+                warn __("\t***ERROR***: %s\n", msg)
+            elsif verbose_level == 1
+                warn __("\tWarning: %s\n", msg)
+            else
+                puts msg
+            end
+        end
+    end
+
+    def msg_(verbose_level, msg)
+        if verbose_level <= $verbose_level
+            if verbose_level == 0
+                warn __("\t***ERROR***: %s", msg)
+            elsif verbose_level == 1
+                warn __("\tWarning: %s", msg)
+            else
+                print msg
+            end
+        end
+    end
+
+    def die(msg)
+        puts msg
+        exit 1
+    end
+
+    def select_theme(name)
+        $theme = name
+        msg 3, __("Selecting theme `%s'", $theme)
+        themedir = "#{$FPATH}/themes/#{$theme}"
+        if !File.directory?(themedir)
+            die __("Theme was not found (tried %s directory).", themedir)
+        end
+        require "#{themedir}/parameters.rb"
+    end
+
+end
similarity index 97%
rename from html_merges.rb
rename to lib/booh/html-merges.rb
index 818df8a..a4b20d0 100644 (file)
 
 require 'gettext'
 bindtextdomain("booh")
-require 'iconv'
-$CURRENT_CHARSET = `locale charmap`.chomp
 
-def utf8(string)
-    return Iconv::iconv("UTF-8", $CURRENT_CHARSET, string).to_s
-end
-
-def from_utf8(string)
-    return Iconv::iconv($CURRENT_CHARSET, "UTF-8", string).to_s
-end
+require 'booh/booh-lib'
+include Booh
 
 $head_code = <<'EOF'
 <meta name="generator" content="Generated by Booh! http://zarb.org/~gc/html/booh.html">
diff --git a/lib/booh/pre-setup.rb b/lib/booh/pre-setup.rb
new file mode 100644 (file)
index 0000000..8b624cf
--- /dev/null
@@ -0,0 +1,31 @@
+# Copyright (C) 2004-2005 Masao Mutoh, Laurent Sansonetti 
+
+copyright = <<EOS
+# This file is automatically generated by the installer.
+# Do not edit by hands.
+EOS
+
+File.open('config.rb', 'w') do |file|
+    file.print <<EOS
+
+module Booh
+    $FPATH = '#{config('data-dir')}/booh'
+end
+EOS
+end
+
+File.open('version.rb', 'w') do |file|
+    begin
+        version = IO.readlines('../../VERSION').join
+    rescue Errno::ENOENT
+        version = "CVS"
+    end
+    file.print <<EOS
+# This file is automatically generated by the installer.
+# Do not edit by hands.
+
+module Booh
+    VERSION = "#{version}"
+end
+EOS
+end
index ddd208c..56625f4 100644 (file)
@@ -2,21 +2,14 @@
 PGOAL = booh
 
 # ruby files to search translatable strings in
-RB_FILES = ../booh ../html_merges.rb
+RB_FILES = ../booh ../html_merges.rb ../booh-gui ../booh-lib.rb
 
 POFILES = $(wildcard *.po)
 MOFILES = $(POFILES:%.po=%.mo)
 LANGS = $(POFILES:%.po=%)
 
-PREFIX = /usr/local
-DATADIR = $(PREFIX)/share
-LOCALEDIR=$(DATADIR)/locale
-
 all: $(MOFILES)
 
-%.mo: %.po
-       rmsgfmt -o $@ "$<"
-
 merge: $(PGOAL).pot
        @for n in $(POFILES); do \
                echo "Merging $$n"; \
@@ -28,11 +21,5 @@ $(PGOAL).pot: $(RB_FILES)
        rm -f $@
        rgettext --keyword=__ -o $@ $(RB_FILES)
 
-install:
-       for l in $(LANGS); do \
-               install -d $(LOCALEDIR)/$$l/LC_MESSAGES; \
-               install -m 644 $$l.mo $(LOCALEDIR)/$$l/LC_MESSAGES/$(PGOAL).mo; \
-       done
-
 clean:
-       @rm -rf *.mo $(PGOAL).pot
+       @rm -rf $(PGOAL).pot
index ad7af9f..a006846 100644 (file)
--- a/po/fr.po
+++ b/po/fr.po
@@ -6,8 +6,8 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
-"POT-Creation-Date: 2005-03-16 22:19+0100\n"
-"PO-Revision-Date: 2005-03-16 22:25+0100\n"
+"POT-Creation-Date: 2005-03-28 01:49+0200\n"
+"PO-Revision-Date: 2005-03-28 01:53+0200\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "MIME-Version: 1.0\n"
@@ -15,34 +15,34 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
 
-#: ../booh:39
+#: ../booh:37
 msgid "Get help message"
 msgstr "Obtenir le message d'aide"
 
-#: ../booh:41
+#: ../booh:39
 msgid "Don't check for needed external programs at startup"
 msgstr ""
 "Ne pas vérifier la présence des programmes externes nécessaires au démarrage"
 
-#: ../booh:43
+#: ../booh:41
 msgid "Directory which contains original images/videos as files or subdirs"
 msgstr ""
 "Répertoire qui contient les images et vidéos originales comme fichiers ou "
 "sous-répetoires"
 
-#: ../booh:44
+#: ../booh:42
 msgid "Directory which will contain the web-album"
 msgstr "Répertoire qui va contenir le web-album"
 
-#: ../booh:45
+#: ../booh:43
 msgid "Clean destination directory"
 msgstr "Nettoyer le répertoire de destination"
 
-#: ../booh:47
+#: ../booh:45
 msgid "Select HTML theme to use"
 msgstr "Sélectionner le thème HTML à utiliser"
 
-#: ../booh:48
+#: ../booh:46
 msgid ""
 "File containing config listing images and videos within directories with "
 "captions"
@@ -50,12 +50,12 @@ msgstr ""
 "Fichier contenant la configuration avec la liste des images et des vidéos au "
 "sein des sous-répertoires avec légendes"
 
-#: ../booh:49
+#: ../booh:47
 msgid "Filename where the script will output a config skeleton"
 msgstr ""
 "Nom de fichier à utiliser pour la création du squelette de configuration"
 
-#: ../booh:50
+#: ../booh:48
 msgid ""
 "File containing config listing, where to merge new images/videos from --"
 "source"
@@ -63,12 +63,12 @@ msgstr ""
 "Fichier contenant la configuration, où fusionner les nouvelles images et "
 "vidéos de --source"
 
-#: ../booh:52
+#: ../booh:50
 msgid "Specify the number of processors for multi-processors machines"
 msgstr ""
 "Spécifier le nombre de processeurs pour les ordinateurs multi-processeurs"
 
-#: ../booh:54
+#: ../booh:52
 msgid ""
 "Set max verbosity level (0: errors, 1: warnings, 2: important messages, 3: "
 "other messages)"
@@ -76,55 +76,37 @@ msgstr ""
 "Déterminer le niveau de verbosité maximal (0 : erreurs, 1: avertissements, "
 "2 : messages importants, 3 : autres messages)"
 
-#: ../booh:75
+#: ../booh:61
 msgid "Usage: %s [OPTION]..."
 msgstr "Utilisation: %s [OPTION]..."
 
-#: ../booh:84
-msgid "\t***ERROR***: %s\n"
-msgstr "\t***ERREUR*** : %s\n"
-
-#: ../booh:86
-msgid "\tWarning: %s\n"
-msgstr "\tAvertissement : %s\n"
-
-#: ../booh:96
-msgid "\t***ERROR***: %s"
-msgstr "\t***ERREUR*** : %s"
-
-#: ../booh:98
-msgid "\tWarning: %s"
-msgstr "\tAvertissement : %s"
-
-#: ../booh:126
+#: ../booh:83
 msgid "Argument to --source must be a directory"
 msgstr "L'argument de --source doit être un répertoire"
 
-#: ../booh:131
+#: ../booh:88
 msgid "If --destination exists, it must be a directory"
 msgstr "Si --destination existe, il doit être un répertoire"
 
-#: ../booh:143 ../booh:162
+#: ../booh:91
+msgid ""
+"Sorry, destination directory can't contain non simple alphanumeric "
+"characters."
+msgstr "Désolé, le répertoire de destination ne peut pas contenir des caractères non alphanumériques."
+
+#: ../booh:103 ../booh:120
 msgid "Config file does not exist or is unreadable."
 msgstr "Le fichier de configuration n'existe pas ou est non lisible."
 
-#: ../booh:181
+#: ../booh:144
 msgid "Missing --source parameter."
 msgstr "Il manque le paramètre --source."
 
-#: ../booh:184
+#: ../booh:147
 msgid "Missing --destination parameter."
 msgstr "Il manque le paramètre --destination."
 
-#: ../booh:200
-msgid "Selecting theme `%s'"
-msgstr "Sélection du thème `%s'"
-
-#: ../booh:203
-msgid "Theme was not found (tried %s directory)."
-msgstr "Thème non trouvé (le répertoire %s a été essayé)."
-
-#: ../booh:214
+#: ../booh:167
 msgid ""
 "The `%s' program is typically needed. Re-run with --no-check if you're sure "
 "you're fine without it."
@@ -132,124 +114,199 @@ msgstr ""
 "Le programme `%s' est typiquement nécessaire. Relancez avec --no-check si "
 "vous êtes sûr que vous n'en aurez pas besoin."
 
-#: ../booh:224
+#: ../booh:177
 msgid "No `%s' found for substitution"
 msgstr "`%s' non trouvé pour substitution"
 
-#: ../booh:290
+#: ../booh:243
 msgid "forgetting runaway process (transcode sucks again?)"
-msgstr "laissons tomber ce processus qui tourne en rond (transcode sux encore une fois ?)"
+msgstr ""
+"laissons tomber ce processus qui tourne en rond (transcode sux encore une "
+"fois ?)"
 
-#: ../booh:324
+#: ../booh:295
 msgid ""
 "* could not extract first image of video %s with transcode, will try first "
 "converting with mencoder"
-msgstr "* impossible d'extraire la première image de la vidéo %s avec transcode, je vais tenter le coup en convertissant d'abord avec mencoder"
+msgstr ""
+"* impossible d'extraire la première image de la vidéo %s avec transcode, je "
+"vais tenter le coup en convertissant d'abord avec mencoder"
 
-#: ../booh:334
+#: ../booh:305
 msgid "could not extract first image of video %s encoded by mencoder"
-msgstr "impossible d'extraire la première image de la vidéo %s encodée par mencoder"
+msgstr ""
+"impossible d'extraire la première image de la vidéo %s encodée par mencoder"
 
-#: ../booh:338
+#: ../booh:309
 msgid "could not make mencoder to encode %s to mpeg4"
 msgstr "impossible d'encoder %s en mpeg4 avec mencoder"
 
-#: ../booh:455
+#: ../booh:425
 msgid "Handling %s from config list..."
 msgstr "Prise en charge de %s selon le fichier de configuration..."
 
-#: ../booh:462
+#: ../booh:432
 msgid "Examining %s..."
 msgstr "Examen de %s..."
 
-#: ../booh:488
+#: ../booh:458
 msgid "\t%s images"
 msgstr "\t%s images"
 
-#: ../booh:490
+#: ../booh:460
 msgid "\t%s videos"
 msgstr "\t%s vidéos"
 
-#: ../booh:509
+#: ../booh:479
 msgid "Outputting in %s..."
 msgstr "Écriture dans %s..."
 
-#: ../booh:521
+#: ../booh:491
 msgid "\tcreating images thumbnails..."
 msgstr "\tcréation des vignettes des images..."
 
-#: ../booh:538
+#: ../booh:508
 msgid "\tcreating videos thumbnails..."
 msgstr "\tcréation des vignettes des vidéos..."
 
-#: ../booh:556
+#: ../booh:528
 msgid "small"
 msgstr "petit"
 
-#: ../booh:556
+#: ../booh:528
 msgid "medium"
 msgstr "moyen"
 
-#: ../booh:556
+#: ../booh:528
 msgid "large"
 msgstr "grand"
 
-#: ../booh:558
+#: ../booh:530
 msgid "\tgenerating HTML pages..."
 msgstr "\tgénération des pages HTML..."
 
-#: ../booh:565 ../html_merges.rb:273 ../html_merges.rb:307
+#: ../booh:537 ../html_merges.rb:270 ../html_merges.rb:304
 msgid "Run slideshow!"
 msgstr "Lancer la présentation !"
 
-#: ../booh:598
+#: ../booh:570
 msgid "(no preview)"
 msgstr "(aucune vignette)"
 
-#: ../booh:637
+#: ../booh:609
 msgid "Return to thumbnails"
 msgstr "Retourner aux vignettes"
 
-#: ../booh:659
+#: ../booh:631
 msgid "\tfixating configuration file..."
 msgstr "\tfixation du fichier de configuration..."
 
-#: ../booh:699
+#: ../booh:671
 msgid "Theme `%s' has no default size."
 msgstr "Le theme `%s' n'a pas de taille par défaut."
 
-#: ../booh:704
+#: ../booh:676
 msgid "\trescanning directories to generate all `index.html' files..."
-msgstr "\trelecture des sous-répertoires pour générer tous les fichiers `index.html'..."
-
-#: ../booh:713
-msgid "."
-msgstr "."
+msgstr ""
+"\trelecture des sous-répertoires pour générer tous les fichiers `index."
+"html'..."
 
-#: ../booh:787 ../booh:790
+#: ../booh:762 ../booh:765
 msgid "Return to albums"
 msgstr "Retourner aux albums"
 
-#: ../booh:802
+#: ../booh:777
 msgid " all done."
 msgstr " fin."
 
-#: ../html_merges.rb:274
+#: ../html_merges.rb:271
 msgid "Stop slideshow"
 msgstr "Arrêter la présentation"
 
-#: ../html_merges.rb:283
+#: ../html_merges.rb:280
 msgid "<<- First"
 msgstr "<<- Premier"
 
-#: ../html_merges.rb:289
+#: ../html_merges.rb:286
 msgid "<- Previous"
 msgstr "<- Précédent"
 
-#: ../html_merges.rb:295
+#: ../html_merges.rb:292
 msgid "Next ->"
 msgstr "Prochain ->"
 
-#: ../html_merges.rb:301
+#: ../html_merges.rb:298
 msgid "Last ->>"
 msgstr "Dernier ->>"
+
+#: ../booh-gui:139
+msgid "%s (video)"
+msgstr "%s (vidéo)"
+
+#: ../booh-gui:270
+msgid "Booh message"
+msgstr "Message de booh"
+
+#: ../booh-gui:329
+msgid "Not a booh file!"
+msgstr "Ce n'est pas un fichier de booh !"
+
+#: ../booh-gui:333 ../booh-gui:337 ../booh-gui:346
+msgid "Corrupted booh file..."
+msgstr "Le fichier booh est corrompu..."
+
+#: ../booh-gui:371
+msgid "_File"
+msgstr "_Fichier"
+
+#: ../booh-gui:373
+msgid "_New (do nothing)"
+msgstr "_Nouveau (ne fait rien)"
+
+#: ../booh-gui:374
+msgid "_Open"
+msgstr "_Ouvrir"
+
+#: ../booh-gui:376
+msgid "_Save (do nothing - autosave on quit)"
+msgstr "_Sauver (ne fait rien - sauvegarde automatique au moment de quitter)"
+
+#: ../booh-gui:377
+msgid "Save _as... (do nothing)"
+msgstr "Sauvegarder _sous... (ne fait rien)"
+
+#: ../booh-gui:379
+msgid "_Quit"
+msgstr "_Quitter"
+
+#: ../booh-gui:384
+msgid "Open file"
+msgstr "Ouverture d'un fichier"
+
+#: ../booh-gui:478
+msgid "Ready."
+msgstr "Prêt."
+
+#: ../booh-lib.rb:64
+msgid "\t***ERROR***: %s\n"
+msgstr "\t***ERREUR*** : %s\n"
+
+#: ../booh-lib.rb:66
+msgid "\tWarning: %s\n"
+msgstr "\tAvertissement : %s\n"
+
+#: ../booh-lib.rb:76
+msgid "\t***ERROR***: %s"
+msgstr "\t***ERREUR*** : %s"
+
+#: ../booh-lib.rb:78
+msgid "\tWarning: %s"
+msgstr "\tAvertissement : %s"
+
+#: ../booh-lib.rb:92
+msgid "Selecting theme `%s'"
+msgstr "Sélection du thème `%s'"
+
+#: ../booh-lib.rb:95
+msgid "Theme was not found (tried %s directory)."
+msgstr "Thème non trouvé (le répertoire %s a été essayé)."
diff --git a/pre-setup.rb b/pre-setup.rb
new file mode 100644 (file)
index 0000000..5209d93
--- /dev/null
@@ -0,0 +1,25 @@
+# Copyright (C) 2004-2005 Dafydd Harries
+#
+# Loosely based on pre-setup.rb from rbbr by Masao Mutoh.
+
+basename = "booh"
+config = Config::CONFIG
+podir = srcdir_root + "/po/"
+
+# Create MO files.
+
+Dir.glob("po/*.po") do |file|
+    lang = /po\/(.*)\.po/.match(file).to_a[1]
+    mo_path_bits = ['data', 'locale', lang, 'LC_MESSAGES']
+    mo_path = File.join(mo_path_bits)
+
+    (0 ... mo_path_bits.length).each do |i|
+        path = File.join(mo_path_bits[0 .. i])
+        puts path
+        Dir.mkdir(path) unless FileTest.exists?(path)
+    end
+
+    system("msgfmt po/#{lang}.po -o #{mo_path}/#{basename}.mo")
+
+    raise "msgfmt failed on po/#{lang}.po" if $? != 0
+end
diff --git a/setup.rb b/setup.rb
new file mode 100644 (file)
index 0000000..0807023
--- /dev/null
+++ b/setup.rb
@@ -0,0 +1,1360 @@
+#
+# setup.rb
+#
+# Copyright (c) 2000-2004 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the terms of
+# the GNU LGPL, Lesser General Public License version 2.1.
+#
+
+unless Enumerable.method_defined?(:map)   # Ruby 1.4.6
+  module Enumerable
+    alias map collect
+  end
+end
+
+unless File.respond_to?(:read)   # Ruby 1.6
+  def File.read(fname)
+    open(fname) {|f|
+      return f.read
+    }
+  end
+end
+
+def File.binread(fname)
+  open(fname, 'rb') {|f|
+    return f.read
+  }
+end
+
+# for corrupted windows stat(2)
+def File.dir?(path)
+  File.directory?((path[-1,1] == '/') ? path : path + '/')
+end
+
+
+class SetupError < StandardError; end
+
+def setup_rb_error(msg)
+  raise SetupError, msg
+end
+
+#
+# Config
+#
+
+if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
+  ARGV.delete(arg)
+  require arg.split(/=/, 2)[1]
+  $".push 'rbconfig.rb'
+else
+  require 'rbconfig'
+end
+
+def multipackage_install?
+  FileTest.directory?(File.dirname($0) + '/packages')
+end
+
+
+class ConfigItem
+  def initialize(name, template, default, desc)
+    @name = name.freeze
+    @template = template
+    @value = default
+    @default = default.dup.freeze
+    @description = desc
+  end
+
+  attr_reader :name
+  attr_reader :description
+
+  attr_accessor :default
+  alias help_default default
+
+  def help_opt
+    "--#{@name}=#{@template}"
+  end
+
+  def value
+    @value
+  end
+
+  def eval(table)
+    @value.gsub(%r<\$([^/]+)>) { table[$1] }
+  end
+
+  def set(val)
+    @value = check(val)
+  end
+
+  private
+
+  def check(val)
+    setup_rb_error "config: --#{name} requires argument" unless val
+    val
+  end
+end
+
+class BoolItem < ConfigItem
+  def config_type
+    'bool'
+  end
+
+  def help_opt
+    "--#{@name}"
+  end
+
+  private
+
+  def check(val)
+    return 'yes' unless val
+    unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val
+      setup_rb_error "config: --#{@name} accepts only yes/no for argument"
+    end
+    (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no'
+  end
+end
+
+class PathItem < ConfigItem
+  def config_type
+    'path'
+  end
+
+  private
+
+  def check(path)
+    setup_rb_error "config: --#{@name} requires argument"  unless path
+    path[0,1] == '$' ? path : File.expand_path(path)
+  end
+end
+
+class ProgramItem < ConfigItem
+  def config_type
+    'program'
+  end
+end
+
+class SelectItem < ConfigItem
+  def initialize(name, template, default, desc)
+    super
+    @ok = template.split('/')
+  end
+
+  def config_type
+    'select'
+  end
+
+  private
+
+  def check(val)
+    unless @ok.include?(val.strip)
+      setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
+    end
+    val.strip
+  end
+end
+
+class PackageSelectionItem < ConfigItem
+  def initialize(name, template, default, help_default, desc)
+    super name, template, default, desc
+    @help_default = help_default
+  end
+
+  attr_reader :help_default
+
+  def config_type
+    'package'
+  end
+
+  private
+
+  def check(val)
+    unless File.dir?("packages/#{val}")
+      setup_rb_error "config: no such package: #{val}"
+    end
+    val
+  end
+end
+
+class ConfigTable_class
+
+  def initialize(items)
+    @items = items
+    @table = {}
+    items.each do |i|
+      @table[i.name] = i
+    end
+    ALIASES.each do |ali, name|
+      @table[ali] = @table[name]
+    end
+  end
+
+  include Enumerable
+
+  def each(&block)
+    @items.each(&block)
+  end
+
+  def key?(name)
+    @table.key?(name)
+  end
+
+  def lookup(name)
+    @table[name] or raise ArgumentError, "no such config item: #{name}"
+  end
+
+  def add(item)
+    @items.push item
+    @table[item.name] = item
+  end
+
+  def remove(name)
+    item = lookup(name)
+    @items.delete_if {|i| i.name == name }
+    @table.delete_if {|name, i| i.name == name }
+    item
+  end
+
+  def new
+    dup()
+  end
+
+  def savefile
+    '.config'
+  end
+
+  def load
+    begin
+      t = dup()
+      File.foreach(savefile()) do |line|
+        k, v = *line.split(/=/, 2)
+        t[k] = v.strip
+      end
+      t
+    rescue Errno::ENOENT
+      setup_rb_error $!.message + "#{File.basename($0)} config first"
+    end
+  end
+
+  def save
+    @items.each {|i| i.value }
+    File.open(savefile(), 'w') {|f|
+      @items.each do |i|
+        f.printf "%s=%s\n", i.name, i.value if i.value
+      end
+    }
+  end
+
+  def [](key)
+    lookup(key).eval(self)
+  end
+
+  def []=(key, val)
+    lookup(key).set val
+  end
+
+end
+
+c = ::Config::CONFIG
+
+rubypath = c['bindir'] + '/' + c['ruby_install_name']
+
+major = c['MAJOR'].to_i
+minor = c['MINOR'].to_i
+teeny = c['TEENY'].to_i
+version = "#{major}.#{minor}"
+
+# ruby ver. >= 1.4.4?
+newpath_p = ((major >= 2) or
+             ((major == 1) and
+              ((minor >= 5) or
+               ((minor == 4) and (teeny >= 4)))))
+
+if c['rubylibdir']
+  # V < 1.6.3
+  _stdruby         = c['rubylibdir']
+  _siteruby        = c['sitedir']
+  _siterubyver     = c['sitelibdir']
+  _siterubyverarch = c['sitearchdir']
+elsif newpath_p
+  # 1.4.4 <= V <= 1.6.3
+  _stdruby         = "$prefix/lib/ruby/#{version}"
+  _siteruby        = c['sitedir']
+  _siterubyver     = "$siteruby/#{version}"
+  _siterubyverarch = "$siterubyver/#{c['arch']}"
+else
+  # V < 1.4.4
+  _stdruby         = "$prefix/lib/ruby/#{version}"
+  _siteruby        = "$prefix/lib/ruby/#{version}/site_ruby"
+  _siterubyver     = _siteruby
+  _siterubyverarch = "$siterubyver/#{c['arch']}"
+end
+libdir = '-* dummy libdir *-'
+stdruby = '-* dummy rubylibdir *-'
+siteruby = '-* dummy site_ruby *-'
+siterubyver = '-* dummy site_ruby version *-'
+parameterize = lambda {|path|
+  path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')\
+      .sub(/\A#{Regexp.quote(libdir)}/,      '$libdir')\
+      .sub(/\A#{Regexp.quote(stdruby)}/,     '$stdruby')\
+      .sub(/\A#{Regexp.quote(siteruby)}/,    '$siteruby')\
+      .sub(/\A#{Regexp.quote(siterubyver)}/, '$siterubyver')
+}
+libdir          = parameterize.call(c['libdir'])
+stdruby         = parameterize.call(_stdruby)
+siteruby        = parameterize.call(_siteruby)
+siterubyver     = parameterize.call(_siterubyver)
+siterubyverarch = parameterize.call(_siterubyverarch)
+
+if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
+  makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
+else
+  makeprog = 'make'
+end
+
+common_conf = [
+  PathItem.new('prefix', 'path', c['prefix'],
+               'path prefix of target environment'),
+  PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
+               'the directory for commands'),
+  PathItem.new('libdir', 'path', libdir,
+               'the directory for libraries'),
+  PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
+               'the directory for shared data'),
+  PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
+               'the directory for man pages'),
+  PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
+               'the directory for man pages'),
+  PathItem.new('stdruby', 'path', stdruby,
+               'the directory for standard ruby libraries'),
+  PathItem.new('siteruby', 'path', siteruby,
+      'the directory for version-independent aux ruby libraries'),
+  PathItem.new('siterubyver', 'path', siterubyver,
+               'the directory for aux ruby libraries'),
+  PathItem.new('siterubyverarch', 'path', siterubyverarch,
+               'the directory for aux ruby binaries'),
+  PathItem.new('rbdir', 'path', '$siterubyver',
+               'the directory for ruby scripts'),
+  PathItem.new('sodir', 'path', '$siterubyverarch',
+               'the directory for ruby extentions'),
+  PathItem.new('rubypath', 'path', rubypath,
+               'the path to set to #! line'),
+  ProgramItem.new('rubyprog', 'name', rubypath,
+                  'the ruby program using for installation'),
+  ProgramItem.new('makeprog', 'name', makeprog,
+                  'the make program to compile ruby extentions'),
+  SelectItem.new('shebang', 'all/ruby/never', 'ruby',
+                 'shebang line (#!) editing mode'),
+  BoolItem.new('without-ext', 'yes/no', 'no',
+               'does not compile/install ruby extentions')
+]
+class ConfigTable_class   # open again
+  ALIASES = {
+    'std-ruby'         => 'stdruby',
+    'site-ruby-common' => 'siteruby',     # For backward compatibility
+    'site-ruby'        => 'siterubyver',  # For backward compatibility
+    'bin-dir'          => 'bindir',
+    'bin-dir'          => 'bindir',
+    'rb-dir'           => 'rbdir',
+    'so-dir'           => 'sodir',
+    'data-dir'         => 'datadir',
+    'ruby-path'        => 'rubypath',
+    'ruby-prog'        => 'rubyprog',
+    'ruby'             => 'rubyprog',
+    'make-prog'        => 'makeprog',
+    'make'             => 'makeprog'
+  }
+end
+multipackage_conf = [
+  PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
+                           'package names that you want to install'),
+  PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
+                           'package names that you do not want to install')
+]
+if multipackage_install?
+  ConfigTable = ConfigTable_class.new(common_conf + multipackage_conf)
+else
+  ConfigTable = ConfigTable_class.new(common_conf)
+end
+
+
+module MetaConfigAPI
+
+  def eval_file_ifexist(fname)
+    instance_eval File.read(fname), fname, 1 if File.file?(fname)
+  end
+
+  def config_names
+    ConfigTable.map {|i| i.name }
+  end
+
+  def config?(name)
+    ConfigTable.key?(name)
+  end
+
+  def bool_config?(name)
+    ConfigTable.lookup(name).config_type == 'bool'
+  end
+
+  def path_config?(name)
+    ConfigTable.lookup(name).config_type == 'path'
+  end
+
+  def value_config?(name)
+    case ConfigTable.lookup(name).config_type
+    when 'bool', 'path'
+      true
+    else
+      false
+    end
+  end
+
+  def add_config(item)
+    ConfigTable.add item
+  end
+
+  def add_bool_config(name, default, desc)
+    ConfigTable.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
+  end
+
+  def add_path_config(name, default, desc)
+    ConfigTable.add PathItem.new(name, 'path', default, desc)
+  end
+
+  def set_config_default(name, default)
+    ConfigTable.lookup(name).default = default
+  end
+
+  def remove_config(name)
+    ConfigTable.remove(name)
+  end
+
+end
+
+
+#
+# File Operations
+#
+
+module FileOperations
+
+  def mkdir_p(dirname, prefix = nil)
+    dirname = prefix + File.expand_path(dirname) if prefix
+    $stderr.puts "mkdir -p #{dirname}" if verbose?
+    return if no_harm?
+
+    # does not check '/'... it's too abnormal case
+    dirs = File.expand_path(dirname).split(%r<(?=/)>)
+    if /\A[a-z]:\z/i =~ dirs[0]
+      disk = dirs.shift
+      dirs[0] = disk + dirs[0]
+    end
+    dirs.each_index do |idx|
+      path = dirs[0..idx].join('')
+      Dir.mkdir path unless File.dir?(path)
+    end
+  end
+
+  def rm_f(fname)
+    $stderr.puts "rm -f #{fname}" if verbose?
+    return if no_harm?
+
+    if File.exist?(fname) or File.symlink?(fname)
+      File.chmod 0777, fname
+      File.unlink fname
+    end
+  end
+
+  def rm_rf(dn)
+    $stderr.puts "rm -rf #{dn}" if verbose?
+    return if no_harm?
+
+    Dir.chdir dn
+    Dir.foreach('.') do |fn|
+      next if fn == '.'
+      next if fn == '..'
+      if File.dir?(fn)
+        verbose_off {
+          rm_rf fn
+        }
+      else
+        verbose_off {
+          rm_f fn
+        }
+      end
+    end
+    Dir.chdir '..'
+    Dir.rmdir dn
+  end
+
+  def move_file(src, dest)
+    File.unlink dest if File.exist?(dest)
+    begin
+      File.rename src, dest
+    rescue
+      File.open(dest, 'wb') {|f| f.write File.binread(src) }
+      File.chmod File.stat(src).mode, dest
+      File.unlink src
+    end
+  end
+
+  def install(from, dest, mode, prefix = nil)
+    $stderr.puts "install #{from} #{dest}" if verbose?
+    return if no_harm?
+
+    realdest = prefix ? prefix + File.expand_path(dest) : dest
+    realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
+    str = File.binread(from)
+    if diff?(str, realdest)
+      verbose_off {
+        rm_f realdest if File.exist?(realdest)
+      }
+      File.open(realdest, 'wb') {|f|
+        f.write str
+      }
+      File.chmod mode, realdest
+
+      File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
+        if prefix
+          f.puts realdest.sub(prefix, '')
+        else
+          f.puts realdest
+        end
+      }
+    end
+  end
+
+  def diff?(new_content, path)
+    return true unless File.exist?(path)
+    new_content != File.binread(path)
+  end
+
+  def command(str)
+    $stderr.puts str if verbose?
+    system str or raise RuntimeError, "'system #{str}' failed"
+  end
+
+  def ruby(str)
+    command config('rubyprog') + ' ' + str
+  end
+  
+  def make(task = '')
+    command config('makeprog') + ' ' + task
+  end
+
+  def extdir?(dir)
+    File.exist?(dir + '/MANIFEST')
+  end
+
+  def all_files_in(dirname)
+    Dir.open(dirname) {|d|
+      return d.select {|ent| File.file?("#{dirname}/#{ent}") }
+    }
+  end
+
+  REJECT_DIRS = %w(
+    CVS SCCS RCS CVS.adm .svn
+  )
+
+  def all_dirs_in(dirname)
+    Dir.open(dirname) {|d|
+      return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS
+    }
+  end
+
+end
+
+
+#
+# Main Installer
+#
+
+module HookUtils
+
+  def run_hook(name)
+    try_run_hook "#{curr_srcdir()}/#{name}" or
+    try_run_hook "#{curr_srcdir()}/#{name}.rb"
+  end
+
+  def try_run_hook(fname)
+    return false unless File.file?(fname)
+    begin
+      instance_eval File.read(fname), fname, 1
+    rescue
+      setup_rb_error "hook #{fname} failed:\n" + $!.message
+    end
+    true
+  end
+
+end
+
+
+module HookScriptAPI
+
+  def get_config(key)
+    @config[key]
+  end
+
+  alias config get_config
+
+  def set_config(key, val)
+    @config[key] = val
+  end
+
+  #
+  # srcdir/objdir (works only in the package directory)
+  #
+
+  #abstract srcdir_root
+  #abstract objdir_root
+  #abstract relpath
+
+  def curr_srcdir
+    "#{srcdir_root()}/#{relpath()}"
+  end
+
+  def curr_objdir
+    "#{objdir_root()}/#{relpath()}"
+  end
+
+  def srcfile(path)
+    "#{curr_srcdir()}/#{path}"
+  end
+
+  def srcexist?(path)
+    File.exist?(srcfile(path))
+  end
+
+  def srcdirectory?(path)
+    File.dir?(srcfile(path))
+  end
+  
+  def srcfile?(path)
+    File.file? srcfile(path)
+  end
+
+  def srcentries(path = '.')
+    Dir.open("#{curr_srcdir()}/#{path}") {|d|
+      return d.to_a - %w(. ..)
+    }
+  end
+
+  def srcfiles(path = '.')
+    srcentries(path).select {|fname|
+      File.file?(File.join(curr_srcdir(), path, fname))
+    }
+  end
+
+  def srcdirectories(path = '.')
+    srcentries(path).select {|fname|
+      File.dir?(File.join(curr_srcdir(), path, fname))
+    }
+  end
+
+end
+
+
+class ToplevelInstaller
+
+  Version   = '3.3.1'
+  Copyright = 'Copyright (c) 2000-2004 Minero Aoki'
+
+  TASKS = [
+    [ 'all',      'do config, setup, then install' ],
+    [ 'config',   'saves your configurations' ],
+    [ 'show',     'shows current configuration' ],
+    [ 'setup',    'compiles ruby extentions and others' ],
+    [ 'install',  'installs files' ],
+    [ 'clean',    "does `make clean' for each extention" ],
+    [ 'distclean',"does `make distclean' for each extention" ]
+  ]
+
+  def ToplevelInstaller.invoke
+    instance().invoke
+  end
+
+  @singleton = nil
+
+  def ToplevelInstaller.instance
+    @singleton ||= new(File.dirname($0))
+    @singleton
+  end
+
+  include MetaConfigAPI
+
+  def initialize(ardir_root)
+    @config = nil
+    @options = { 'verbose' => true }
+    @ardir = File.expand_path(ardir_root)
+  end
+
+  def inspect
+    "#<#{self.class} #{__id__()}>"
+  end
+
+  def invoke
+    run_metaconfigs
+    case task = parsearg_global()
+    when nil, 'all'
+      @config = load_config('config')
+      parsearg_config
+      init_installers
+      exec_config
+      exec_setup
+      exec_install
+    else
+      @config = load_config(task)
+      __send__ "parsearg_#{task}"
+      init_installers
+      __send__ "exec_#{task}"
+    end
+  end
+  
+  def run_metaconfigs
+    eval_file_ifexist "#{@ardir}/metaconfig"
+  end
+
+  def load_config(task)
+    case task
+    when 'config'
+      ConfigTable.new
+    when 'clean', 'distclean'
+      if File.exist?(ConfigTable.savefile)
+      then ConfigTable.load
+      else ConfigTable.new
+      end
+    else
+      ConfigTable.load
+    end
+  end
+
+  def init_installers
+    @installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
+  end
+
+  #
+  # Hook Script API bases
+  #
+
+  def srcdir_root
+    @ardir
+  end
+
+  def objdir_root
+    '.'
+  end
+
+  def relpath
+    '.'
+  end
+
+  #
+  # Option Parsing
+  #
+
+  def parsearg_global
+    valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/
+
+    while arg = ARGV.shift
+      case arg
+      when /\A\w+\z/
+        setup_rb_error "invalid task: #{arg}" unless valid_task =~ arg
+        return arg
+
+      when '-q', '--quiet'
+        @options['verbose'] = false
+
+      when       '--verbose'
+        @options['verbose'] = true
+
+      when '-h', '--help'
+        print_usage $stdout
+        exit 0
+
+      when '-v', '--version'
+        puts "#{File.basename($0)} version #{Version}"
+        exit 0
+      
+      when '--copyright'
+        puts Copyright
+        exit 0
+
+      else
+        setup_rb_error "unknown global option '#{arg}'"
+      end
+    end
+
+    nil
+  end
+
+
+  def parsearg_no_options
+    unless ARGV.empty?
+      setup_rb_error "#{task}:  unknown options: #{ARGV.join ' '}"
+    end
+  end
+
+  alias parsearg_show       parsearg_no_options
+  alias parsearg_setup      parsearg_no_options
+  alias parsearg_clean      parsearg_no_options
+  alias parsearg_distclean  parsearg_no_options
+
+  def parsearg_config
+    re = /\A--(#{ConfigTable.map {|i| i.name }.join('|')})(?:=(.*))?\z/
+    @options['config-opt'] = []
+
+    while i = ARGV.shift
+      if /\A--?\z/ =~ i
+        @options['config-opt'] = ARGV.dup
+        break
+      end
+      m = re.match(i)  or setup_rb_error "config: unknown option #{i}"
+      name, value = *m.to_a[1,2]
+      @config[name] = value
+    end
+  end
+
+  def parsearg_install
+    @options['no-harm'] = false
+    @options['install-prefix'] = ''
+    while a = ARGV.shift
+      case a
+      when /\A--no-harm\z/
+        @options['no-harm'] = true
+      when /\A--prefix=(.*)\z/
+        path = $1
+        path = File.expand_path(path) unless path[0,1] == '/'
+        @options['install-prefix'] = path
+      else
+        setup_rb_error "install: unknown option #{a}"
+      end
+    end
+  end
+
+  def print_usage(out)
+    out.puts 'Typical Installation Procedure:'
+    out.puts "  $ ruby #{File.basename $0} config"
+    out.puts "  $ ruby #{File.basename $0} setup"
+    out.puts "  # ruby #{File.basename $0} install (may require root privilege)"
+    out.puts
+    out.puts 'Detailed Usage:'
+    out.puts "  ruby #{File.basename $0} <global option>"
+    out.puts "  ruby #{File.basename $0} [<global options>] <task> [<task options>]"
+
+    fmt = "  %-24s %s\n"
+    out.puts
+    out.puts 'Global options:'
+    out.printf fmt, '-q,--quiet',   'suppress message outputs'
+    out.printf fmt, '   --verbose', 'output messages verbosely'
+    out.printf fmt, '-h,--help',    'print this message'
+    out.printf fmt, '-v,--version', 'print version and quit'
+    out.printf fmt, '   --copyright',  'print copyright and quit'
+    out.puts
+    out.puts 'Tasks:'
+    TASKS.each do |name, desc|
+      out.printf fmt, name, desc
+    end
+
+    fmt = "  %-24s %s [%s]\n"
+    out.puts
+    out.puts 'Options for CONFIG or ALL:'
+    ConfigTable.each do |item|
+      out.printf fmt, item.help_opt, item.description, item.help_default
+    end
+    out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
+    out.puts
+    out.puts 'Options for INSTALL:'
+    out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
+    out.printf fmt, '--prefix=path',  'install path prefix', '$prefix'
+    out.puts
+  end
+
+  #
+  # Task Handlers
+  #
+
+  def exec_config
+    @installer.exec_config
+    @config.save   # must be final
+  end
+
+  def exec_setup
+    @installer.exec_setup
+  end
+
+  def exec_install
+    @installer.exec_install
+  end
+
+  def exec_show
+    ConfigTable.each do |i|
+      printf "%-20s %s\n", i.name, i.value
+    end
+  end
+
+  def exec_clean
+    @installer.exec_clean
+  end
+
+  def exec_distclean
+    @installer.exec_distclean
+  end
+
+end
+
+
+class ToplevelInstallerMulti < ToplevelInstaller
+
+  include HookUtils
+  include HookScriptAPI
+  include FileOperations
+
+  def initialize(ardir)
+    super
+    @packages = all_dirs_in("#{@ardir}/packages")
+    raise 'no package exists' if @packages.empty?
+  end
+
+  def run_metaconfigs
+    eval_file_ifexist "#{@ardir}/metaconfig"
+    @packages.each do |name|
+      eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
+    end
+  end
+
+  def init_installers
+    @installers = {}
+    @packages.each do |pack|
+      @installers[pack] = Installer.new(@config, @options,
+                                       "#{@ardir}/packages/#{pack}",
+                                       "packages/#{pack}")
+    end
+
+    with    = extract_selection(config('with'))
+    without = extract_selection(config('without'))
+    @selected = @installers.keys.select {|name|
+                  (with.empty? or with.include?(name)) \
+                      and not without.include?(name)
+                }
+  end
+
+  def extract_selection(list)
+    a = list.split(/,/)
+    a.each do |name|
+      setup_rb_error "no such package: #{name}"  unless @installers.key?(name)
+    end
+    a
+  end
+
+  def print_usage(f)
+    super
+    f.puts 'Inluded packages:'
+    f.puts '  ' + @packages.sort.join(' ')
+    f.puts
+  end
+
+  #
+  # multi-package metaconfig API
+  #
+
+  attr_reader :packages
+
+  def declare_packages(list)
+    raise 'package list is empty' if list.empty?
+    list.each do |name|
+      raise "directory packages/#{name} does not exist"\
+              unless File.dir?("#{@ardir}/packages/#{name}")
+    end
+    @packages = list
+  end
+
+  #
+  # Task Handlers
+  #
+
+  def exec_config
+    run_hook 'pre-config'
+    each_selected_installers {|inst| inst.exec_config }
+    run_hook 'post-config'
+    @config.save   # must be final
+  end
+
+  def exec_setup
+    run_hook 'pre-setup'
+    each_selected_installers {|inst| inst.exec_setup }
+    run_hook 'post-setup'
+  end
+
+  def exec_install
+    run_hook 'pre-install'
+    each_selected_installers {|inst| inst.exec_install }
+    run_hook 'post-install'
+  end
+
+  def exec_clean
+    rm_f ConfigTable.savefile
+    run_hook 'pre-clean'
+    each_selected_installers {|inst| inst.exec_clean }
+    run_hook 'post-clean'
+  end
+
+  def exec_distclean
+    rm_f ConfigTable.savefile
+    run_hook 'pre-distclean'
+    each_selected_installers {|inst| inst.exec_distclean }
+    run_hook 'post-distclean'
+  end
+
+  #
+  # lib
+  #
+
+  def each_selected_installers
+    Dir.mkdir 'packages' unless File.dir?('packages')
+    @selected.each do |pack|
+      $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose']
+      Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
+      Dir.chdir "packages/#{pack}"
+      yield @installers[pack]
+      Dir.chdir '../..'
+    end
+  end
+
+  def verbose?
+    @options['verbose']
+  end
+
+  def no_harm?
+    @options['no-harm']
+  end
+
+end
+
+
+class Installer
+
+  FILETYPES = %w( bin lib ext data )
+
+  include HookScriptAPI
+  include HookUtils
+  include FileOperations
+
+  def initialize(config, opt, srcroot, objroot)
+    @config = config
+    @options = opt
+    @srcdir = File.expand_path(srcroot)
+    @objdir = File.expand_path(objroot)
+    @currdir = '.'
+  end
+
+  def inspect
+    "#<#{self.class} #{File.basename(@srcdir)}>"
+  end
+
+  #
+  # Hook Script API base methods
+  #
+
+  def srcdir_root
+    @srcdir
+  end
+
+  def objdir_root
+    @objdir
+  end
+
+  def relpath
+    @currdir
+  end
+
+  #
+  # configs/options
+  #
+
+  def no_harm?
+    @options['no-harm']
+  end
+
+  def verbose?
+    @options['verbose']
+  end
+
+  def verbose_off
+    begin
+      save, @options['verbose'] = @options['verbose'], false
+      yield
+    ensure
+      @options['verbose'] = save
+    end
+  end
+
+  #
+  # TASK config
+  #
+
+  def exec_config
+    exec_task_traverse 'config'
+  end
+
+  def config_dir_bin(rel)
+  end
+
+  def config_dir_lib(rel)
+  end
+
+  def config_dir_ext(rel)
+    extconf if extdir?(curr_srcdir())
+  end
+
+  def extconf
+    opt = @options['config-opt'].join(' ')
+    command "#{config('rubyprog')} #{curr_srcdir()}/extconf.rb #{opt}"
+  end
+
+  def config_dir_data(rel)
+  end
+
+  #
+  # TASK setup
+  #
+
+  def exec_setup
+    exec_task_traverse 'setup'
+  end
+
+  def setup_dir_bin(rel)
+    all_files_in(curr_srcdir()).each do |fname|
+      adjust_shebang "#{curr_srcdir()}/#{fname}"
+    end
+  end
+
+  def adjust_shebang(path)
+    return if no_harm?
+    tmpfile = File.basename(path) + '.tmp'
+    begin
+      File.open(path, 'rb') {|r|
+        first = r.gets
+        return unless File.basename(config('rubypath')) == 'ruby'
+        return unless File.basename(first.sub(/\A\#!/, '').split[0]) == 'ruby'
+        $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose?
+        File.open(tmpfile, 'wb') {|w|
+          w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath'))
+          w.write r.read
+        }
+        move_file tmpfile, File.basename(path)
+      }
+    ensure
+      File.unlink tmpfile if File.exist?(tmpfile)
+    end
+  end
+
+  def setup_dir_lib(rel)
+  end
+
+  def setup_dir_ext(rel)
+    make if extdir?(curr_srcdir())
+  end
+
+  def setup_dir_data(rel)
+  end
+
+  #
+  # TASK install
+  #
+
+  def exec_install
+    rm_f 'InstalledFiles'
+    exec_task_traverse 'install'
+  end
+
+  def install_dir_bin(rel)
+    install_files collect_filenames_auto(), "#{config('bindir')}/#{rel}", 0755
+  end
+
+  def install_dir_lib(rel)
+    install_files ruby_scripts(), "#{config('rbdir')}/#{rel}", 0644
+  end
+
+  def install_dir_ext(rel)
+    return unless extdir?(curr_srcdir())
+    install_files ruby_extentions('.'),
+                  "#{config('sodir')}/#{File.dirname(rel)}",
+                  0555
+  end
+
+  def install_dir_data(rel)
+    install_files collect_filenames_auto(), "#{config('datadir')}/#{rel}", 0644
+  end
+
+  def install_files(list, dest, mode)
+    mkdir_p dest, @options['install-prefix']
+    list.each do |fname|
+      install fname, dest, mode, @options['install-prefix']
+    end
+  end
+
+  def ruby_scripts
+    collect_filenames_auto().select {|n| /\.rb\z/ =~ n }
+  end
+  
+  # picked up many entries from cvs-1.11.1/src/ignore.c
+  reject_patterns = %w( 
+    core RCSLOG tags TAGS .make.state
+    .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
+    *~ *.old *.bak *.BAK *.orig *.rej _$* *$
+
+    *.org *.in .*
+  )
+  mapping = {
+    '.' => '\.',
+    '$' => '\$',
+    '#' => '\#',
+    '*' => '.*'
+  }
+  REJECT_PATTERNS = Regexp.new('\A(?:' +
+                               reject_patterns.map {|pat|
+                                 pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] }
+                               }.join('|') +
+                               ')\z')
+
+  def collect_filenames_auto
+    mapdir((existfiles() - hookfiles()).reject {|fname|
+             REJECT_PATTERNS =~ fname
+           })
+  end
+
+  def existfiles
+    all_files_in(curr_srcdir()) | all_files_in('.')
+  end
+
+  def hookfiles
+    %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
+      %w( config setup install clean ).map {|t| sprintf(fmt, t) }
+    }.flatten
+  end
+
+  def mapdir(filelist)
+    filelist.map {|fname|
+      if File.exist?(fname)   # objdir
+        fname
+      else                    # srcdir
+        File.join(curr_srcdir(), fname)
+      end
+    }
+  end
+
+  def ruby_extentions(dir)
+    Dir.open(dir) {|d|
+      ents = d.select {|fname| /\.#{::Config::CONFIG['DLEXT']}\z/ =~ fname }
+      if ents.empty?
+        setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
+      end
+      return ents
+    }
+  end
+
+  #
+  # TASK clean
+  #
+
+  def exec_clean
+    exec_task_traverse 'clean'
+    rm_f ConfigTable.savefile
+    rm_f 'InstalledFiles'
+  end
+
+  def clean_dir_bin(rel)
+  end
+
+  def clean_dir_lib(rel)
+  end
+
+  def clean_dir_ext(rel)
+    return unless extdir?(curr_srcdir())
+    make 'clean' if File.file?('Makefile')
+  end
+
+  def clean_dir_data(rel)
+  end
+
+  #
+  # TASK distclean
+  #
+
+  def exec_distclean
+    exec_task_traverse 'distclean'
+    rm_f ConfigTable.savefile
+    rm_f 'InstalledFiles'
+  end
+
+  def distclean_dir_bin(rel)
+  end
+
+  def distclean_dir_lib(rel)
+  end
+
+  def distclean_dir_ext(rel)
+    return unless extdir?(curr_srcdir())
+    make 'distclean' if File.file?('Makefile')
+  end
+
+  #
+  # lib
+  #
+
+  def exec_task_traverse(task)
+    run_hook "pre-#{task}"
+    FILETYPES.each do |type|
+      if config('without-ext') == 'yes' and type == 'ext'
+        $stderr.puts 'skipping ext/* by user option' if verbose?
+        next
+      end
+      traverse task, type, "#{task}_dir_#{type}"
+    end
+    run_hook "post-#{task}"
+  end
+
+  def traverse(task, rel, mid)
+    dive_into(rel) {
+      run_hook "pre-#{task}"
+      __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
+      all_dirs_in(curr_srcdir()).each do |d|
+        traverse task, "#{rel}/#{d}", mid
+      end
+      run_hook "post-#{task}"
+    }
+  end
+
+  def dive_into(rel)
+    return unless File.dir?("#{@srcdir}/#{rel}")
+
+    dir = File.basename(rel)
+    Dir.mkdir dir unless File.dir?(dir)
+    prevdir = Dir.pwd
+    Dir.chdir dir
+    $stderr.puts '---> ' + rel if verbose?
+    @currdir = rel
+    yield
+    Dir.chdir prevdir
+    $stderr.puts '<--- ' + rel if verbose?
+    @currdir = File.dirname(rel)
+  end
+
+end
+
+
+if $0 == __FILE__
+  begin
+    if multipackage_install?
+      ToplevelInstallerMulti.invoke
+    else
+      ToplevelInstaller.invoke
+    end
+  rescue SetupError
+    raise if $DEBUG
+    $stderr.puts $!.message
+    $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
+    exit 1
+  end
+end
diff --git a/themes/simple/parameters.rb b/themes/simple/parameters.rb
deleted file mode 100644 (file)
index fdac890..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-#                         *  BOOH  *
-#
-# A.k.a `Best web-album Of the world, Or your money back, Humerus'.
-#
-# The acronyn sucks, however this is a tribute to Dragon Ball by
-# Akira Toriyama, where the last enemy beaten by heroes of Dragon
-# Ball is named "Boo". But there was already a free software project
-# called Boo, so this one will be it "Booh". Or whatever.
-#
-#
-# Copyright (c) 2004 Guillaume Cottenceau <gc3 at bluewin.ch>
-#
-# This software may be freely redistributed under the terms of the GNU
-# public license version 2.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-
-#- we often will want to have one size to nicely fit 800x600 screens,
-#- one for 1024x768 and one for 1280x1024
-#- it's necessary to fit according to the typical space taken by
-#- widgets defined in the skeleton of the theme
-$images_size = [
-    {
-        'name' => 'small',
-        'fullscreen' => '552x414',
-        'thumbnails' => '192x144',
-    },
-    {
-        'name' => 'medium',
-        'fullscreen' => '704x528',
-        'thumbnails' => '240x180',
-        'default' => true,
-    },
-    {
-        'name' => 'large',
-        'fullscreen' => '880x660',
-        'thumbnails' => '300x225',
-    }
-]
-
-$albums_thumbnail_size = '300x225'
diff --git a/themes/simple/skeleton_image.html b/themes/simple/skeleton_image.html
deleted file mode 100644 (file)
index 169824f..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-
-<head>
-    <title>~~title~~</title>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <meta name="author" content="`Simple' theme - Guillaume Cottenceau">
-    ~~~head_code~~~
-    <style type="text/css" media="screen">
-    <!--
-body {
-        background-color: #ffffff;
-        color: #000000;
-        font-family: Verdana, Arial, Helvetica, sans-serif, monospace;
-}
-
-a:link {
-        color: #7B7BA8;
-        text-decoration: none;
-}
-
-a:visited {
-        color: #7B7BA8;
-        text-decoration: none;
-}
-
-a:hover {
-        color: #7B7BA8;
-        text-decoration: underline;
-}
-
-input {
-        border: solid 1px;
-}
-    -->
-    </style>
-</head>
-
-<body bgcolor="#FFFFFF" ~~~body_additions~~~>
-
-<table width="100%">
-<tr>
-    <td width="10%">&nbsp;</td>
-    <td width="1%">~~~button_first~~~</td>
-    <td width="3%">&nbsp;</td>
-    <td width="1%">~~~button_previous~~~</td>
-    <td align="center"><font size="-2">~~size_small~~ | ~~size_medium~~ | ~~size_large~~</font></td>
-    <td width="1%">~~~button_next~~~</td>
-    <td width="3%">&nbsp;</td>
-    <td width="1%">~~~button_last~~~</td>
-    <td width="10%">&nbsp;</td>
-</tr>
-<!-- <p id="dbg_text">&nbsp;</p> -->
-<tr>
-    <td align="center" colspan="9">
-        ~~~image~~~
-    </td>
-</tr>
-<tr>
-    <td align="center" colspan="9">
-        <font size="+1" ~~~caption_additions~~~>&nbsp;</font>
-        <font size="-2" ~~~image_counter_additions~~~>&nbsp;</font>
-    </td>
-</tr>
-
-~~~body_code~~~
-
-<tr>
-    <td colspan="9" align="center">
-        ~~~button_slideshow~~~
-        <font size="-1">~~thumbnails~~</font>
-    </td>
-</tr>
-</table>
-</html>
diff --git a/themes/simple/skeleton_index.html b/themes/simple/skeleton_index.html
deleted file mode 100644 (file)
index 434af35..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-
-<head>
-    <title>~~title~~</title>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <style type="text/css" media="screen">
-    <!--
-body {
-        background-color: #ffffff;
-        color: #000000;
-        font-family: Verdana, Arial, Helvetica, sans-serif, monospace;
-}
-
-a:link {
-        color: #7B7BA8;
-        text-decoration: none;
-}
-
-a:visited {
-        color: #7B7BA8;
-        text-decoration: none;
-}
-
-a:hover {
-        color: #7B7BA8;
-        text-decoration: underline;
-}
-
-input {
-        border: solid 1px;
-}
-    -->
-    </style>
-</head>
-
-<body bgcolor="#FFFFFF">
-
-~~ifnavigation?~~<p>~~navigation~~</p>~~fi~~
-
-<p align="center"><font size="+3">~~title~~</font></p>
-
-<table width="100%" cellpadding="10" border="0">
-~~iterate1_open~~
-<tr>
-    <td width="50%" align="right" valign="top">
-       ~~image_iteration~~
-    </td>
-    <td width="50%" align="left">
-       <p>~~caption_iteration~~</p>
-    </td>
-</tr>
-~~iterate1_close~~
-</table>
-
-</body>
-</html>
diff --git a/themes/simple/skeleton_thumbnails.html b/themes/simple/skeleton_thumbnails.html
deleted file mode 100644 (file)
index a0e0f7c..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-
-<head>
-    <title>~~title~~</title>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <style type="text/css" media="screen">
-    <!--
-body {
-        background-color: #ffffff;
-        color: #000000;
-        font-family: Verdana, Arial, Helvetica, sans-serif, monospace;
-}
-
-a:link {
-        color: #7B7BA8;
-        text-decoration: none;
-}
-
-a:visited {
-        color: #7B7BA8;
-        text-decoration: none;
-}
-
-a:hover {
-        color: #7B7BA8;
-        text-decoration: underline;
-}
-
-input {
-        border: solid 1px;
-}
-    -->
-    </style>
-</head>
-
-<body bgcolor="#FFFFFF">
-
-<p align="center">
-    <font size="+3">~~title~~</font>
-    <br/>
-    <font size="-2">~~size_small~~ | ~~size_medium~~ | ~~size_large~~</font>
-    <br/>
-    <br/>
-    ~~run_slideshow~~
-</p>
-
-<table width="100%">
-~~iterate1_open~~
-<tr>
-    ~~iterate2_open_max4~~
-    <td width="25%" align="center" valign="top">
-       ~~image_iteration~~
-       <p>~~ifvideo?~~<img src="video.png"/>~~fi~~ ~~caption_iteration~~</p>
-    </td>
-    ~~iterate2_close~~
-</tr>
-~~iterate1_close~~
-</table>
-
-<p align="center">~~return_to_albums~~</p>
-
-</body>
-</html>
diff --git a/themes/simple/video.png b/themes/simple/video.png
deleted file mode 100644 (file)
index 074cc97..0000000
Binary files a/themes/simple/video.png and /dev/null differ