multiple elements selection and autoscroll in there and in drag-n-drop
authorgc <gc>
Thu, 14 Jul 2005 20:43:34 +0000 (20:43 +0000)
committergc <gc>
Thu, 14 Jul 2005 20:43:34 +0000 (20:43 +0000)
bin/booh
bin/booh-backend
lib/booh/GtkAutoTable.rb
lib/booh/UndoHandler.rb
lib/booh/booh-lib.rb

index 0553716a9c12373a438c8e67652c72660bd4b7bf..c4810eb13f2c483b4764b7aff739e98aaa39e540 100755 (executable)
--- a/bin/booh
+++ b/bin/booh
@@ -184,6 +184,7 @@ end
 def save_undo(name, closure, *params)
     UndoHandler.save_undo(name, closure, [ *params ])
     $undo_tb.sensitive = $undo_mb.sensitive = true
+    $redo_tb.sensitive = $redo_mb.sensitive = false
 end
 
 def view_element(filename, closures)
@@ -264,26 +265,36 @@ def view_element(filename, closures)
     w.show_all
 end
 
+def scroll_upper(scrolledwindow, ypos_top)
+    newval = scrolledwindow.vadjustment.value -
+        ((scrolledwindow.vadjustment.value - ypos_top - 1) / scrolledwindow.vadjustment.step_increment + 1) * scrolledwindow.vadjustment.step_increment
+    if newval < scrolledwindow.vadjustment.lower
+        newval = scrolledwindow.vadjustment.lower
+    end
+    scrolledwindow.vadjustment.value = newval
+end
+
+def scroll_lower(scrolledwindow, ypos_bottom)
+    newval = scrolledwindow.vadjustment.value +
+        ((ypos_bottom - (scrolledwindow.vadjustment.value + scrolledwindow.vadjustment.page_size) - 1) / scrolledwindow.vadjustment.step_increment + 1) * scrolledwindow.vadjustment.step_increment
+    if newval > scrolledwindow.vadjustment.upper - scrolledwindow.vadjustment.page_size
+        newval = scrolledwindow.vadjustment.upper - scrolledwindow.vadjustment.page_size
+    end
+    scrolledwindow.vadjustment.value = newval
+end
+
 def autoscroll_if_needed(scrolledwindow, image, textview)
-    #- autoscroll if cursor or image is not visible
-    ypos_top = (image && image.window) ? image.window.position[1] : textview.window.position[1]
-    ypos_bottom = max(textview.window.position[1] + textview.window.size[1], image && image.window ? image.window.position[1] + image.window.size[1] : -1)
-    current_miny_visible = scrolledwindow.vadjustment.value
-    current_maxy_visible = scrolledwindow.vadjustment.value + scrolledwindow.vadjustment.page_size
-    if ypos_top < current_miny_visible
-        newval = scrolledwindow.vadjustment.value -
-            ((current_miny_visible - ypos_top - 1) / scrolledwindow.vadjustment.step_increment + 1) * scrolledwindow.vadjustment.step_increment
-        if newval < scrolledwindow.vadjustment.lower
-            newval = scrolledwindow.vadjustment.lower
+    #- autoscroll if cursor or image is not visible, if possible
+    if image && image.window || textview.window
+        ypos_top = (image && image.window) ? image.window.position[1] : textview.window.position[1]
+        ypos_bottom = max(textview.window.position[1] + textview.window.size[1], image && image.window ? image.window.position[1] + image.window.size[1] : -1)
+        current_miny_visible = scrolledwindow.vadjustment.value
+        current_maxy_visible = scrolledwindow.vadjustment.value + scrolledwindow.vadjustment.page_size
+        if ypos_top < current_miny_visible
+            scroll_upper(scrolledwindow, ypos_top)
+        elsif ypos_bottom > current_maxy_visible
+            scroll_lower(scrolledwindow, ypos_bottom)
         end
-        scrolledwindow.vadjustment.value = newval
-    elsif ypos_bottom > current_maxy_visible
-        newval = scrolledwindow.vadjustment.value +
-            ((ypos_bottom - current_maxy_visible - 1) / scrolledwindow.vadjustment.step_increment + 1) * scrolledwindow.vadjustment.step_increment
-        if newval > scrolledwindow.vadjustment.upper - scrolledwindow.vadjustment.page_size
-            newval = scrolledwindow.vadjustment.upper - scrolledwindow.vadjustment.page_size
-        end
-        scrolledwindow.vadjustment.value = newval
     end
 end
 
