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