much more
authorgc <gc>
Tue, 10 Jul 2007 21:00:17 +0000 (21:00 +0000)
committergc <gc>
Tue, 10 Jul 2007 21:00:17 +0000 (21:00 +0000)
bin/booh-classifier

index 963564cdeb3185e6d0e6ae2082da548dbe686d43..f221ffbec2a2c06ba37bb1647377206897addb4c 100644 (file)
@@ -181,6 +181,44 @@ def show_mem(*txt)
     puts get_mem
 end
 
+def min(v1, v2)
+    return v1 < v2 ? v1 : v2
+end
+def max(v1, v2)
+    return v1 > v2 ? v1 : v2
+end
+
+class Gdk::Color
+    def darker
+        color = dup
+        color.red = max(color.red - 10000, 0)
+        color.green = max(color.green - 10000, 0)
+        color.blue = max(color.blue - 10000, 0)
+        return color
+    end
+    def lighter
+        color = dup
+        color.red = min(color.red + 10000, 65535)
+        color.green = min(color.green + 10000, 65535)
+        color.blue = min(color.blue + 10000, 65535)
+        return color
+    end
+end
+
+$colors = [ Gdk::Color.new(0, 65535, 0),
+            Gdk::Color.new(0, 0, 65535),
+            Gdk::Color.new(65535, 65535, 0),
+            Gdk::Color.new(0, 65535, 65535),
+            Gdk::Color.new(65535, 0, 65535) ]
+
+class Tag
+    attr_accessor :color, :name
+    def initialize(name)
+        @name = name
+    end
+end
+$tags = {}
+
 class Entry
     @@thumbnails_height = 64
     @@max_height = nil
@@ -188,7 +226,7 @@ class Entry
         return @@thumbnails_height
     end
 
-    attr_accessor :path, :type, :button
+    attr_accessor :path, :type, :angle, :button, :removed, :tagged
 
     def initialize(path, type)
         @path = path
@@ -266,6 +304,36 @@ class Entry
             return @pixbuf_thumbnail
         }
     end
+    def free_pixbuf_thumbnail
+        @protect_cleanup.synchronize {
+            if @pixbuf_thumbnail.nil?
+                return false
+            else
+                puts ">>> free_pixbuf_thumbnail #{path}"
+                @pixbuf_thumbnail = nil
+                return true
+            end
+        }
+    end
+
+    def show_bg
+        if removed
+            red = Gdk::Color.new(65535, 0, 0)
+            button.modify_bg(Gtk::StateType::NORMAL, red)
+            button.modify_bg(Gtk::StateType::PRELIGHT, red.lighter)
+            button.modify_bg(Gtk::StateType::ACTIVE, red)
+        elsif tagged
+            button.modify_bg(Gtk::StateType::NORMAL, tagged.color)
+            button.modify_bg(Gtk::StateType::PRELIGHT, tagged.color.lighter)
+            button.modify_bg(Gtk::StateType::ACTIVE, tagged.color)
+        else
+            # TODO need to add proper undo support :/
+            white = Gdk::Color.new(55535, 55535, 55535)
+            button.modify_bg(Gtk::StateType::NORMAL, white)
+            button.modify_bg(Gtk::StateType::PRELIGHT, white.lighter)
+            button.modify_bg(Gtk::StateType::ACTIVE, white)
+        end
+    end
 
     private
     def load_into_pixbuf_full
@@ -278,10 +346,12 @@ class Entry
                 return
             end
             if @pixbuf_full
-                angle = guess_rotate(path)
-                if angle != 0
-                    puts ">>> load_into_pixbuf_full #{path} => rotate #{angle}"
-                    @pixbuf_full = rotate_pixbuf(@pixbuf_full, angle)
+                if @angle.nil?
+                    @angle = guess_rotate(path)
+                end
+                if @angle != 0
+                    puts ">>> load_into_pixbuf_full #{path} => rotate #{@angle}"
+                    @pixbuf_full = rotate_pixbuf(@pixbuf_full, @angle)
                 end
                 if @pixbuf_full.height > @@max_height
                     #- save a lot of memory, don't store in actual full size
@@ -344,10 +414,7 @@ class MainView < Gtk::DrawingArea
         else
             @entry = $allentries[index]
         end
-        update_shown
-        #- should "freeze" or something to prevent blinking
-        window.clear
-        draw
+        redraw        
         @preloader.run
     end
 
@@ -355,6 +422,15 @@ class MainView < Gtk::DrawingArea
         return @index
     end
 
+    def redraw
+        update_shown
+        w, h = window.size
+        window.begin_paint(Gdk::Rectangle.new(0, 0, w, h))
+        window.clear
+        draw
+        window.end_paint
+    end
+
     def update_shown
         if @entry
             width, height = window.size 
@@ -378,6 +454,13 @@ class MainView < Gtk::DrawingArea
         end
     end
 
+    def show_next
+        if @index < $allentries.size - 1
+            next_entry = $allentries[get_shown_entry + 1]
+            next_entry.button.grab_focus
+        end
+    end
+
 end
 
 def check_memory_free_cache_if_needed