@@ -434,7 +445,11 @@ def change_frame_offset(xmldir, attributes_prefix, value)
 end
 
 def ask_new_frame_offset(xmldir, attributes_prefix)
-    value = xmldir.attributes["#{attributes_prefix}frame-offset"]
+    if xmldir
+        value = xmldir.attributes["#{attributes_prefix}frame-offset"]
+    else
+        value = ''
+    end
 
     dialog = Gtk::Dialog.new(utf8(_("Change frame offset")),
                              $main_window,
@@ -469,7 +484,7 @@ from. There are approximately 25 frames per second in a video.
         dialog.destroy
         if response == Gtk::Dialog::RESPONSE_OK
             $modified = true
-            msg 3, "changing frame offset top #{newval}"
+            msg 3, "changing frame offset to #{newval}"
             return { :old => value, :new => newval }
         else
             return nil
@@ -577,7 +592,20 @@ def gen_real_thumbnail(type, origfile, destfile, xmldir, size, img, infotype)
     }
 end
 
-def popup_thumbnail_menu(event, optionals, type, xmldir, attributes_prefix, possible_moves, closures)
+def popup_thumbnail_menu(event, optionals, type, xmldir, attributes_prefix, possible_actions, closures)
+    distribute_multiple_call = Proc.new { |action, arg|
+        $selected_elements.each_key { |path|
+            $name2widgets[path][:img].pixbuf = $selected_elements[path][:pixbuf]
+        }
+        if possible_actions[:can_multiple] && $selected_elements.length > 0
+            UndoHandler.begin_batch
+            $selected_elements.each_key { |k| $name2closures[k][action].call(arg) }
+            UndoHandler.end_batch
+        else
+            closures[action].call(arg)
+        end
+        $selected_elements = {}
+    }
     menu = Gtk::Menu.new
     if optionals.include?('change_image')
         menu.append(changeimg = Gtk::ImageMenuItem.new(utf8(_("Change image"))))
