With all the talk and excitement over Nat's tagging patch, I wanted to bring my query patch back up to get feedback, patches, etc. The patch allows for arbitrary queries with AND, OR, and NOT operators, doesn't use a dialog box (it looks very much like the "Find" bar that pops up when you do a simple query now) and supports modifying the query via the context menu, program menu, and drag and drop. It also supports shift-F10 context menus and the patch includes a class that makes supporting that much simpler - so hopefully we can fix the other context menus in F-Spot soon. Issues it has include - if you query based on a category, it should count any child of that category as a match as well. - discoverability; I've thought about putting buttons at the top of the tag list that make querying more discoverable - tags in the query without photos are invisible - triggers a few too many reloads You can see a screenshot that gives you some idea of it's functionality here: http://bugzilla.gnome.org/attachment.cgi?id=54566&action=view In addition to applying the patch with patch -p0 < query.patch from the src directory, you'll need to copy the attached f-spot-not.png image into the icons directory. Peace, Gabriel Burt
diff -rup ../../f-spot-HEAD/src/DirectoryAdaptor.cs DirectoryAdaptor.cs --- ../../f-spot-HEAD/src/DirectoryAdaptor.cs 2005-11-10 00:10:28.000000000 -0600 +++ DirectoryAdaptor.cs 2005-11-10 01:59:05.000000000 -0600 @@ -81,7 +81,7 @@ namespace FSpot { public override void Reload () { System.Collections.Hashtable ht = new System.Collections.Hashtable (); - Photo [] photos = query.Store.Query (null, null); + Photo [] photos = query.Store.Query (null, null, null); foreach (Photo p in photos) { if (ht.Contains (p.DirectoryPath)) { diff -rup ../../f-spot-HEAD/src/f-spot.glade f-spot.glade --- ../../f-spot-HEAD/src/f-spot.glade 2005-11-10 00:10:29.000000000 -0600 +++ f-spot.glade 2005-11-10 02:34:13.000000000 -0600 @@ -7169,9 +7169,17 @@ Photo Details</property> </child> <child> - <widget class="GtkMenuItem" id="find_tag"> + <widget class="GtkMenuItem" id="include_tag"> <property name="visible">True</property> - <property name="label" translatable="yes">Find by _Tag</property> + <property name="label" translatable="yes">_Include Tag</property> + <property name="use_underline">True</property> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="require_tag"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Require Tag</property> <property name="use_underline">True</property> </widget> </child> diff -rup ../../f-spot-HEAD/src/MainWindow.cs MainWindow.cs --- ../../f-spot-HEAD/src/MainWindow.cs 2005-11-10 00:10:28.000000000 -0600 +++ MainWindow.cs 2005-11-10 02:02:43.000000000 -0600 @@ -74,7 +74,8 @@ public class MainWindow { [Glade.Widget] MenuItem attach_tag; [Glade.Widget] MenuItem remove_tag; - [Glade.Widget] MenuItem find_tag; + [Glade.Widget] MenuItem require_tag; + [Glade.Widget] MenuItem include_tag; [Glade.Widget] Scale zoom_scale; @@ -82,6 +83,11 @@ public class MainWindow { [Glade.Widget] Gtk.Image near_image; [Glade.Widget] Gtk.Image far_image; + + // Not used in this version, only for adding buttons at the top + // of the tag list for including or requiring a tag for discoverability sake + [Glade.Widget] Gtk.ToggleButton tag_query_include; + [Glade.Widget] Gtk.ToggleButton tag_query_require; Gtk.Toolbar toolbar; @@ -97,6 +103,7 @@ public class MainWindow { FSpot.FullScreenView fsview; FSpot.PhotoQuery query; FSpot.GroupSelector group_selector; + FSpot.QueryWidget query_widget; FSpot.Delay slide_delay; @@ -105,9 +112,10 @@ public class MainWindow { bool write_metadata = false; // Drag and Drop - enum TargetType { + public enum TargetType { UriList, TagList, + TagQueryItem, PhotoList, RootWindow }; @@ -129,6 +137,7 @@ public class MainWindow { private static TargetEntry [] tag_dest_target_table = new TargetEntry [] { new TargetEntry ("application/x-fspot-photos", 0, (uint) TargetType.PhotoList), + new TargetEntry ("application/x-fspot-tag-query-item", 0, (uint) TargetType.TagQueryItem), new TargetEntry ("text/uri-list", 0, (uint) TargetType.UriList), new TargetEntry ("application/x-fspot-tags", 0, (uint) TargetType.TagList), }; @@ -182,6 +191,7 @@ public class MainWindow { DragAction.Copy | DragAction.Move ); tag_selection_widget.ButtonPressEvent += HandleTagSelectionButtonPressEvent; + tag_selection_widget.RowActivated += HandleTagSelectionRowActivated; info_box = new InfoBox (); info_box.VersionIdChanged += HandleInfoBoxVersionIdChange; @@ -205,9 +215,9 @@ public class MainWindow { view_vbox.PackStart (group_selector, false, false, 0); view_vbox.ReorderChild (group_selector, 0); - FSpot.QueryDisplay query_display = new FSpot.QueryDisplay (query, tag_selection_widget); - view_vbox.PackStart (query_display, false, false, 0); - view_vbox.ReorderChild (query_display, 1); + query_widget = new FSpot.QueryWidget (query, db, tag_selection_widget); + view_vbox.PackStart (query_widget, false, false, 0); + view_vbox.ReorderChild (query_widget, 1); icon_view = new QueryView (query); LoadPreference (Preferences.THUMBNAIL_WIDTH); @@ -237,8 +247,11 @@ public class MainWindow { near_image.SetFromStock ("f-spot-stock_near", IconSize.SmallToolbar); far_image.SetFromStock ("f-spot-stock_far", IconSize.SmallToolbar); - menu = new TagMenu (find_tag, db.Tags); - menu.TagSelected += HandleFindTagMenuSelected; + menu = new TagMenu (include_tag, db.Tags); + menu.TagSelected += HandleFindTagIncluded; + + menu = new TagMenu (require_tag, db.Tags); + menu.TagSelected += HandleFindTagRequired; PhotoTagMenu pmenu = new PhotoTagMenu (); pmenu.TagSelected += HandleRemoveTagMenuSelected; @@ -281,7 +294,8 @@ public class MainWindow { main_window.DeleteEvent += HandleDeleteEvent; - query_display.HandleChanged (query); + query_widget.HandleChanged (query); + query_widget.Hide (); if (Toplevel == null) Toplevel = this; @@ -380,6 +394,16 @@ public class MainWindow { } } + public bool TagIncluded (Tag tag) + { + return query_widget.TagIncluded (tag); + } + + public bool TagRequired (Tag tag) + { + return query_widget.TagRequired (tag); + } + void HandleViewNotebookSwitchPage (object sender, SwitchPageArgs args) { switch (view_notebook.CurrentPage) { @@ -515,6 +539,11 @@ public class MainWindow { args.RetVal = true; } } + + void HandleTagSelectionRowActivated (object sender, RowActivatedArgs args) + { + query_widget.Include (new Tag [] {tag_selection_widget.TagByPath (args.Path)}); + } void HandleTagSelectionDragBegin (object sender, DragBeginArgs args) { @@ -558,7 +587,7 @@ public class MainWindow { args.SelectionData.Set (targets[0], 8, data, data.Length); break; - } + } } void HandleTagSelectionDragDrop (object sender, DragDropArgs args) @@ -591,6 +620,8 @@ public class MainWindow { foreach (int num in SelectedIds ()) { AddTagExtended (num, tags); } + + query_widget.PhotoTagsChanged (tags); break; case (uint)TargetType.UriList: UriList list = new UriList (args.SelectionData); @@ -960,19 +991,64 @@ public class MainWindow { foreach (int num in SelectedIds ()) { AddTagExtended (num, new Tag [] {t}); } + + query_widget.PhotoTagsChanged (new Tag[] {t}); + } + + void HandleFindTagIncluded (Tag t) + { + query_widget.Include (new Tag [] {t}); + } + + void HandleFindTagRequired (Tag t) + { + query_widget.Require (new Tag [] {t}); } - void HandleFindTagMenuSelected (Tag t) + public void HandleTagIncludeToggled (object sender, EventArgs args) { - tag_selection_widget.TagSelection = new Tag [] {t}; + if (tag_query_include.Active) + HandleIncludeTag (null, null); + else + HandleUnIncludeTag (null, null); + } + + public void HandleTagRequireToggled (object sender, EventArgs args) + { + if (tag_query_require.Active) + HandleRequireTag (null, null); + else + HandleUnRequireTag (null, null); + } + + public void HandleIncludeTag (object sender, EventArgs args) + { + query_widget.Include (tag_selection_widget.TagHighlight ()); + } + + public void HandleUnIncludeTag (object sender, EventArgs args) + { + query_widget.UnInclude (tag_selection_widget.TagHighlight ()); + } + + public void HandleRequireTag (object sender, EventArgs args) + { + query_widget.Require (tag_selection_widget.TagHighlight ()); } + public void HandleUnRequireTag (object sender, EventArgs args) + { + query_widget.UnRequire (tag_selection_widget.TagHighlight ()); + } + public void HandleRemoveTagMenuSelected (Tag t) { foreach (int num in SelectedIds ()) { query.Photos [num].RemoveTag (t); query.Commit (num); } + + query_widget.PhotoTagsChanged (new Tag [] {t}); } // @@ -1328,6 +1404,8 @@ public class MainWindow { foreach (int num in ids) { AddTagExtended (num, tags); } + + query_widget.PhotoTagsChanged (tags); } public void HandleRemoveTagCommand (object obj, EventArgs args) @@ -1338,6 +1416,8 @@ public class MainWindow { query.Photos [num].RemoveTag (tags); query.Commit (num); } + + query_widget.PhotoTagsChanged (tags); } public void HandleEditSelectedTag (object obj, EventArgs args) @@ -1463,7 +1543,7 @@ public class MainWindow { else group_selector.Show (); } - + void HandleDisplayInfoSidebar (object sender, EventArgs args) { if (info_vpaned.Visible) @@ -1966,6 +2046,9 @@ public class MainWindow { attach_tag.Sensitive = active_selection; remove_tag.Sensitive = active_selection; + + //tag_query_include.Sensitive = tag_sensitive; + //tag_query_require.Sensitive = tag_sensitive; rotate_left.Sensitive = active_selection; rotate_right.Sensitive = active_selection; @@ -1988,5 +2071,13 @@ public class MainWindow { remove_tag_from_selection.Sensitive = tag_sensitive && active_selection; } + public void GetWidgetPosition(Widget widget, out int x, out int y) + { + main_window.GdkWindow.GetOrigin(out x, out y); + + x += widget.Allocation.X; + y += widget.Allocation.Y; + } + } diff -rup ../../f-spot-HEAD/src/Makefile.am Makefile.am --- ../../f-spot-HEAD/src/Makefile.am 2005-11-10 00:10:28.000000000 -0600 +++ Makefile.am 2005-11-10 02:20:06.000000000 -0600 @@ -65,6 +65,7 @@ F_SPOT_CSDISTFILES = \ $(srcdir)/PixbufUtils.cs \ $(srcdir)/PixbufCache.cs \ $(srcdir)/PixelBuffer.cs \ + $(srcdir)/PopupManager.cs \ $(srcdir)/Preferences.cs \ $(srcdir)/PreviewPopup.cs \ $(srcdir)/PrintDialog.cs \ @@ -80,6 +81,7 @@ F_SPOT_CSDISTFILES = \ $(srcdir)/TagCommands.cs \ $(srcdir)/TagMenu.cs \ $(srcdir)/TagPopup.cs \ + $(srcdir)/TagQueryWidget.cs \ $(srcdir)/TagSelectionWidget.cs \ $(srcdir)/TagStore.cs \ $(srcdir)/TagView.cs \ @@ -94,6 +96,7 @@ F_SPOT_CSDISTFILES = \ $(srcdir)/ThumbnailCommand.cs \ $(srcdir)/QueryDisplay.cs \ $(srcdir)/QueryView.cs \ + $(srcdir)/QueryWidget.cs \ $(srcdir)/ZoomUtils.cs \ $(srcdir)/GPhotoCamera.cs \ $(srcdir)/CameraSelectionDialog.cs \ @@ -126,6 +129,7 @@ RESOURCES = \ -resource:$(top_srcdir)/icons/f-spot-hidden.png,f-spot-hidden.png \ -resource:$(top_srcdir)/icons/f-spot-loading.png,f-spot-loading.png \ -resource:$(top_srcdir)/icons/f-spot-logo.png,f-spot-logo.png \ + -resource:$(top_srcdir)/icons/f-spot-not.png,f-spot-not.png \ -resource:$(top_srcdir)/icons/f-spot-other.png,f-spot-other.png \ -resource:$(top_srcdir)/icons/f-spot-people.png,f-spot-people.png \ -resource:$(top_srcdir)/icons/f-spot-places.png,f-spot-places.png \ Only in : Makefile.in diff -rup ../../f-spot-HEAD/src/PhotoQuery.cs PhotoQuery.cs --- ../../f-spot-HEAD/src/PhotoQuery.cs 2005-11-10 00:10:28.000000000 -0600 +++ PhotoQuery.cs 2005-11-10 01:57:25.000000000 -0600 @@ -7,13 +7,14 @@ namespace FSpot { private Photo [] photos; private PhotoStore store; private Tag [] tags; + private string extra_condition; private PhotoStore.DateRange range = null; // Constructor public PhotoQuery (PhotoStore store) { this.store = store; - photos = store.Query (null, range); + photos = store.Query (null, null, range); } public int Count { @@ -61,7 +62,19 @@ namespace FSpot { set { tags = value; - photos = store.Query (tags, range); + photos = store.Query (tags, extra_condition, range); + RequestReload (); + } + } + + public string ExtraCondition { + get { + return extra_condition; + } + + set { + extra_condition = value; + photos = store.Query (tags, extra_condition, range); RequestReload (); } } @@ -72,7 +85,7 @@ namespace FSpot { } set { range = value; - photos = store.Query (tags, range); + photos = store.Query (tags, extra_condition, range); RequestReload (); } } diff -rup ../../f-spot-HEAD/src/PhotoStore.cs PhotoStore.cs --- ../../f-spot-HEAD/src/PhotoStore.cs 2005-11-10 00:10:28.000000000 -0600 +++ PhotoStore.cs 2005-11-10 01:58:44.000000000 -0600 @@ -1033,15 +1033,16 @@ public class PhotoStore : DbStore { // Queries. public Photo [] Query (Tag [] tags, DateTime start, DateTime end) { - return Query (tags, new DateRange (start, end)); + return Query (tags, null, new DateRange (start, end)); } public Photo [] Query (Tag [] tags) { - return Query (tags, null); + return Query (tags, null, null); } public Photo [] Query (string query) { + //Console.WriteLine("Query: {0}", query); //Console.WriteLine ("Query Start {0}", System.DateTime.Now.ToLongTimeString ()); SqliteCommand command = new SqliteCommand (); @@ -1111,7 +1112,7 @@ public class PhotoStore : DbStore { return Query (query_string); } - public Photo [] Query (Tag [] tags, DateRange range) + public Photo [] Query (Tag [] tags, string extra_condition, DateRange range) { string query; @@ -1134,9 +1135,10 @@ public class PhotoStore : DbStore { // photos.default_version_id // FROM photos, photo_tags // WHERE photos.id = photo_tags.photo_id - // AND (photo_tags.tag_id = tag1 - // OR photo_tags.tag_id = tag2 - // OR photo_tags.tag_id = tag3 ...) + // AND (photo_tags.tag_id = cat1tag1 + // OR photo_tags.tag_id = cat1tag2 ) + // AND (photo_tags.tag_id = cat2tag1 + // OR photo_tags.tag_id = cat2tag2 ) // GROUP BY photos.id StringBuilder query_builder = new StringBuilder (); @@ -1169,8 +1171,7 @@ public class PhotoStore : DbStore { continue; if (first) { - query_builder.Append (String.Format ("{0} photos.id IN (SELECT photo_id FROM photo_tags WHERE tag_id IN (", - hide || range != null ? " AND " : " WHERE ")); + query_builder.Append (String.Format ("{0} photos.id IN (SELECT photo_id FROM photo_tags WHERE tag_id IN (", hide || range != null ? " AND " : " WHERE ")); } query_builder.Append (String.Format ("{0}{1} ", first ? "" : ", ", t.Id)); @@ -1181,6 +1182,16 @@ public class PhotoStore : DbStore { if (!first) query_builder.Append (")) "); } + + if (extra_condition != null) { + query_builder.Append ( + String.Format ( + "{0} {1} ", + (hide || range != null || (tags != null && tags.Length > 0)) ? " AND " : " WHERE ", + extra_condition + ) + ); + } query_builder.Append ("ORDER BY photos.time"); query = query_builder.ToString (); diff -rup ../../f-spot-HEAD/src/TagSelectionWidget.cs TagSelectionWidget.cs --- ../../f-spot-HEAD/src/TagSelectionWidget.cs 2005-11-10 00:10:28.000000000 -0600 +++ TagSelectionWidget.cs 2005-11-10 01:34:43.000000000 -0600 @@ -48,24 +48,34 @@ public class TagSelectionWidget : TreeVi public Tag TagAtPosition (int x, int y) { TreePath path; - TreeIter iter; // Work out which tag we're dropping onto if (!this.GetPathAtPos (x, y, out path)) return null; + return TagByPath (path); + } + + public Tag TagByPath (TreePath path) + { + TreeIter iter; + if (!Model.GetIter (out iter, path)) return null; + + return TagByIter (iter); + } + + public Tag TagByIter (TreeIter iter) + { + GLib.Value val = new GLib.Value (); - GLib.Value value = new GLib.Value (); - - Model.GetValue (iter, 0, ref value); - uint tag_id = (uint) value; - Tag tag = tag_store.Get (tag_id) as Tag; + Model.GetValue (iter, 0, ref val); + uint tag_id = (uint) val; - return tag; + return tag_store.Get (tag_id) as Tag; } - + public void Select (Tag tag) { if (! selection.Contains (tag.Id)) @@ -438,8 +448,8 @@ public class TagSelectionWidget : TreeVi toggle_renderer.Toggled += new ToggledHandler (OnCellToggled); TreeViewColumn column; - column = AppendColumn ("check", toggle_renderer, new TreeCellDataFunc (CheckBoxDataFunc)); - column.SortColumnId = 0; + //column = AppendColumn ("check", toggle_renderer, new TreeCellDataFunc (CheckBoxDataFunc)); + //column.SortColumnId = 0; AppendColumn ("icon", new CellRendererPixbuf (), new TreeCellDataFunc (IconDataFunc)); diff -rup ../../f-spot-HEAD/src/TimeAdaptor.cs TimeAdaptor.cs --- ../../f-spot-HEAD/src/TimeAdaptor.cs 2005-11-10 00:10:29.000000000 -0600 +++ TimeAdaptor.cs 2005-11-10 01:59:43.000000000 -0600 @@ -117,7 +117,7 @@ namespace FSpot { { years.Clear (); - Photo [] photos = query.Store.Query (null, null); + Photo [] photos = query.Store.Query (null, null, null); Array.Sort (query.Photos); Array.Reverse (query.Photos); Array.Reverse (photos); --- /dev/null 2005-11-08 00:39:39.504664232 -0600 +++ TagQueryWidget.cs 2005-11-10 02:24:19.000000000 -0600 @@ -0,0 +1,1177 @@ +using System; +using System.Collections; +using System.Text; +using Mono.Posix; +using Gtk; +using Gdk; + +namespace FSpot.Tags { + public class LiteralPopup : AbstractPopup { + private Literal literal; + + public LiteralPopup (Gtk.Widget es, Literal literal) : base(es) + { + this.literal = literal; + } + + protected override object GetObject () + { + return EventSource; + } + + public override void Populate(Gtk.Menu popup_menu) + { + GtkUtil.MakeMenuItem (popup_menu, + Catalog.GetString ((literal.IsNegated ? "Include" : "Filter")), + new EventHandler (literal.HandleToggleNegatedCommand), true); + + int required_by, grouped_with; + bool required = LogicWidget.Root.TagRequired (literal.Tag, out required_by, out grouped_with); + if (required && (required_by > 1 || grouped_with > 1)) + GtkUtil.MakeMenuItem (popup_menu, Mono.Posix.Catalog.GetString ("Do not require"), + new EventHandler (literal.HandleUnRequireTag), true); + else if (!required) + GtkUtil.MakeMenuItem (popup_menu, Mono.Posix.Catalog.GetString ("Require"), + new EventHandler (literal.HandleRequireTag), true); + + GtkUtil.MakeMenuItem (popup_menu, Catalog.GetString ("Remove"), + new EventHandler (literal.HandleRemoveCommand), true); + + GtkUtil.MakeMenuSeparator (popup_menu); + + MenuItem attach_item = new MenuItem (Catalog.GetString ("With")); + TagMenu attach_menu = new TagMenu (attach_item, MainWindow.Toplevel.Database.Tags); + attach_menu.TagSelected += literal.HandleAttachTagCommand; + attach_item.ShowAll (); + popup_menu.Append (attach_item); + } + } + + public abstract class LogicTerm { + public LogicTerm (LogicTerm parent, Literal after) + { + this.parent = parent; + + if (parent != null) { + if (after == null) + parent.Add (this); + else + parent.SubTerms.Insert (parent.SubTerms.IndexOf (after) + 1, this); + } + } + + /** Properties **/ + + public bool HasMultiple { + get { + return (SubTerms.Count > 1); + } + } + + public LogicTerm Last { + get { + // Return the last Literal in this term + if (SubTerms.Count > 0) + return SubTerms[SubTerms.Count - 1] as LogicTerm; + else + return null; + } + } + + public int Count { + get { + return SubTerms.Count; + } + } + + public LogicTerm Parent { + get { + return parent; + } + } + + /** Methods **/ + + public void Add (LogicTerm term) + { + SubTerms.Add (term); + } + + public void Remove (LogicTerm term) + { + SubTerms.Remove (term); + + // Remove ourselves if we're now empty + if (SubTerms.Count == 0) + if (Parent != null) + Parent.Remove (this); + } + + public ArrayList FindByTag (Tag t) + { + return FindByTag (t, true); + } + + public ArrayList FindByTag (Tag t, bool recursive) + { + ArrayList results = new ArrayList (); + + if (tag != null && tag == t) + results.Add (this); + + if (recursive) + foreach (LogicTerm term in SubTerms) + results.AddRange (term.FindByTag (t, true)); + else + foreach (LogicTerm term in SubTerms) { + foreach (LogicTerm literal in SubTerms) { + if (literal.tag != null && literal.tag == t) { + results.Add (literal); + } + } + + if (term.tag != null && term.tag == t) { + results.Add (term); + } + } + + return results; + } + + public ArrayList LiteralParents () + { + ArrayList results = new ArrayList (); + + bool meme = false; + foreach (LogicTerm term in SubTerms) { + if (term is Literal) + meme = true; + + results.AddRange (term.LiteralParents ()); + } + + if (meme) + results.Add (this); + + return results; + } + + public bool TagIncluded(Tag t) + { + ArrayList parents = LiteralParents (); + + if (parents.Count == 0) + return false; + + foreach (LogicTerm term in parents) { + bool termHasTag = false; + bool onlyTerm = true; + foreach (LogicTerm literal in term.SubTerms) { + if (literal.tag != null) { + if (literal.tag == t) { + termHasTag = true; + } else { + onlyTerm = false; + } + } + } + + if (termHasTag && onlyTerm) + return true; + } + + return false; + } + + public bool TagRequired(Tag t) + { + int count, grouped_with; + return TagRequired(t, out count, out grouped_with); + } + + public bool TagRequired(Tag t, out int num_terms, out int grouped_with) + { + ArrayList parents = LiteralParents (); + + num_terms = 0; + grouped_with = 100; + int min_grouped_with = 100; + + if (parents.Count == 0) + return false; + + foreach (LogicTerm term in parents) { + bool termHasTag = false; + + // Don't count it as required if it's the only subterm..though it is.. + // it is more clearly identified as Included at that point. + //if (term.Count > 1) { + foreach (LogicTerm literal in term.SubTerms) { + if (literal.tag != null) { + if (literal.tag == t) { + num_terms++; + termHasTag = true; + grouped_with = term.SubTerms.Count; + break; + } + } + } + //} + + if (grouped_with < min_grouped_with) + min_grouped_with = grouped_with; + + if (!termHasTag) + return false; + } + + grouped_with = min_grouped_with; + + return true; + } + + // Recursively generate the SQL condition clause that this + // term represents. + public virtual string ConditionString () + { + + StringBuilder condition = new StringBuilder ("("); + + for (int i = 0; i < SubTerms.Count; i++) { + LogicTerm term = SubTerms[i] as LogicTerm; + condition.Append (term.ConditionString ()); + + if (i != SubTerms.Count - 1) + condition.Append (SQLOperator ()); + } + + condition.Append(")"); + + return condition.ToString (); + } + + public virtual Gtk.Widget SeparatorWidget () + { + return null; + } + + public virtual string SQLOperator () + { + return ""; + } + + private ArrayList SubTerms = new ArrayList (); + private LogicTerm parent = null; + private string separator; + + protected Tag tag = null; + } + + public class AndTerm : LogicTerm { + public AndTerm (LogicTerm parent, Literal after) : base (parent, after) {} + + public override Widget SeparatorWidget () + { + Widget sep = new Label (""); + sep.SetSizeRequest (3, 1); + sep.Show (); + return sep; + //return null; + } + + public override string SQLOperator () + { + return " AND "; + } + } + + public class OrTerm : LogicTerm { + public OrTerm (LogicTerm parent, Literal after) : base (parent, after) {} + + private static string OR = " " + Catalog.GetString ("or") + " "; + + public override Gtk.Widget SeparatorWidget () + { + Widget label = new Label (OR); + label.Show (); + return label; + } + + public override string SQLOperator () + { + return " OR "; + } + } + + public class Literal : LogicTerm { + public Literal (LogicTerm parent, Tag tag, Literal after) : base (parent, after) { + this.tag = tag; + } + + /** Properties **/ + + public static ArrayList FocusedLiterals + { + get { + return focusedLiterals; + } + set { + focusedLiterals = value; + } + } + + public static Tooltips Tips { + set { + tips = value; + } + } + + public Tag Tag { + get { + return tag; + } + } + + public bool IsNegated { + get { + return isNegated; + } + + set { + isNegated = value; + + UpdateImage (); + + if (NegatedToggled != null) + NegatedToggled (this); + } + } + + private Pixbuf NegatedIcon + { + get { + if (negated_icon != null) + return negated_icon; + + + negated_icon = normal_icon.Copy (); + + int offset = thumbnail_size - overlay_size; + (NegatedOverlay ()).Composite (negated_icon, offset, 0, overlay_size, overlay_size, offset, 0, 1.0, 1.0, InterpType.Bilinear, 200); + + return negated_icon; + } + + set { + negated_icon = null; + } + } + + public Widget Widget { + get { + if (widget != null) + return widget; + + + EventBox container = new EventBox (); + image = new Gtk.Image (NormalIcon); + container.Add (image); + //image.Ypad = 2; + + /*Button button = new Button (image); + container.Add (button); + button.Show (); + + button.BorderWidth = 0; + button.Style.XThickness = 0; + button.Style.YThickness = 0; + button.Relief = ReliefStyle.None;*/ + + container.CanFocus = true; + //container.Add (image); + + container.KeyPressEvent += KeyHandler; + container.ButtonPressEvent += ClickHandler; + container.FocusInEvent += HandleFocusIn; + + //image.KeyPressEvent += KeyHandler; + //image.ButtonPressEvent += ClickHandler; + //image.FocusInEvent += HandleFocusIn; + + PopupManager pm = new PopupManager (new LiteralPopup (container, this)); + + // Setup this widget as a drag source (so tags can be moved after being placed) + container.DragDataGet += HandleDragDataGet; + container.DragBegin += HandleDragBegin; + container.DragEnd += HandleDragEnd; + + Gtk.Drag.SourceSet (container, Gdk.ModifierType.Button1Mask | Gdk.ModifierType.Button3Mask, + tag_target_table, DragAction.Copy | DragAction.Move); + + // Setup this widget as a drag destination (so tags can be added to our parent's LogicTerm) + container.DragDataReceived += HandleDragDataReceived; + + Gtk.Drag.DestSet (container, DestDefaults.All, tag_dest_target_table, + DragAction.Copy | DragAction.Move ); + + tips.SetTip (container, tag.Name, null); + + image.Show (); + container.Show (); + + widget = container; + + return widget; + } + } + + private Pixbuf NormalIcon + { + get { + if (normal_icon != null) + return normal_icon; + + //Pixbuf normal_icon = new Pixbuf (Gdk.Colorspace.Rgb, true, 8, 32, 32); + //normal_icon.Fill (0x00000000); + + Pixbuf scaled = null; + scaled = tag.Icon; + + for (Category category = tag.Category; category != null && scaled == null; category = category.Category) + scaled = category.Icon; + + // FIXME need to have a default icon here for tags w/o icons + if (scaled == null) { + Console.WriteLine ("FIXME: Tag icon is null, so using all black icon!"); + normal_icon = new Pixbuf (Gdk.Colorspace.Rgb, true, 8, 30, 30); + normal_icon.Fill (0x00000000); + return normal_icon; + } + + if (scaled.Width != thumbnail_size) { + scaled = scaled.ScaleSimple (thumbnail_size, thumbnail_size, InterpType.Bilinear); + } + + normal_icon = scaled; + + return normal_icon; + } + + set { + normal_icon = null; + } + } + + /** Methods **/ + public void Update () + { + // Clear out the old icons + NormalIcon = null; + NegatedIcon = null; + + UpdateImage (); + } + + public void RemoveSelf () + { + if (Removing != null) + Removing (this); + + if (Parent != null) + Parent.Remove (this); + + if (Removed != null) + Removed (this); + } + + public override string ConditionString () + { + return String.Format ( + "id {0}IN (SELECT photo_id FROM photo_tags WHERE tag_id={1})", + (IsNegated ? "NOT " : ""), tag.Id); + } + + public override Gtk.Widget SeparatorWidget () + { + return new Label ("ERR"); + } + + private void UpdateImage () + { + if (IsNegated) { + tips.SetTip (widget, String.Format (Catalog.GetString ("Not {0}"), tag.Name), null); + image.Pixbuf = NegatedIcon; + } else { + tips.SetTip (widget, tag.Name, null); + image.Pixbuf = NormalIcon; + } + } + + private static Pixbuf NegatedOverlay () + { + // Probably should have a listener for thumbnail_size that will + // regenerate all the icons and negated icons + if (negated_overlay == null) { + System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly (); + negated_overlay = new Pixbuf (assembly.GetManifestResourceStream ("f-spot-not.png")); + negated_overlay = negated_overlay.ScaleSimple (overlay_size, overlay_size, InterpType.Bilinear); + } + + return negated_overlay; + } + + public static void RemoveFocusedLiterals () + { + if (focusedLiterals != null) + foreach (Literal literal in focusedLiterals) + literal.RemoveSelf (); + } + + /** Handlers **/ + + private void KeyHandler (object o, KeyPressEventArgs args) + { + args.RetVal = false; + + switch (args.Event.Key) { + case Gdk.Key.Delete: + RemoveFocusedLiterals (); + args.RetVal = true; + return; + } + } + + private void ClickHandler (object o, ButtonPressEventArgs args) + { + args.RetVal = true; + + switch (args.Event.Type) { + case EventType.TwoButtonPress: + if (args.Event.Button == 1) + IsNegated = !IsNegated; + else + args.RetVal = false; + return; + + case EventType.ButtonPress: + Widget.GrabFocus (); + + if (args.Event.Button == 1) { + // FIXME allow multiple selection of literals so they can be deleted, modified all at once + //if ((args.Event.State & ModifierType.ControlMask) != 0) { + //} + } + //else if (args.Event.Button == 3) + //{ + //} + + return; + + default: + args.RetVal = false; + return; + } + } + + void HandleDragDataGet (object sender, DragDataGetArgs args) + { + args.RetVal = true; + switch (args.Info) { + case (uint) MainWindow.TargetType.TagList: + case (uint) MainWindow.TargetType.TagQueryItem: + Byte [] data = Encoding.UTF8.GetBytes (""); + Atom [] targets = args.Context.Targets; + + args.SelectionData.Set (targets[0], 8, data, data.Length); + + return; + default: + args.RetVal = false; + focusedLiterals = null; + break; + } + } + + void HandleDragBegin (object sender, DragBeginArgs args) + { + Gtk.Drag.SetIconPixbuf (args.Context, image.Pixbuf, 0, 0); + focusedLiterals.Add (this); + } + + void HandleDragEnd (object sender, DragEndArgs args) + { + // Remove any literals still marked as focused, because + // the user is throwing them away. + RemoveFocusedLiterals (); + + focusedLiterals = new ArrayList(); + args.RetVal = true; + } + + private void HandleDragDataReceived (object o, EventArgs args) + { + // If focusedLiterals is not null, this is a drag of a tag that's already been placed + if (focusedLiterals.Count == 0) + { + if (TermAdded != null) + TermAdded (Parent, this); + } + else + { + if (! focusedLiterals.Contains(this)) + if (LiteralsMoved != null) + LiteralsMoved (focusedLiterals, Parent, this); + + // Unmark the literals as focused so they don't get nixed + focusedLiterals = null; + } + } + + public void HandleToggleNegatedCommand (object o, EventArgs args) + { + IsNegated = !IsNegated; + } + + public void HandleRemoveCommand (object o, EventArgs args) + { + RemoveSelf (); + } + + public void HandleAttachTagCommand (Tag t) + { + if (AttachTag != null) + AttachTag (t, Parent, this); + } + + private void HandleFocusIn (object o, EventArgs args) + { + //Console.WriteLine (tag.Name + " now has focus!"); + //(Widget as Container).Children[0].GrabFocus (); + } + + public void HandleRequireTag (object sender, EventArgs args) + { + if (RequireTag != null) + RequireTag (new Tag [] {this.Tag}); + } + + public void HandleUnRequireTag (object sender, EventArgs args) + { + if (UnRequireTag != null) + UnRequireTag (new Tag [] {this.Tag}); + } + + // TODO bind this to be proportional to the icon_view thumbnail size? + private static int thumbnail_size = 30; + + private static int overlay_size = (int) (.40 * thumbnail_size); + + private static TargetEntry [] tag_target_table = new TargetEntry [] { + new TargetEntry ("application/x-fspot-tag-query-item", 0, (uint) MainWindow.TargetType.TagQueryItem), + }; + + private static TargetEntry [] tag_dest_target_table = new TargetEntry [] { + new TargetEntry ("application/x-fspot-tags", 0, (uint) MainWindow.TargetType.TagList), + new TargetEntry ("application/x-fspot-tag-query-item", 0, (uint) MainWindow.TargetType.TagQueryItem), + }; + + private static ArrayList focusedLiterals = new ArrayList(); + private Gtk.Image image; + + private Pixbuf normal_icon; + //private EventBox widget; + private Widget widget; + private Pixbuf negated_icon; + private static Pixbuf negated_overlay; + private static Tooltips tips; + private bool isNegated = false; + + public delegate void NegatedToggleHandler (Literal group); + public event NegatedToggleHandler NegatedToggled; + + public delegate void RemovingHandler (Literal group); + public event RemovingHandler Removing; + + public delegate void RemovedHandler (Literal group); + public event RemovedHandler Removed; + + public delegate void TermAddedHandler (LogicTerm parent, Literal after); + public event TermAddedHandler TermAdded; + + public delegate void AttachTagHandler (Tag tag, LogicTerm parent, Literal after); + public event AttachTagHandler AttachTag; + + public delegate void TagRequiredHandler (Tag [] tags); + public event TagRequiredHandler RequireTag; + + public delegate void TagUnRequiredHandler (Tag [] tags); + public event TagUnRequiredHandler UnRequireTag; + + public delegate void LiteralsMovedHandler (ArrayList literals, LogicTerm parent, Literal after); + public event LiteralsMovedHandler LiteralsMoved; + } + + public class LogicWidget : HBox { + public LogicWidget (PhotoQuery query, TagStore tag_store, TagSelectionWidget selector) : base () + { + //SetFlag (WidgetFlags.NoWindow); + this.query = query; + this.tag_selection_widget = selector; + + CanFocus = true; + Sensitive = true; + + Literal.Tips = tips; + + tips.Enable (); + + rootAdd = new Gtk.EventBox (); + rootAdd.CanFocus = true; + rootAdd.DragMotion += HandleDragMotion; + rootAdd.DragDataReceived += HandleDragDataReceived; + rootAdd.DragLeave += HandleLeave; + rootAdd.Show (); + tips.SetTip (rootAdd, Catalog.GetString ("Drop a tag here to include it"), null); + + // TODO listen for tag edits, deletes + Init (); + + Gtk.Drag.DestSet (rootAdd, DestDefaults.All, tag_dest_target_table, + DragAction.Copy | DragAction.Move ); + PackEnd (rootAdd, true, true, 0); + + tag_store.TagChanged += HandleTagChanged; + tag_store.TagDeleted += HandleTagDeleted; + + Show (); + } + + private void Init () + { + rootTerm = new OrTerm (null, null); + } + + private void Preview () + { + if (sepBox == null) { + sepBox = new HBox (); + Widget sep = rootTerm.SeparatorWidget (); + if (sep != null) { + sep.Show (); + sepBox.PackStart (sep, false, false, 0); + rootAdd.Add (sepBox); + } + } + + sepBox.Show (); + } + + /** Handlers **/ + + // When the user edits a tag (it's icon, name, etc) we get called + // and update the images/text in the query as needed to reflect the changes. + private void HandleTagChanged (Tag t) + { + foreach (Literal term in rootTerm.FindByTag (t)) { + term.Update (); + } + } + + // If the user deletes a tag that is in use in the query, remove it from the query too. + private void HandleTagDeleted (Tag t) + { + foreach (Literal term in rootTerm.FindByTag (t)) { + term.RemoveSelf (); + } + } + + private void HandleDragMotion (object o, DragMotionArgs args) + { + if (!preview && rootTerm.Count > 0) { + Preview (); + preview = true; + } + } + + private void HandleLeave (object o, EventArgs args) + { + if (preview && Children.Length > 1) { + sepBox.Hide (); + preview = false; + } + } + + private void HandleLiteralsMoved (ArrayList literals, LogicTerm parent, Literal after) + { + preventUpdate = true; + foreach (Literal term in literals) { + Tag tag = term.Tag; + + // Don't listen for it to be removed since we are + // moving it. We will update when we're done. + term.Removed -= HandleRemoved; + term.RemoveSelf (); + + // Add it to where it was dropped + ArrayList groups = InsertTerm (new Tag[] {tag}, parent, after); + + if (term.IsNegated) + foreach (Literal group in groups) + group.IsNegated = true; + } + preventUpdate = false; + UpdateQuery (); + } + + private void HandleTermAdded (LogicTerm parent, Literal after) + { + InsertTerm (parent, after); + } + + private void HandleAttachTag (Tag tag, LogicTerm parent, Literal after) + { + InsertTerm (new Tag [] {tag}, parent, after); + } + + private void HandleNegated (Literal group) + { + UpdateQuery (); + } + + private void HandleRemoving (Literal term) + { + // Remove separators as needed + if (term.Parent != null) { + if (term.Parent.Count > 1) + { + if (term == term.Parent.Last) + Remove (Children[WidgetPosition (term.Widget) - 1]); + else + Remove (Children[WidgetPosition (term.Widget) + 1]); + } + else if (term.Parent.Count == 1) + { + if (term.Parent.Parent != null) { + if (term.Parent.Parent.Count > 1) { + if (term.Parent == term.Parent.Parent.Last) + Remove (Children[WidgetPosition (term.Widget) - 1]); + else + Remove (Children[WidgetPosition (term.Widget) + 1]); + } + } + } + } + + // Remove the term's widget + Remove (term.Widget); + } + + private void HandleRemoved (Literal group) + { + UpdateQuery (); + } + + private void HandleDragDataReceived (object o, DragDataReceivedArgs args) + { + InsertTerm (rootTerm, null); + + args.RetVal = true; + } + + /** Helper Functions **/ + + public void PhotoTagsChanged (Tag [] tags) + { + bool refresh_required = false; + + foreach (Tag tag in tags) { + if ((rootTerm.FindByTag (tag)).Count > 0) { + refresh_required = true; + break; + } + } + + if (refresh_required) + UpdateQuery (); + } + + // Inserts a widget into a Box at a certain index + private void InsertWidget (int index, Gtk.Widget widget) { + widget.Visible = true; + PackStart (widget, false, false, 0); + ReorderChild (widget, index); + } + + // Return the index position of a widget in this Box + private int WidgetPosition (Gtk.Widget widget) + { + for (int i = 0; i < Children.Length; i++) + if (Children[i] == widget) + return i; + + return Children.Length - 1; + } + + public bool TagIncluded (Tag tag) + { + return rootTerm.TagIncluded (tag); + } + + public bool TagRequired (Tag tag) + { + return rootTerm.TagRequired (tag); + } + + // Add a tag to the rootTerm, at the end of the Box + public void Include (Tag [] tags) + { + ArrayList new_tags = new ArrayList(tags.Length); + foreach (Tag tag in tags) { + if (! rootTerm.TagIncluded (tag)) + new_tags.Add (tag); + } + + if (new_tags.Count == 0) + return; + + tags = (Tag []) new_tags.ToArray (typeof (Tag)); + + InsertTerm (tags, rootTerm, null); + } + + public void UnInclude (Tag [] tags) + { + ArrayList new_tags = new ArrayList(tags.Length); + foreach (Tag tag in tags) { + if (rootTerm.TagIncluded (tag)) + new_tags.Add (tag); + } + + if (new_tags.Count == 0) + return; + + tags = (Tag []) new_tags.ToArray (typeof (Tag)); + + bool needsUpdate = false; + preventUpdate = true; + foreach (LogicTerm parent in rootTerm.LiteralParents ()) { + if (parent.Count == 1) { + foreach (Tag tag in tags) { + if ((parent.Last as Literal).Tag == tag) { + (parent.Last as Literal).RemoveSelf (); + needsUpdate = true; + break; + } + } + } + } + preventUpdate = false; + + if (needsUpdate) + UpdateQuery (); + } + + // AND this tag with all terms + public void Require (Tag [] tags) + { + // TODO it would be awesome if this was done by putting parentheses around + // OR terms and ANDing the result with this term. + + // Trim out tags that are already required + ArrayList new_tags = new ArrayList(tags.Length); + foreach (Tag tag in tags) { + if (! rootTerm.TagRequired (tag)) + new_tags.Add (tag); + } + + if (new_tags.Count == 0) + return; + + tags = (Tag []) new_tags.ToArray (typeof (Tag)); + + bool added = false; + preventUpdate = true; + foreach (LogicTerm parent in rootTerm.LiteralParents ()) { + // TODO logic could be broken if a term's SubTerms are a mixture + // of Literals and non-Literals + InsertTerm (tags, parent, parent.Last as Literal); + added = true; + } + + // If there were no LiteralParents to add this tag to, then add it to the rootTerm + // TODO should add the first tag in the array, + // then add the others to the first's parent (so they will be ANDed together) + if (!added) + InsertTerm (tags, rootTerm, null); + + preventUpdate = false; + + UpdateQuery (); + } + + public void UnRequire (Tag [] tags) + { + // Trim out tags that are not required + ArrayList new_tags = new ArrayList(tags.Length); + foreach (Tag tag in tags) { + if (rootTerm.TagRequired (tag)) + new_tags.Add (tag); + } + + if (new_tags.Count == 0) + return; + + tags = (Tag []) new_tags.ToArray (typeof (Tag)); + + preventUpdate = true; + foreach (LogicTerm parent in rootTerm.LiteralParents ()) { + // Don't remove if this tag is the only child of a term + if (parent.Count > 1) { + foreach (Tag tag in tags) { + ((parent.FindByTag (tag))[0] as Literal).RemoveSelf (); + } + } + } + + preventUpdate = false; + + UpdateQuery (); + } + + private void InsertTerm (LogicTerm parent, Literal after) + { + if (Literal.FocusedLiterals.Count != 0) { + HandleLiteralsMoved (Literal.FocusedLiterals, parent, after); + + // Prevent them from being removed again + Literal.FocusedLiterals = null; + } + else + InsertTerm (tag_selection_widget.TagHighlight (), parent, after); + } + + public ArrayList InsertTerm (Tag [] tags, LogicTerm parent, Literal after) + { + int position; + if (after != null) + position = WidgetPosition (after.Widget) + 1; + else + position = Children.Length - 1; + + ArrayList added = new ArrayList (); + + foreach (Tag tag in tags) { + //Console.WriteLine ("Adding tag {0}", tag.Name); + + // Don't put a tag into a LogicTerm twice + if ((parent.FindByTag (tag, true)).Count > 0) + continue; + + if (parent.Count > 0) { + Widget sep = parent.SeparatorWidget (); + + //if (sep != null) { + InsertWidget (position, sep); + position++; + //} + } + + // Encapsulate new OR terms within a new AND term of which they are the + // only member, so later other terms can be AND'd with them + // + // TODO should really see what type of term the parent is, and + // encapsulate this term in a term of the opposite type. This will + // allow the query system to be expanded to work for multiple levels much easier. + if (parent == rootTerm) { + parent = new AndTerm (rootTerm, after); + after = null; + } + + Literal term = new Literal (parent, tag, after); + term.TermAdded += HandleTermAdded; + term.LiteralsMoved += HandleLiteralsMoved; + term.AttachTag += HandleAttachTag; + term.NegatedToggled += HandleNegated; + term.Removing += HandleRemoving; + term.Removed += HandleRemoved; + term.RequireTag += Require; + term.UnRequireTag += UnRequire; + + added.Add (term); + + // Insert this widget into the appropriate place in the hbox + InsertWidget (position, term.Widget); + } + + UpdateQuery (); + + return added; + } + + public bool Cleared () + { + return rootTerm.Count == 0; + } + + // Update the query, which updates the icon_view + public void UpdateQuery () + { + if (preventUpdate) + return; + + if (rootTerm.Count == 0) { + query.ExtraCondition = null; + } else { + if (sepBox != null) + sepBox.Hide (); + + query.ExtraCondition = rootTerm.ConditionString (); + } + } + + // Clear out the query, starting afresh + public void Clear () + { + // Don't remove the last widget, because it's the rootAdd widget + // which is useful for starting the next query + int last = Children.Length - 1; + int i = 0; + foreach (Widget widget in Children) { + if (i != last) + Remove (widget); + i++; + } + + Init (); + + UpdateQuery (); + } + + private PhotoQuery query; + private TagSelectionWidget tag_selection_widget; + + private static Tooltips tips = new Tooltips (); + + private static LogicTerm rootTerm; + public static LogicTerm Root + { + get { + return rootTerm; + } + } + + private EventBox rootAdd; + private HBox sepBox; + + private bool preventUpdate = false; + private bool preview = false; + + private ArrayList widgets = new ArrayList (); + + // Drag and Drop + private static TargetEntry [] tag_dest_target_table = new TargetEntry [] { + new TargetEntry ("application/x-fspot-tags", 0, (uint) MainWindow.TargetType.TagList), + new TargetEntry ("application/x-fspot-tag-query-item", 0, (uint) MainWindow.TargetType.TagQueryItem), + }; + } +} --- /dev/null 2005-11-08 00:39:39.504664232 -0600 +++ QueryWidget.cs 2005-11-10 01:28:23.000000000 -0600 @@ -0,0 +1,115 @@ +namespace FSpot { + public class QueryWidget : Gtk.VBox { + PhotoQuery query; + Tags.LogicWidget logic_widget; + Gtk.Label label; + Gtk.HBox warning_box; + Gtk.Button clear_button; + TagSelectionWidget selector; + Gtk.Tooltips tips = new Gtk.Tooltips (); + + public QueryWidget (PhotoQuery query, Db db, TagSelectionWidget selector) + { + tips.Enable (); + + this.query = query; + query.Changed += HandleChanged; + this.selector = selector; + + Gtk.HSeparator sep = new Gtk.HSeparator (); + sep.Show (); + this.PackStart (sep, false, false, 0); + + Gtk.HBox hbox = new Gtk.HBox (); + hbox.Show (); + this.PackStart (hbox, false, false, 0); + + label = new Gtk.Label (Mono.Posix.Catalog.GetString ("Find: ")); + label.Show (); + label.Ypad = 9; + hbox.PackStart (label, false, false, 0); + + logic_widget = new Tags.LogicWidget (query, db.Tags, selector); + logic_widget.Show (); + hbox.PackStart (logic_widget, true, true, 0); + + warning_box = new Gtk.HBox (); + warning_box.PackStart (new Gtk.Label ("")); + + Gtk.Image warning_image = new Gtk.Image ("gtk-dialog-warning", Gtk.IconSize.Button); + warning_image.Show (); + warning_box.PackStart (warning_image, false, false, 0); + + Gtk.Label warning = new Gtk.Label (Mono.Posix.Catalog.GetString ("No matching images found ")); + warning_box.PackStart (warning, false, false, 0); + warning_box.ShowAll (); + warning_box.Spacing = 6; + warning_box.Visible = false; + + hbox.PackStart (warning_box); + + clear_button = new Gtk.Button (); + clear_button.Add (new Gtk.Image ("gtk-stop", Gtk.IconSize.Button)); + clear_button.Clicked += HandleClearButtonClicked; + clear_button.Relief = Gtk.ReliefStyle.None; + hbox.PackStart (clear_button, false, false, 0); + tips.SetTip (clear_button, Mono.Posix.Catalog.GetString("Clear Query"), null); + + warning_box.Visible = false; + } + + public void HandleClearButtonClicked (object sender, System.EventArgs args) + { + logic_widget.Clear (); + } + + public void HandleChanged (IBrowsableCollection collection) + { + if (logic_widget.Cleared ()) { + this.Visible = false; + Hide (); + } else { + this.Visible = true; + Show (); + } + + warning_box.Visible = (query.Count < 1); + } + + public void PhotoTagsChanged (Tag[] tags) + { + System.Console.WriteLine ("QueryWidget - tags changed"); + logic_widget.PhotoTagsChanged (tags); + } + + public void Include (Tag [] tags) + { + logic_widget.Include (tags); + } + + public void UnInclude (Tag [] tags) + { + logic_widget.UnInclude (tags); + } + + public void Require (Tag [] tags) + { + logic_widget.Require (tags); + } + + public void UnRequire (Tag [] tags) + { + logic_widget.UnRequire (tags); + } + + public bool TagIncluded (Tag tag) + { + return logic_widget.TagIncluded (tag); + } + + public bool TagRequired (Tag tag) + { + return logic_widget.TagRequired (tag); + } + } +} --- /dev/null 2005-11-08 00:39:39.504664232 -0600 +++ PopupManager.cs 2005-11-10 02:25:20.000000000 -0600 @@ -0,0 +1,98 @@ +using System; + +public class PopupManager { + private AbstractPopup popup; + + public PopupManager (AbstractPopup popup) + { + this.popup = popup; + + // Listen for context-menu events + popup.EventSource.ButtonPressEvent += this.MousePopup; + popup.EventSource.PopupMenu += this.NonMousePopup; + } + + public void MousePopup (object sender, Gtk.ButtonPressEventArgs args) + { + if (args.Event.Type == Gdk.EventType.ButtonPress && args.Event.Button == 3) { + popup.X = (int) args.Event.X; + popup.Y = (int) args.Event.Y; + + args.RetVal = Popup (null); + } + + args.RetVal = false; + } + + public void NonMousePopup (object sender, Gtk.PopupMenuArgs args) + { + args.RetVal = Popup (popup.Positioner); + } + + private bool Popup (Gtk.MenuPositionFunc positioner) + { + // FIXME this is a hack to handle the --view case for the time being. + if (MainWindow.Toplevel == null) { + return false; + } + + Gtk.Menu popup_menu = new Gtk.Menu (); + popup.Populate (popup_menu); + popup_menu.Popup (null, null, positioner, IntPtr.Zero, 0, Gtk.Global.CurrentEventTime); + + return true; + } + + /*public void WidgetCentroidPosition (Gtk.Menu menu, out int x, out int y, out bool push_in) + { + // Position the menu in the middle of the focused widget + MainWindow.Toplevel.GetWidgetPosition(widget, out x, out y); + + x += widget.Allocation.Width / 2; + y += widget.Allocation.Height / 2; + + push_in = false; + }*/ +} + +public abstract class AbstractPopup { + private int x, y; + private Gtk.Widget event_source; + + public AbstractPopup (Gtk.Widget event_source) + { + this.event_source = event_source; + } + + public int X { + get { return x; } + set { x = value; } + } + + public int Y { + get { return y; } + set { y = value; } + } + + public Gtk.Widget EventSource { + get { return event_source; } + set { event_source = value; } + } + + // Populate the popup menu + public abstract void Populate (Gtk.Menu menu); + + // Called by popup_menu.Popup to find out where to place the menu + public virtual void Positioner (Gtk.Menu menu, out int x, out int y, out bool push_in) + { + MainWindow.Toplevel.GetWidgetPosition(EventSource, out x, out y); + + x += EventSource.Allocation.Width / 2; + y += EventSource.Allocation.Height / 2; + + push_in = true; + } + + // Gets the object associated with the currently saved x, y coordinates + protected abstract object GetObject (); +}
Attachment:
f-spot-not.png
Description: PNG image