Hi all, On Fri, 2006-03-17 at 21:30 +0000, James Fitzsimons wrote: > On Wed, 2006-03-15 at 07:27 +1100, Bengt Thuree wrote: > > On Tue, 2006-03-14 at 19:05 +0000, James Fitzsimons wrote: > > > Something I find myself doing all the time is comparing two or more very > > > similar photos. I think it would be great if there was some sort of > > > compare functionality in f-spot and have been thinking about having a > > > crack at implementing something. > > > > Have a look at, and file possible comments/patches there > > http://bugzilla.gnome.org/show_bug.cgi?id=306958 I have spent a while hacking at a compare view and have found myself a bit stuck. It probably was a bit of an ambitious place to start for a Gtk novice but hey! I thought I'd just post the patch here and solicit comments. I'd appreciate any comments technical (I did mention I'm new to Gtk) and otherwise. I couldn't figure out how to get cvs diff to include my new CompareView.cs file into the patch so it is included separately. Also I couldn't figure out how to get the patch to include a change I made to the makefile so you will have to add the CompareView.cs file to the list of source files in the makefile to get this to build. To use the compare view select two (or more) photos in icon view then select the new full screen view icon (I'm not to sure how to make a new icon just yet) with the "Compare photos" tip. A few things I would like pointers on. - The button above the zoom slider should toggles between not linked, zoom linked, and zoom+scrolling linked. If anyone can tell me how to get the icon on a button widget to change in the onclick event I'd be most grateful (need more icons for this too - maybe chained / unchained / something else). I have so far only managed to get it to work with the stock icons. - The scroll bars don't render properly. I have a feeling this is something to do with the ImageView class / table combination. As a test I packed a standard Gtk.Image widget into a scrolled window and packed that in a table and that worked fine... - You should be able to click an image and then change the zoom factor for just that image. Unfortunately I can't seem to find anyway of figuring out which PhotoImageView was clicked? I'd originally envisaged doing something smart like laying the photos out in columns if they are in portrait and rows if they are in landscape but you can't get that information until after the images are loaded which is after you have to have packed them into the table, unless someone can tell me otherwise? Finally - is this even useful? Thanks for any comments / advice. Regards, James Fitzsimons
Index: MainWindow.cs =================================================================== RCS file: /cvs/gnome/f-spot/src/MainWindow.cs,v retrieving revision 1.291 diff -u -r1.291 MainWindow.cs --- MainWindow.cs 24 May 2006 06:59:15 -0000 1.291 +++ MainWindow.cs 24 May 2006 21:54:10 -0000 @@ -34,6 +34,7 @@ [Glade.Widget] ScrolledWindow icon_view_scrolled; [Glade.Widget] Box photo_box; + [Glade.Widget] Box compare_box; [Glade.Widget] Notebook view_notebook; [Glade.Widget] ScrolledWindow tag_selection_scrolled; @@ -109,11 +110,13 @@ Gtk.ToggleButton browse_button; Gtk.ToggleButton edit_button; + Gtk.ToggleButton compare_button; InfoBox info_box; FSpot.InfoDisplay info_display; QueryView icon_view; PhotoView photo_view; + CompareView compare_view; // FIXME probably need to make a wrapper class round CompareImageView like PhotoView. FSpot.FullScreenView fsview; FSpot.PhotoQuery query; FSpot.GroupSelector group_selector; @@ -214,6 +217,9 @@ edit_button = GtkUtil.MakeToolbarToggleButton (toolbar, "f-spot-edit-image", new System.EventHandler (HandleToggleViewPhoto)) as ToggleButton; SetTip (edit_button, Catalog.GetString ("View and edit a photo")); + compare_button = GtkUtil.MakeToolbarToggleButton(toolbar, "f-spot-edit-image", + new System.EventHandler (HandleToggleViewCompare)) as ToggleButton; + SetTip (compare_button, Catalog.GetString ("Compare photos")); toolbar.AppendSpace (); @@ -328,8 +334,7 @@ photo_view.KeyPressEvent += HandlePhotoViewKeyPressEvent; photo_view.UpdateStarted += HandlePhotoViewUpdateStarted; photo_view.UpdateFinished += HandlePhotoViewUpdateFinished; - - photo_view.View.ZoomChanged += HandleZoomChanged; + photo_view.View.ZoomChanged += HandleZoomChanged; // Tag typing: focus the tag entry if the user starts typing a tag icon_view.KeyPressEvent += HandlePossibleTagTyping; @@ -354,7 +359,12 @@ this.selection = new MainSelection (this); this.selection.Changed += HandleSelectionChanged; this.selection.ItemsChanged += HandleSelectionItemsChanged; - + + // add compare view + compare_view = new CompareView (query, this.selection); + compare_box.Add (compare_view); + compare_view.ZoomChanged += HandleZoomChanged; + UpdateMenus (); main_window.ShowAll (); @@ -401,7 +411,8 @@ // Switching mode. public enum ModeType { IconView, - PhotoView + PhotoView, + CompareView }; public void SetViewMode (ModeType value) @@ -425,6 +436,11 @@ JumpTo (icon_view.FocusCell); zoom_scale.Value = photo_view.NormalizedZoom; break; + case ModeType.CompareView: + if (view_notebook.CurrentPage != 2) + view_notebook.CurrentPage = 2; + zoom_scale.Value = compare_view.NormalizedZoom; + break; } Selection.MarkChanged (); UpdateToolbar (); @@ -445,6 +461,13 @@ if (edit_button.Active != state) edit_button.Active = state; } + + if (compare_button != null) { + bool state = view_mode == ModeType.CompareView; + + if (compare_button.Active != state) + compare_button.Active = state; + } } private void HandleDbItemsChanged (object sender, DbItemEventArgs args) @@ -493,9 +516,10 @@ ids = new int [] { fsview.View.Item.Index }; else { switch (view_mode) { + case ModeType.CompareView: case ModeType.IconView: ids = icon_view.Selection.Ids; - break; + break; default: case ModeType.PhotoView: if (photo_view.Item.IsValid) @@ -524,6 +548,7 @@ switch (win.view_mode) { case ModeType.PhotoView: return win.photo_view.Item.IsValid ? 1 : 0; + case ModeType.CompareView: case ModeType.IconView: return win.icon_view.Selection.Count; } @@ -536,6 +561,7 @@ switch (win.view_mode) { case ModeType.PhotoView: return item == win.photo_view.Item.Current ? 0 : -1; + case ModeType.CompareView: case ModeType.IconView: return win.icon_view.Selection.IndexOf (item); } @@ -547,6 +573,7 @@ switch (win.view_mode) { case ModeType.PhotoView: return item == win.photo_view.Item.Current ? true : false; + case ModeType.CompareView: case ModeType.IconView: return win.icon_view.Selection.Contains (item); } @@ -566,6 +593,7 @@ if (index == 0) return win.photo_view.Item.Current; break; + case ModeType.CompareView: case ModeType.IconView: return win.icon_view.Selection [index]; } @@ -583,6 +611,8 @@ break; case ModeType.IconView: return win.icon_view.Selection.Items; + case ModeType.CompareView: + return win.icon_view.Selection.Items; } return new IBrowsableItem [0]; } @@ -593,7 +623,7 @@ // FIXME for now we only listen to changes directly from the query // when we are in PhotoView mode because we presume that we'll get // proper notification from the icon view selection in icon view mode - if (win.view_mode != ModeType.PhotoView || ItemsChanged == null) + if (win.view_mode == ModeType.IconView || ItemsChanged == null) return; foreach (int item in args.Items) { @@ -1928,15 +1958,13 @@ else if (edit_button.Active) SetViewMode (ModeType.PhotoView); } - - void HandleViewBrowse (object sender, EventArgs args) - { - SetViewMode (ModeType.IconView); - } - - void HandleViewPhoto (object sender, EventArgs args) + + void HandleToggleViewCompare (object sender, EventArgs args) { - SetViewMode (ModeType.PhotoView); + if (view_mode == ModeType.CompareView) + compare_button.Active = true; + else if (compare_button.Active) + SetViewMode (ModeType.CompareView); } void HandleViewFullscreen (object sender, EventArgs args) @@ -1961,6 +1989,11 @@ void HandleZoomScaleValueChanged (object sender, System.EventArgs args) { switch (view_mode) { + case ModeType.CompareView: + compare_view.ZoomChanged -= HandleZoomChanged; + compare_view.NormalizedZoom = zoom_scale.Value; + compare_view.ZoomChanged += HandleZoomChanged; + break; case ModeType.PhotoView: photo_view.View.ZoomChanged -= HandleZoomChanged; photo_view.NormalizedZoom = zoom_scale.Value; @@ -1989,6 +2022,10 @@ double zoom = .5; switch (view_mode) { + case ModeType.CompareView: + zoom = compare_view.NormalizedZoom; + zoom_scale.Value = zoom; + break; case ModeType.PhotoView: zoom = photo_view.NormalizedZoom; zoom_scale.Value = zoom; @@ -2020,6 +2057,9 @@ private void ZoomOut () { switch (view_mode) { + case ModeType.CompareView: + compare_view.ZoomOut (); + break; case ModeType.PhotoView: photo_view.ZoomOut (); break; @@ -2031,9 +2071,18 @@ private void ZoomIn () { + double old_zoom; switch (view_mode) { + case ModeType.CompareView: + old_zoom = compare_view.Zoom; + try { + compare_view.ZoomIn (); + } catch { + compare_view.Zoom = old_zoom; + } + break; case ModeType.PhotoView: - double old_zoom = photo_view.Zoom; + old_zoom = photo_view.Zoom; try { photo_view.ZoomIn (); } catch { Index: f-spot.glade =================================================================== RCS file: /cvs/gnome/f-spot/src/f-spot.glade,v retrieving revision 1.161 diff -u -r1.161 f-spot.glade --- f-spot.glade 24 May 2006 05:48:39 -0000 1.161 +++ f-spot.glade 24 May 2006 21:54:27 -0000 @@ -8027,10 +8027,6 @@ <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property> <property name="shadow_type">GTK_SHADOW_IN</property> <property name="window_placement">GTK_CORNER_TOP_LEFT</property> - - <child> - <placeholder/> - </child> </widget> <packing> <property name="tab_expand">False</property> @@ -8081,6 +8077,73 @@ <widget class="GtkLabel" id="label3"> <property name="visible">True</property> <property name="label" translatable="yes">View</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="type">tab</property> + </packing> + </child> + + <child> + <widget class="GtkHBox" id="compare_box"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child> + <placeholder/> + </child> + </widget> + <packing> + <property name="tab_expand">False</property> + <property name="tab_fill">True</property> + </packing> + </child> + + + <child> + <widget class="GtkLabel" id="label211"> + <property name="visible">True</property> + <property name="label" translatable="yes"></property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="type">tab</property> + </packing> + </child> + + <child> + <placeholder/> + </child> + + <child> + <widget class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="label" translatable="yes">Compare</property> <property name="use_underline">False</property> <property name="use_markup">False</property> <property name="justify">GTK_JUSTIFY_LEFT</property>
// // CompareImageView.cs: // // Authors: // James Fitzsimons <james fitzsimons gmail com> // // (C) 2006 James Fitzsimons. // using Gtk; using Gdk; using System; using System.Collections; namespace FSpot { public class CompareView : EventBox { private class ToolBarButton : Button { public ToolBarButton () : base () { CanFocus = false; Relief = ReliefStyle.None; } public ToolBarButton (Gtk.Widget widget) : base (widget) { CanFocus = false; Relief = ReliefStyle.None; } // FIXME won't be needed once figured out how to use proper images public ToolBarButton (string stockName) : base (stockName) { CanFocus = false; Relief = ReliefStyle.None; } } private enum LinkedMode { ZoomAndScrollLinked, ZoomLinked, Unlinked } public delegate void ZoomChangedHandler (object sender, System.EventArgs args); public event ZoomChangedHandler ZoomChanged; private Gtk.Table compare_table; private FSpot.PhotoQuery query; private IBrowsableCollection selection; private ArrayList photo_image_views; private ArrayList scrolled_windows; private ToolBarButton zoom_link_button; private int selected_index; private LinkedMode current_mode; private Gtk.Image zoom_scrolled_linked_image; private Gtk.Image zoom_linked_image; private Gtk.Image unlinked_image; private Gtk.Image zoom_button_image; public CompareView (FSpot.PhotoQuery query, IBrowsableCollection iconViewSelection) : base () { this.Query = query; this.selection = iconViewSelection; this.selection.Changed += HandleSelectionChanged; photo_image_views = new ArrayList(); scrolled_windows = new ArrayList(); selected_index = 0; current_mode = LinkedMode.ZoomAndScrollLinked; Box vbox = new VBox (false, 6); Add (vbox); EventBox eventbox = new EventBox (); Frame frame = new Frame (); eventbox.Add (frame); frame.ShadowType = ShadowType.In; vbox.PackStart (eventbox, true, true, 0); Box inner_vbox = new VBox (false , 2); frame.Add (inner_vbox); compare_table = new Gtk.Table (1,1,true); compare_table.ColumnSpacing = 2; compare_table.RowSpacing = 2; inner_vbox.PackStart (compare_table, true, true, 0); FSpot.Global.ModifyColors (compare_table); FSpot.Global.ModifyColors (eventbox); Box toolbar_hbox = new HBox (false, 6); vbox.PackStart (toolbar_hbox, false, true, 0); // zoom_scrolled_linked_image = new Gtk.Image ("f-spot-crop", IconSize.Button); // zoom_linked_image = new Gtk.Image ("f-spot-desaturate", IconSize.Button); // unlinked_image = new Gtk.Image ("f-spot-sepia", IconSize.Button); zoom_link_button = new ToolBarButton (Stock.New); zoom_link_button.Clicked += HandleZoomLinkButtonClicked; toolbar_hbox.PackEnd (zoom_link_button, false, true, 0); vbox.ShowAll(); } public FSpot.PhotoQuery Query { get { return query; } set { query = value; } } private int SelectedIndex { set { selected_index = value; } get { return selected_index; } } public double Zoom { get { if (current_mode != LinkedMode.Unlinked) { // FIXME average zoom values? double zoom = 0; for (int i = 0; i < photo_image_views.Count; i++) { PhotoImageView photo_view = photo_image_views[i] as PhotoImageView; zoom += photo_view.Zoom; } return zoom / photo_image_views.Count; } else { PhotoImageView photo_view = photo_image_views [SelectedIndex] as PhotoImageView; return photo_view.Zoom; } } set { if (current_mode != LinkedMode.Unlinked) { for (int i = 0; i < photo_image_views.Count; i++) { PhotoImageView photo_view = photo_image_views[i] as PhotoImageView; photo_view.Zoom = value; } } else { PhotoImageView photo_view = photo_image_views [SelectedIndex] as PhotoImageView; photo_view.Zoom = value; } } } public double NormalizedZoom { get { if (current_mode != LinkedMode.Unlinked) { // FIXME average zoom values? double zoom = 0; for (int i = 0; i < photo_image_views.Count; i++) { PhotoImageView photo_view = photo_image_views[i] as PhotoImageView; zoom += photo_view.NormalizedZoom; } return zoom / photo_image_views.Count; } else { PhotoImageView photo_view = photo_image_views [SelectedIndex] as PhotoImageView; return photo_view.NormalizedZoom; } } set { if (current_mode != LinkedMode.Unlinked) { for (int i = 0; i < photo_image_views.Count; i++) { PhotoImageView photo_view = photo_image_views[i] as PhotoImageView; photo_view.NormalizedZoom = value; } } else { PhotoImageView photo_view = photo_image_views [SelectedIndex] as PhotoImageView; photo_view.NormalizedZoom = value; } } } public void ZoomIn () { if (current_mode != LinkedMode.Unlinked) { for (int i = 0; i < photo_image_views.Count; i++) { PhotoImageView photo_view = photo_image_views[i] as PhotoImageView; photo_view.ZoomIn (); } } else { PhotoImageView photo_view = photo_image_views [SelectedIndex] as PhotoImageView; photo_view.ZoomIn (); } } public void ZoomOut () { if (current_mode != LinkedMode.Unlinked) { for (int i = 0; i < photo_image_views.Count; i++) { PhotoImageView photo_view = photo_image_views[i] as PhotoImageView; photo_view.ZoomOut (); } } else { PhotoImageView photo_view = photo_image_views [SelectedIndex] as PhotoImageView; photo_view.ZoomOut (); } } private void HandleZoomLinkButtonClicked (object obj, EventArgs args) { Gtk.Image img; switch (current_mode) { case LinkedMode.ZoomAndScrollLinked: current_mode = LinkedMode.ZoomLinked; // set icon //zoom_link_button.Image = zoom_linked_image; img = new Gtk.Image (); img.Stock = Stock.About; zoom_link_button.Image = img; break; case LinkedMode.ZoomLinked: current_mode = LinkedMode.Unlinked; // set icon // zoom_link_button.Image = unlinked_image; img = new Gtk.Image (); img.Stock = Stock.Close; zoom_link_button.Image = img; break; case LinkedMode.Unlinked: current_mode = LinkedMode.ZoomAndScrollLinked; // set icon // zoom_link_button.Image = zoom_scrolled_linked_image; img = new Gtk.Image (); img.Stock = Stock.New; zoom_link_button.Image = img; break; } zoom_link_button.ShowAll (); Console.WriteLine("Mode {0}", current_mode); } private void HandleZoomChanged (object sender, System.EventArgs args) { if (this.ZoomChanged != null) { this.ZoomChanged (sender, args); } } private void HandleButtonPressEvent (object sender, ButtonPressEventArgs args) { if (args.Event.Type == EventType.ButtonPress && args.Event.Button == 3) { PhotoPopup popup = new PhotoPopup (); popup.Activate (this.Toplevel, args.Event); } else if (args.Event.Type == EventType.ButtonPress && args.Event.Button == 1) { SelectedIndex = PhotoViewAtPosition ((int) args.Event.X, (int) args.Event.Y); } } private int PhotoViewAtPosition (int x, int y) { Console.WriteLine("clicked x {0}, y {1}", x, y); // if click location was outside table boundaries return -1 // compare_table.s return 0; } private void HandleHorizontalScrolledEvent (object o, EventArgs args) { if (current_mode == LinkedMode.ZoomAndScrollLinked) { Adjustment sender = o as Adjustment; foreach (ScrolledWindow window in scrolled_windows) { if (sender != window.Hadjustment) window.Hadjustment.Value = sender.Value; } } } private void HandleVerticalScrolledEvent (object o, EventArgs args) { if (current_mode == LinkedMode.ZoomAndScrollLinked) { Adjustment sender = o as Adjustment; foreach (ScrolledWindow window in scrolled_windows) { if (sender != window.Vadjustment) window.Vadjustment.Value = sender.Value; } } } public void HandleSelectionChanged (FSpot.IBrowsableCollection collection) { if (collection.Count == 0) return; // clear collection of photo image views for (int i = 0; i < photo_image_views.Count; i++) { photo_image_views.Clear (); } // clear collection of scrolled windows for (int i = 0; i < scrolled_windows.Count; i++) { scrolled_windows.Clear (); } this.compare_table.BorderWidth = 3; // configure table uint rows; uint cols; if (collection.Count <= 3) { rows = 1; cols = (uint) collection.Count; } else { rows = (uint) Math.Ceiling (collection.Count / 4.0); cols = 4; } this.compare_table.Resize (rows, cols); // pack photo image views into table and display photos PhotoImageView photo_view; ScrolledWindow photo_view_scrolled; int count = 0; for (uint i = 0; i < rows; i++) { for (uint j = 0; j < cols; j++) { if (count++ < collection.Count) { photo_view = new PhotoImageView (this.Query); photo_view.ZoomChanged += HandleZoomChanged; photo_image_views.Add (photo_view); photo_view_scrolled = new ScrolledWindow (null, null); scrolled_windows.Add (photo_view_scrolled); photo_view_scrolled.SetPolicy (PolicyType.Automatic, PolicyType.Automatic); photo_view_scrolled.ShadowType = ShadowType.None; photo_view_scrolled.ButtonPressEvent += HandleButtonPressEvent; photo_view_scrolled.Hadjustment.ValueChanged += HandleHorizontalScrolledEvent; photo_view_scrolled.Vadjustment.ValueChanged += HandleVerticalScrolledEvent; photo_view_scrolled.Add (photo_view); FSpot.Global.ModifyColors (photo_view); FSpot.Global.ModifyColors (photo_view_scrolled); this.compare_table.Attach (photo_view_scrolled, j, j + 1, i, i + 1); } } } this.compare_table.ShowAll (); for (int i = 0; i < photo_image_views.Count; i++) { PhotoImageView view = photo_image_views[i] as PhotoImageView; view.Item.Index = this.Query.IndexOf (collection.Items[i]); } } } }
Attachment:
signature.asc
Description: This is a digitally signed message part