@@ -587,63 +615,93 @@ def popup_thumbnail_menu(event, optionals, type, xmldir, attributes_prefix, poss
     end
     menu.append(r90 = Gtk::ImageMenuItem.new(utf8(_("Rotate clockwise"))))
     r90.image = Gtk::Image.new("#{$FPATH}/images/stock-rotate-90-16.png")
-    r90.signal_connect('activate') { closures[:rotate].call(90) }
+    r90.signal_connect('activate') { distribute_multiple_call.call(:rotate, 90) }
     menu.append(r270 = Gtk::ImageMenuItem.new(utf8(_("Rotate counter-clockwise"))))
     r270.image = Gtk::Image.new("#{$FPATH}/images/stock-rotate-270-16.png")
-    r270.signal_connect('activate') { closures[:rotate].call(-90) }
-    menu.append(               Gtk::SeparatorMenuItem.new)
-    if !possible_moves[:forbid_left]
-        menu.append(moveleft = Gtk::ImageMenuItem.new(utf8(_("Move left"))))
-        moveleft.image = Gtk::Image.new("#{$FPATH}/images/stock-move-left.png")
-        moveleft.signal_connect('activate') { closures[:move].call('left') }
-        if !possible_moves[:can_left]
-            moveleft.sensitive = false
-        end
-    end
-    if !possible_moves[:forbid_right]
-        menu.append(moveright = Gtk::ImageMenuItem.new(utf8(_("Move right"))))
-        moveright.image = Gtk::Image.new("#{$FPATH}/images/stock-move-right.png")
-        moveright.signal_connect('activate') { closures[:move].call('right') }
-        if !possible_moves[:can_right]
-            moveright.sensitive = false
-        end
-    end
-    menu.append(moveup = Gtk::ImageMenuItem.new(utf8(_("Move up"))))
-    moveup.image = Gtk::Image.new("#{$FPATH}/images/stock-move-up.png")
-    moveup.signal_connect('activate') { closures[:move].call('up') }
-    if !possible_moves[:can_up]
-        moveup.sensitive = false
-    end
-    menu.append(movedown = Gtk::ImageMenuItem.new(utf8(_("Move down"))))
-    movedown.image = Gtk::Image.new("#{$FPATH}/images/stock-move-down.png")
-    movedown.signal_connect('activate') { closures[:move].call('down') }
-    if !possible_moves[:can_down]
-        movedown.sensitive = false
+    r270.signal_connect('activate') { distribute_multiple_call.call(:rotate, -90) }
+    if !possible_actions[:can_multiple] || $selected_elements.length == 0
+        menu.append(               Gtk::SeparatorMenuItem.new)
+        if !possible_actions[:forbid_left]
+            menu.append(moveleft = Gtk::ImageMenuItem.new(utf8(_("Move left"))))
+            moveleft.image = Gtk::Image.new("#{$FPATH}/images/stock-move-left.png")
+            moveleft.signal_connect('activate') { closures[:move].call('left') }
+            if !possible_actions[:can_left]
+                moveleft.sensitive = false
+            end
+        end
+        if !possible_actions[:forbid_right]
+            menu.append(moveright = Gtk::ImageMenuItem.new(utf8(_("Move right"))))
+            moveright.image = Gtk::Image.new("#{$FPATH}/images/stock-move-right.png")
+            moveright.signal_connect('activate') { closures[:move].call('right') }
+            if !possible_actions[:can_right]
+                moveright.sensitive = false
+            end
+        end
+        menu.append(moveup = Gtk::ImageMenuItem.new(utf8(_("Move up"))))
+        moveup.image = Gtk::Image.new("#{$FPATH}/images/stock-move-up.png")
+        moveup.signal_connect('activate') { closures[:move].call('up') }
+        if !possible_actions[:can_up]
+            moveup.sensitive = false
+        end
+        menu.append(movedown = Gtk::ImageMenuItem.new(utf8(_("Move down"))))
+        movedown.image = Gtk::Image.new("#{$FPATH}/images/stock-move-down.png")
+        movedown.signal_connect('activate') { closures[:move].call('down') }
+        if !possible_actions[:can_down]
+            movedown.sensitive = false
+        end
     end
     if type == 'video'
-        menu.append(               Gtk::SeparatorMenuItem.new)
-        menu.append(  color_swap = Gtk::ImageMenuItem.new(utf8(_("Red/blue color swap"))))
-        color_swap.image = Gtk::Image.new("#{$FPATH}/images/stock-color-triangle-16.png")
-        color_swap.signal_connect('activate') { closures[:color_swap].call }
-        menu.append(        flip = Gtk::ImageMenuItem.new(utf8(_("Flip upside-down"))))
-        flip.image = Gtk::Image.new("#{$FPATH}/images/stock-rotate-180-16.png")
-        flip.signal_connect('activate') { closures[:rotate].call(180) }
-        menu.append(frame_offset = Gtk::ImageMenuItem.new(utf8(_("Specify frame offset"))))
-        frame_offset.image = Gtk::Image.new("#{$FPATH}/images/stock-video-16.png")
-        frame_offset.signal_connect('activate') { closures[:frame_offset].call }
+        if !possible_actions[:can_multiple] || $selected_elements.length == 0 || $selected_elements.reject { |k,v| $name2widgets[k][:type] == 'video' }.empty?
+            menu.append(               Gtk::SeparatorMenuItem.new)
+            menu.append(  color_swap = Gtk::ImageMenuItem.new(utf8(_("Red/blue color swap"))))
+            color_swap.image = Gtk::Image.new("#{$FPATH}/images/stock-color-triangle-16.png")
+            color_swap.signal_connect('activate') { distribute_multiple_call.call(:color_swap) }
+            menu.append(        flip = Gtk::ImageMenuItem.new(utf8(_("Flip upside-down"))))
+            flip.image = Gtk::Image.new("#{$FPATH}/images/stock-rotate-180-16.png")
+            flip.signal_connect('activate') { distribute_multiple_call.call(:rotate, 180) }
+            menu.append(frame_offset = Gtk::ImageMenuItem.new(utf8(_("Specify frame offset"))))
+            frame_offset.image = Gtk::Image.new("#{$FPATH}/images/stock-video-16.png")
+            frame_offset.signal_connect('activate') {
+                if possible_actions[:can_multiple] && $selected_elements.length > 0
+                    if values = ask_new_frame_offset(nil, '')
+                        distribute_multiple_call.call(:frame_offset, values)
+                    end
+                else
+                    closures[:frame_offset].call
+                end
+            }
+        end
     end
     menu.append(               Gtk::SeparatorMenuItem.new)
-    menu.append(whitebalance = Gtk::ImageMenuItem.new(utf8(_("Fix white-balance"))))
-    whitebalance.image = Gtk::Image.new("#{$FPATH}/images/stock-tool-color-balance-16.png")
-    whitebalance.signal_connect('activate') { closures[:whitebalance].call }
-    menu.append(enhance      = Gtk::ImageMenuItem.new(utf8(xmldir.attributes["#{attributes_prefix}enhance"] ? _("Original contrast") :
-                                                                                                              _("Enhance constrast"))))
+    if !possible_actions[:can_multiple] || $selected_elements.length == 0
+        menu.append(whitebalance = Gtk::ImageMenuItem.new(utf8(_("Fix white-balance"))))
+        whitebalance.image = Gtk::Image.new("#{$FPATH}/images/stock-tool-color-balance-16.png")
+        whitebalance.signal_connect('activate') { closures[:whitebalance].call }
+    end
+    if !possible_actions[:can_multiple] || $selected_elements.length == 0
+        menu.append(enhance = Gtk::ImageMenuItem.new(utf8(xmldir.attributes["#{attributes_prefix}enhance"] ? _("Original contrast") :
+                                                                                                             _("Enhance constrast"))))
+    else
+        menu.append(enhance = Gtk::ImageMenuItem.new(utf8(_("Toggle contrast enhancement"))))
+    end
     enhance.image = Gtk::Image.new("#{$FPATH}/images/stock-channels-16.png")
-    enhance.signal_connect('activate') { closures[:enhance].call }
+    enhance.signal_connect('activate') { distribute_multiple_call.call(:enhance) }
     if optionals.include?('delete')
+        menu.append(               Gtk::SeparatorMenuItem.new)
+        menu.append(cut_item     = Gtk::ImageMenuItem.new(Gtk::Stock::CUT))
+        cut_item.signal_connect('activate') { distribute_multiple_call.call(:cut) }
+        if !possible_actions[:can_multiple] || $selected_elements.length == 0
+            menu.append(paste_item   = Gtk::ImageMenuItem.new(Gtk::Stock::PASTE))
+            paste_item.signal_connect('activate') { closures[:paste].call }
+            menu.append(clear_item   = Gtk::ImageMenuItem.new(Gtk::Stock::CLEAR))
+            clear_item.signal_connect('activate') { $cuts = [] }
+            if $cuts.size == 0
+                paste_item.sensitive = clear_item.sensitive = false
+            end
+        end
         menu.append(               Gtk::SeparatorMenuItem.new)
         menu.append(delete_item  = Gtk::ImageMenuItem.new(Gtk::Stock::DELETE))
-        delete_item.signal_connect('activate') { closures[:delete].call }
+        delete_item.signal_connect('activate') { distribute_multiple_call.call(:delete) }
     end
     menu.show_all
     menu.popup(nil, nil, event.button, event.time)
@@ -684,7 +742,7 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     $vbox2widgets[vbox] = { :textview => textview, :image => img }
 
     #- to be able to find widgets by name
-    $name2widgets[filename] = { :textview => textview }
+    $name2widgets[filename] = { :textview => textview, :evtbox => evtbox, :vbox => vbox, :img => img, :type => type }
 
     cleanup_all_thumbnails = Proc.new {
         #- remove out of sync images
@@ -749,27 +807,31 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
                   })
     }
 