@@ -429,6 +512,169 @@ def autoscroll_if_needed(button)
     end
 end
 
+def show_popup(parent, msg, *options)
+    dialog = Gtk::Dialog.new
+    if options[0]
+        options = options[0]
+    else
+        options = {}
+    end
+    if options[:title]
+        dialog.title = options[:title]
+    else
+        dialog.title = utf8(_("Booh message"))
+    end
+    lbl = Gtk::Label.new
+    if options[:nomarkup]
+        lbl.text = msg
+    else
+        lbl.markup = msg
+    end
+    if options[:centered]
+        lbl.set_justify(Gtk::Justification::CENTER)
+    end
+    if options[:selectable]
+        lbl.selectable = true
+    end
+    if options[:topwidget]
+        dialog.vbox.add(options[0][:topwidget])
+    end
+    if options[:scrolled]
+        sw = Gtk::ScrolledWindow.new(nil, nil)
+        sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
+        sw.add_with_viewport(lbl)
+        dialog.vbox.add(sw)
+        dialog.set_default_size(500, 600)
+    else
+        dialog.vbox.add(lbl)
+        dialog.set_default_size(200, 120)
+    end
+    if options[:bottomwidget]
+        dialog.vbox.add(options[:bottomwidget])
+    end
+    if options[:okcancel]
+        dialog.add_button(Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL)
+    end
+    dialog.add_button(Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK)
+
+    if options[:pos_centered]
+        dialog.window_position = Gtk::Window::POS_CENTER
+    else
+        dialog.window_position = Gtk::Window::POS_MOUSE
+    end
+
+    if options[:linkurl]
+        linkbut = Gtk::Button.new('')
+        linkbut.child.markup = "<span foreground=\"#00000000FFFF\" underline=\"single\">#{options[0][:linkurl]}</span>"
+        linkbut.signal_connect('clicked') {
+            open_url(options[0][:linkurl] + '/index.html')
+            dialog.response(Gtk::Dialog::RESPONSE_OK)
+            set_mousecursor_normal
+        }
+        linkbut.relief = Gtk::RELIEF_NONE
+        linkbut.signal_connect('enter-notify-event') { set_mousecursor(Gdk::Cursor::HAND2, linkbut); false }
+        linkbut.signal_connect('leave-notify-event') { set_mousecursor(nil, linkbut); false }
+        dialog.vbox.add(Gtk::Alignment.new(0.5, 0.5, 0, 0).add(linkbut))
+    end
+
+    dialog.show_all
+
+    if options[:stuff_connector]
+        options[:stuff_connector].call({ :dialog => dialog })
+    end
+                                        
+    if !options[:not_transient]
+        dialog.transient_for = parent
+        dialog.run { |response|
+            if options[:data_getter]
+                options[:data_getter].call
+            end
+            dialog.destroy
+            if options[:okcancel]
+                return response == Gtk::Dialog::RESPONSE_OK
+            end
+        }
+    else
+        dialog.signal_connect('response') { dialog.destroy }
+    end
+end
+
+def thumbnail_keypressed(entry, event)
+    if event.state & Gdk::Window::MOD1_MASK != 0
+        #- ALT pressed: Alt-Left and Alft-Right rotate
+        if event.keyval == Gdk::Keyval::GDK_Left || event.keyval == Gdk::Keyval::GDK_Right
+            if event.keyval == Gdk::Keyval::GDK_Left
+                entry.angle = (entry.angle - 90) % 360
+            else
+                entry.angle = (entry.angle + 90) % 360
+            end
+            entry.free_pixbuf_full
+            entry.free_pixbuf_main
+            entry.free_pixbuf_thumbnail
+            $mainview.redraw
+            entry.button.set_image(img = Gtk::Image.new(entry.pixbuf_thumbnail))
+        end
+
+    else
+        if event.keyval == Gdk::Keyval::GDK_Delete
+            entry.removed = true
+            entry.tagged = nil
+            entry.show_bg
+            $mainview.show_next
+
+        elsif event.keyval == Gdk::Keyval::GDK_space
+            entry.removed = false
+            entry.tagged = nil
+            entry.show_bg
+            $mainview.show_next
+
+        else
+            char = [ Gdk::Keyval.to_unicode(event.keyval) ].pack("C*")
+            if char =~ /^[a-zA-z0-9]$/
+                tag = $tags[char]
+                
+                if tag.nil?
+                    vb = Gtk::VBox.new(false, 0)
+                    vb.pack_start(entry = Gtk::Entry.new.set_text(char), false, false)
+                    vb.pack_start(Gtk::Alignment.new(0.5, 0.5, 0, 0).add(bt = Gtk::Button.new(utf8(_("  Change color  ")))))
+                    text = nil
+                    color = nil
+                    bt.signal_connect('clicked') {
+                        color = $colors.shift
+                        if color.nil?
+                            color = Gdk::Color.new(16384 + rand(49151), 16384 + rand(49151), 16384 + rand(49151))
+                        end
+                        bt.modify_bg(Gtk::StateType::NORMAL, color)
+                        bt.modify_bg(Gtk::StateType::PRELIGHT, color)
+                        bt.modify_bg(Gtk::StateType::ACTIVE, color.darker)
+                    }
+                    bt.clicked
+                    if show_popup($main_window,
+                                  utf8(_("You typed the text character '%s', which is not associated with a tag.\nType in the full name of the tag below to create a new one.")) % char,
+                                  { :okcancel => true, :bottomwidget => vb, :data_getter => proc { text = entry.text },
+                                    :stuff_connector => proc { |stuff| entry.select_region(0, 0); entry.position = -1;
+                                          entry.signal_connect('activate') { stuff[:dialog].response(Gtk::Dialog::RESPONSE_OK) } } } )
+                        if text.length > 0
+                            char = text[0,1]  #- in case it changed
+                            tag = Tag.new(text)
+                            tag.color = color
+                            $tags[char] = tag
+                            label = Gtk::Label.new.set_markup('<b>(' + char + ')</b>' + text[1..-1]).set_justify(Gtk::Justification::CENTER)
+                            $tags_vbox.pack_start(evt = Gtk::EventBox.new.add(label).modify_bg(Gtk::StateType::NORMAL, tag.color).show_all)
+                        end
+                    end
+
+                else
+                    entry.removed = false
+                    entry.tagged = tag
+                    entry.show_bg
+                    $mainview.show_next
+                end
+            end
+        end
+    end
+end
+
 def show_entries
     e = Thread.new {
         t1 = Time.now
@@ -447,6 +693,7 @@ def show_entries
                                        $imagesline.pack_start(entry.button.show_all, false, false)
                                        entry.button.signal_connect('clicked') { $mainview.set_shown_entry(i) }
                                        entry.button.signal_connect('focus-in-event') { entry.button.clicked; autoscroll_if_needed(entry.button) }
+                                       entry.button.signal_connect('key-press-event') { |w, e| thumbnail_keypressed(entry, e) }
                                        if i == 0
                                            entry.button.grab_focus
                                        end
@@ -488,8 +735,7 @@ def open_dir(path)
         end
         Dir.entries(dir).each { |file|
             type = entry2type(file)
-            if type
-# && $allentries.size < 5
+           if type && $allentries.size < 2
                 $allentries << Entry.new(File.join(dir, file), type)
             end
         }
@@ -594,13 +840,25 @@ def create_menubar
     return mb
 end
 
+def reset_tags
+    for child in $tags_vbox.children
+        $tags_vbox.remove(child)
+    end
+    $tags_vbox.pack_start(Gtk::Label.new(utf8(_("Tags list:"))).set_justify(Gtk::Justification::CENTER), false, false)
+end
+
 def create_main_window
 
     mb = create_menubar
 
     main_vbox = Gtk::VBox.new(false, 0)
     main_vbox.pack_start(mb, false, false)
-    main_vbox.pack_start($mainview = MainView.new, true, true)
+    mainview_hbox = Gtk::HBox.new
+    mainview_hbox.pack_start(tags_vbox_vbox = Gtk::VBox.new(false, 0), false, true)
+    tags_vbox_vbox.pack_start($tags_vbox = Gtk::VBox.new(false, 5), false, false)
+    reset_tags
+    mainview_hbox.pack_start($mainview = MainView.new, true, true)
+    main_vbox.pack_start(mainview_hbox, true, true)
     $imagesline_sw = Gtk::ScrolledWindow.new(nil, nil)
     $imagesline_sw.set_policy(Gtk::POLICY_ALWAYS, Gtk::POLICY_NEVER)
     $imagesline_sw.add_with_viewport($imagesline = Gtk::HBox.new(false, 0))
@@ -656,17 +914,17 @@ Gtk.init
 
 
 #- Gdk::Pixbuf#rotate memory leak check (in ruby-gnome2 <= 0.16.0)
-pb = Gdk::Pixbuf.new("#{$FPATH}/images/logo.png")
-1.upto(5) { pb = pb.rotate(Gdk::Pixbuf::ROTATE_CLOCKWISE) }
-GC.start
-mem = get_mem
-1.upto(5) { pb = pb.rotate(Gdk::Pixbuf::ROTATE_CLOCKWISE) }
-GC.start
-mem2 = get_mem
-if mem2 != mem
-    puts _("Gdk::Pixbuf#scale memory leak detected (this is normal with unpatched ruby-gnome2 <= 0.16.0). Application would slow down to a crawl, won't proceed.")
-    exit 1
-end
+#pb = Gdk::Pixbuf.new("#{$FPATH}/images/logo.png")
+#1.upto(5) { pb = pb.rotate(Gdk::Pixbuf::ROTATE_CLOCKWISE) }
+#GC.start
+#mem = get_mem
+#1.upto(5) { pb = pb.rotate(Gdk::Pixbuf::ROTATE_CLOCKWISE) }
+#GC.start
+#mem2 = get_mem
+#if mem2 != mem
+#    puts _("Gdk::Pixbuf#scale memory leak detected (this is normal with unpatched ruby-gnome2 <= 0.16.0). Application would slow down to a crawl, won't proceed.")
+#    exit 1
+#end
 
 
 create_main_window