-    change_frame_offset_and_cleanup = Proc.new {
-        if values = ask_new_frame_offset($xmldir.elements["[@filename='#{filename}']"], '')
-            perform_change_frame_offset_and_cleanup = Proc.new { |val|
-                change_frame_offset($xmldir.elements["[@filename='#{filename}']"], '', val)
-                my_gen_real_thumbnail.call
-            }
-            perform_change_frame_offset_and_cleanup.call(values[:new])
-
-            save_undo(_("specify frame offset"),
+    change_frame_offset_and_cleanup_real = Proc.new { |values|
+        perform_change_frame_offset_and_cleanup = Proc.new { |val|
+            change_frame_offset($xmldir.elements["[@filename='#{filename}']"], '', val)
+            my_gen_real_thumbnail.call
+        }
+        perform_change_frame_offset_and_cleanup.call(values[:new])
+        
+        save_undo(_("specify frame offset"),
+                  Proc.new {
+                      perform_change_frame_offset_and_cleanup.call(values[:old])
+                      textview.grab_focus
+                      autoscroll_if_needed($autotable_sw, img, textview)
+                      $notebook.set_page(1)
                       Proc.new {
-                          perform_change_frame_offset_and_cleanup.call(values[:old])
+                          perform_change_frame_offset_and_cleanup.call(values[:new])
                           textview.grab_focus
                           autoscroll_if_needed($autotable_sw, img, textview)
                           $notebook.set_page(1)
-                          Proc.new {
-                              perform_change_frame_offset_and_cleanup.call(values[:new])
-                              textview.grab_focus
-                              autoscroll_if_needed($autotable_sw, img, textview)
-                              $notebook.set_page(1)
-                          }
-                      })
+                      }
+                  })
+    }
+
+    change_frame_offset_and_cleanup = Proc.new {
+        if values = ask_new_frame_offset($xmldir.elements["[@filename='#{filename}']"], '')
+            change_frame_offset_and_cleanup_real.call(values)
         end
     }
 
@@ -867,6 +929,7 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
                               autotable.reinsert(pos, vbox, filename)
                               $notebook.set_page(1)
                               autotable.queue_draws << proc { textview.grab_focus; autoscroll_if_needed($autotable_sw, img, textview) }
+                              $cuts = []
                               Proc.new {
                                   perform_delete.call
                                   $notebook.set_page(1)
@@ -876,6 +939,40 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
         end
     }
 
+    cut = Proc.new {
+        delete.call
+        $cuts << { :vbox => vbox, :filename => filename }
+        $statusbar.push(0, utf8(_("%s elements in the clipboard.") % $cuts.size ))
+    }
+    paste = Proc.new {
+        if $cuts.size > 0
+            $cuts.each { |elem|
+                autotable.reinsert(autotable.get_current_number(vbox), elem[:vbox], elem[:filename])
+            }
+            last = $cuts[-1]
+            autotable.queue_draws << proc {
+                $vbox2widgets[last[:vbox]][:textview].grab_focus
+                autoscroll_if_needed($autotable_sw, $vbox2widgets[last[:vbox]][:image], $vbox2widgets[last[:vbox]][:textview])
+            }
+            save_undo(_("paste"),
+                      Proc.new { |cuts|
+                          cuts.each { |elem| autotable.remove(elem[:vbox]) }
+                          $notebook.set_page(1)
+                          Proc.new {
+                              cuts.each { |elem|
+                                  autotable.reinsert(autotable.get_current_number(vbox), elem[:vbox], elem[:filename])
+                              }
+                              $notebook.set_page(1)
+                          }
+                      }, $cuts)
+            $statusbar.push(0, utf8(_("Pasted %s elements.") % $cuts.size ))
+            $cuts = []
+        end
+    }
+
+    $name2closures[filename] = { :rotate => rotate_and_cleanup, :enhance => enhance_and_cleanup, :delete => delete, :cut => cut,
+                                 :color_swap => color_swap_and_cleanup, :frame_offset => change_frame_offset_and_cleanup_real }
+
     textview.signal_connect('key-press-event') { |w, event|
         propagate = true
         if event.state != 0
@@ -969,6 +1066,7 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
                     $config['nogestures'] or $gesture_press = { :filename => filename, :x => event.x, :y => event.y }
                 end
             end
+            $ignore_for_multiple_selections = true
         elsif event.event_type == Gdk::Event::BUTTON_PRESS && event.button == 3
             if event.state & Gdk::Window::BUTTON1_MASK != 0
                 #- gesture undo: hold left mouse button then click right mouse button
@@ -978,7 +1076,7 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
         elsif event.event_type == Gdk::Event::BUTTON2_PRESS && event.button == 1
             view_element(filename, { :delete => delete })
         end
-        false  #- propagate
+        false   #- propagate
     }
 
     evtbox.signal_connect('button-release-event') { |w, event|
@@ -988,9 +1086,10 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
                 next_ = autotable.get_next_widget(vbox)
                 popup_thumbnail_menu(event, ['delete'], type, $xmldir.elements["[@filename='#{filename}']"], '',
                                      { :can_left => x > 0, :can_right => next_ && autotable.get_current_pos(next_)[0] > x,
-                                         :can_up => y > 0, :can_down => y < autotable.get_max_y },
+                                       :can_up => y > 0, :can_down => y < autotable.get_max_y, :can_multiple => true },
                                      { :rotate => rotate_and_cleanup, :move => move, :color_swap => color_swap_and_cleanup, :enhance => enhance_and_cleanup,
-                                         :frame_offset => change_frame_offset_and_cleanup, :delete => delete, :whitebalance => whitebalance_and_cleanup })
+                                       :frame_offset => change_frame_offset_and_cleanup, :delete => delete, :whitebalance => whitebalance_and_cleanup,
+                                       :cut => cut, :paste => paste })
             end
             $ignore_next_release = false
             $gesture_press = nil
@@ -1025,24 +1124,39 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
         if !done
             ctxt.targets.each { |target|
                 if target.name == 'reorder-elements'
-                    from, to = selection_data.data.to_i, autotable.get_current_number(vbox)
-                    if from != to
-                        $modified = true
-                        autotable.move(from, to)
-                        save_undo(_("reorder"),
-                                  Proc.new { |from, to|
-                                      if to > from
-                                          autotable.move(to - 1, from)
-                                      else
-                                          autotable.move(to, from + 1)
-                                      end
-                                      $notebook.set_page(1)
-                                      Proc.new {
-                                          autotable.move(from, to)
+                    move = Proc.new { |from,to|
+                        if from != to
+                            $modified = true
+                            autotable.move(from, to)
+                            save_undo(_("reorder"),
+                                      Proc.new { |from, to|
+                                          if to > from
+                                              autotable.move(to - 1, from)
+                                          else
+                                              autotable.move(to, from + 1)
+                                          end
                                           $notebook.set_page(1)
-                                      }
-                                  }, from, to)
+                                          Proc.new {
+                                              autotable.move(from, to)
+                                              $notebook.set_page(1)
+                                          }
+                                      }, from, to)
+                        end
+                    }
+                    if $multiple_dnd.size == 0
+                        move.call(selection_data.data.to_i,
+                                  autotable.get_current_number(vbox))
+                    else
+                        UndoHandler.begin_batch
+                        $multiple_dnd.sort { |a,b| autotable.get_current_number($name2widgets[a][:vbox]) <=> autotable.get_current_number($name2widgets[b][:vbox]) }.
+                                      each { |path|
+                            #- need to update current position between each call
+                            move.call(autotable.get_current_number($name2widgets[path][:vbox]),
+                                      autotable.get_current_number(vbox))
+                        }
+                        UndoHandler.end_batch
                     end
+                    $multiple_dnd = []
                 end
             }
         end
@@ -1065,6 +1179,112 @@ def create_auto_table
 
     $autotable_sw.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS)
     $autotable_sw.add_with_viewport(thumbnails_vb)
+
+    #- follows stuff for handling multiple elements selection
+    press_x = nil; press_y = nil; pos_x = nil; pos_y = nil; $selected_elements = {}
+    gc = nil
+    update_selected = Proc.new {
+        $autotable.current_order.each { |path|
+            w = $name2widgets[path][:evtbox].window
+            xm = w.position[0] + w.size[0]/2
+            ym = w.position[1] + w.size[1]/2
+            if ym < press_y && ym > pos_y || ym < pos_y && ym > press_y
+                if (xm < press_x && xm > pos_x || xm < pos_x && xm > press_x) && ! $selected_elements[path]
+                    $selected_elements[path] = { :pixbuf => $name2widgets[path][:img].pixbuf }
+                    $name2widgets[path][:img].pixbuf = $name2widgets[path][:img].pixbuf.saturate_and_pixelate(1, true)
+                end
+            end
+            if $selected_elements[path] && ! $selected_elements[path][:keep]
+                if ((xm < press_x && xm < pos_x || xm > pos_x && xm > press_x) || (ym < press_y && ym < pos_y || ym > pos_y && ym > press_y))
+                    $name2widgets[path][:img].pixbuf = $selected_elements[path][:pixbuf]
+                    $selected_elements.delete(path)
+                end
+            end
+        }
+    }
+    $autotable.signal_connect('realize') { |w,e|
+        gc = Gdk::GC.new($autotable.window)
+        gc.set_line_attributes(1, Gdk::GC::LINE_ON_OFF_DASH, Gdk::GC::CAP_PROJECTING, Gdk::GC::JOIN_ROUND)
+        gc.function = Gdk::GC::INVERT
+        #- autoscroll handling for DND and multiple selections
+        Gtk.timeout_add(100) {
+            w, x, y, mask = $autotable.window.pointer
+            if mask & Gdk::Window::BUTTON1_MASK != 0
+                if y < $autotable_sw.vadjustment.value
+                    if pos_x
+                        $autotable.window.draw_lines(gc, [[press_x, press_y], [pos_x, press_y], [pos_x, pos_y], [press_x, pos_y], [press_x, press_y]])
+                    end
+                    scroll_upper($autotable_sw, y)
+                    if not press_x.nil?
+                        w, pos_x, pos_y = $autotable.window.pointer
+                        $autotable.window.draw_lines(gc, [[press_x, press_y], [pos_x, press_y], [pos_x, pos_y], [press_x, pos_y], [press_x, press_y]])
+                        update_selected.call
+                    end
+                end
+                if y > $autotable_sw.vadjustment.value + $autotable_sw.vadjustment.page_size
+                    if pos_x
+                        $autotable.window.draw_lines(gc, [[press_x, press_y], [pos_x, press_y], [pos_x, pos_y], [press_x, pos_y], [press_x, press_y]])
+                    end
+                    scroll_lower($autotable_sw, y)
+                    if not press_x.nil?
+                        w, pos_x, pos_y = $autotable.window.pointer
+                        $autotable.window.draw_lines(gc, [[press_x, press_y], [pos_x, press_y], [pos_x, pos_y], [press_x, pos_y], [press_x, press_y]])
+                        update_selected.call
+                    end
+                end
+            end
+            true
+        }
+    }
+
+    $autotable.signal_connect('button-press-event') { |w,e|
+        if e.button == 1
+            if !$ignore_for_multiple_selections
+                press_x = e.x
+                press_y = e.y
+                if e.state & Gdk::Window::SHIFT_MASK == 0
+                    $selected_elements.each_key { |path| $name2widgets[path][:img].pixbuf = $selected_elements[path][:pixbuf] }
+                    $selected_elements = {}
+                    $statusbar.push(0, utf8(_("Nothing selected.")))
+                else
+                    $selected_elements.each_key { |path| $selected_elements[path][:keep] = true }
+                end
+                set_mousecursor(Gdk::Cursor::TCROSS)
+            end
+        end
+    }
+    $autotable.signal_connect('button-release-event') { |w,e|
+        if e.button == 1
+            if $ignore_for_multiple_selections
+                #- unselect all only now
+                $multiple_dnd = $selected_elements.keys
+                $selected_elements.each_key { |path| $name2widgets[path][:img].pixbuf = $selected_elements[path][:pixbuf] }
+                $selected_elements = {}
+                $ignore_for_multiple_selections = false
+            else
+                if pos_x
+                    $autotable.window.draw_lines(gc, [[press_x, press_y], [pos_x, press_y], [pos_x, pos_y], [press_x, pos_y], [press_x, press_y]])
+                    if $selected_elements.length > 0
+                        $statusbar.push(0, utf8(_("%s elements selected.") % $selected_elements.length))
+                    end
+                end
+                press_x = press_y = pos_x = pos_y = nil
+                set_mousecursor(Gdk::Cursor::LEFT_PTR)
+            end
+        end
+    }
+    $autotable.signal_connect('motion-notify-event') { |w,e|
+        if ! press_x.nil?
+            if pos_x
+                $autotable.window.draw_lines(gc, [[press_x, press_y], [pos_x, press_y], [pos_x, pos_y], [press_x, pos_y], [press_x, press_y]])
+            end
+            pos_x = e.x
+            pos_y = e.y
+            $autotable.window.draw_lines(gc, [[press_x, press_y], [pos_x, press_y], [pos_x, pos_y], [press_x, pos_y], [press_x, press_y]])
+            update_selected.call
+        end
+    }
+
 end
 
 def create_subalbums_page
@@ -1408,9 +1628,16 @@ def remove_all_captions
 end
 
 def change_dir
+    $selected_elements.each_key { |path|
+        $name2widgets[path][:img].pixbuf = $selected_elements[path][:pixbuf]
+    }
     $autotable.clear
     $vbox2widgets = {}
     $name2widgets = {}
+    $name2closures = {}
+    $selected_elements = {}
+    $cuts = []
+    $multiple_dnd = []
     UndoHandler.cleanup
     $undo_tb.sensitive = $undo_mb.sensitive = false
     $redo_tb.sensitive = $redo_mb.sensitive = false
index 0cf165a5a7c7432d64ad2bbd1c522b1726fabca6..64ac2ae19cff133e1349b7a837c2bbbad29f1b9b 100755 (executable)
@@ -645,17 +645,18 @@ def walk_source_dir
         #- create thumbnails for videos
         videos.each { |video|
             info("processing element")
-            thumbnail_ok = true
             if $forgui
                 thumbnail_dest_img = dest_dir + '/' + make_dest_filename(video.sub(/\.[^\.]+$/, '')) + "-#{$default_size['thumbnails']}.jpg"
-                thumbnail_ok &&= gen_thumbnails_element("#{dir}/#{video}", xmldir, true, [ { 'filename' => thumbnail_dest_img, 'size' => $default_size['thumbnails'] } ])
+                gen_thumbnails_element("#{dir}/#{video}", xmldir, true, [ { 'filename' => thumbnail_dest_img, 'size' => $default_size['thumbnails'] } ])
             else
+                todo = []
                 for sizeobj in $images_size
                     size_thumbnails = sizeobj['thumbnails']
                     thumbnail_dest_img = dest_dir + '/' + make_dest_filename(video.sub(/\.[^\.]+$/, '')) + "-#{size_thumbnails}.jpg"
                     thumbnail_videos[sizeobj['name']] << File.basename(thumbnail_dest_img)
-                    thumbnail_ok &&= gen_thumbnails_element("#{dir}/#{video}", xmldir, true, [ { 'filename' => thumbnail_dest_img, 'size' => size_thumbnails } ])
+                    todo << { 'filename' => thumbnail_dest_img, 'size' => size_thumbnails }
                 end
+                gen_thumbnails_element("#{dir}/#{video}", xmldir, true, todo)
             end
             destvideo = "#{dest_dir}/#{video}"
             if !File.exists?(destvideo)
index ca6b87b6f38405144b5e76b05a302f298cbed52a..ec83cfff97294676e7d7860ab4b1e9c518df9f43 100644 (file)
@@ -28,7 +28,7 @@ class Gtk::Allocation
     end
 end
 
-class Gtk::AutoTable < Gtk::VBox
+class Gtk::AutoTable < Gtk::EventBox
 
     attr_accessor :queue_draws
 
@@ -40,7 +40,7 @@ class Gtk::AutoTable < Gtk::VBox
         @queue_draws = []
         @row_spacings = row_spacings
         @table = nil
-        super
+        super()
         recreate_table
         signal_connect('size-allocate') { |w, allocation|
             msg 3, "got self allocation: #{allocation}"
index 8c882bd1fca3a7b48bcd089d8a394fb1a8694587..5b978089d2564f125d78f434d1bdd74cde8a4389 100644 (file)
@@ -18,6 +18,7 @@
 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
 require 'gettext'
+require 'gtk2'
 include GetText
 bindtextdomain("booh")
 
@@ -25,31 +26,56 @@ module UndoHandler
 
     @undo_actions = []
     @redo_actions = []
+    @batch_mode = false
 
     module_function
 
     def save_undo(name, closure, params)
-        @undo_actions << { :name => name, :closure => closure, :params => params }
+        entry = { :name => name, :closure => closure, :params => params }
+        if @batch_mode
+            @batch << entry
+        else
+            @undo_actions << [ entry ]
+        end
+        @redo_actions = []
     end
 
     def undo(statusbar)
-        todo = @undo_actions.pop
-        redo_closure = todo[:closure].call(*todo[:params])
-        statusbar.pop(0)
-        statusbar.push(0, utf8(_("Undo %s.") % todo[:name]))
-        @redo_actions << { :name => todo[:name], :redo => redo_closure, :undo => todo }
+        all_todos = @undo_actions.pop
+        redos = []
+        all_todos.reverse.each { |todo|
+            redo_closure = todo[:closure].call(*todo[:params])
+            statusbar.pop(0)
+            statusbar.push(0, utf8(_("Undo %s.") % todo[:name]))
+            redos << { :name => todo[:name], :redo => redo_closure, :undo => todo }
+        }
+        @redo_actions << redos
         return !@undo_actions.empty?
     end
 
     def redo(statusbar)
-        redo_item = @redo_actions.pop
-        redo_item[:redo].call
-        statusbar.pop(0)
-        statusbar.push(0, utf8(_("Redo %s.") % redo_item[:name]))
-        @undo_actions << redo_item[:undo]
+        all_redos = @redo_actions.pop
+        undos = []
+        all_redos.reverse.each { |redo_item|
+            redo_item[:redo].call
+            statusbar.pop(0)
+            statusbar.push(0, utf8(_("Redo %s.") % redo_item[:name]))
+            undos << redo_item[:undo]
+        }
+        @undo_actions << undos
         return !@redo_actions.empty?
     end
 
+    def begin_batch
+        @batch_mode = true
+        @batch = []
+    end
+
+    def end_batch
+        @batch_mode = false
+        @undo_actions << @batch
+    end
+
     def cleanup
         @undo_actions.clear
         @redo_actions.clear
index 0cd1fe029c6083883ed9a781cf8c0a7476ccae1b..6b0b691fedc3794ead16ef847f9f8d03cb8e356b 100644 (file)
@@ -137,7 +137,7 @@ module Booh
     end
 
     def waitjobs
-        while $pids.length > 0
+        while $pids && $pids.length > 0
             waitjob
         end
     end
@@ -296,6 +296,7 @@ module Booh
                 end
             end
             orig_image = "#{dest_dir}/#{File.basename(orig)}.jpg000000.jpg"
+            system("rm -f '#{orig_image}'")
             for dest in dests
                 if !File.exists?(orig_image)
                     transcode_options = ''