[tracker/tracker-0.12: 186/202] tracker-needle: Added tracker-tags-view to edit tags as GtkVBox
- From: Martyn James Russell <mr src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [tracker/tracker-0.12: 186/202] tracker-needle: Added tracker-tags-view to edit tags as GtkVBox
- Date: Thu, 6 Oct 2011 18:04:05 +0000 (UTC)
commit fd7167166c3419eea5476f956142339f48b243b0
Author: Martyn Russell <martyn lanedo com>
Date: Tue Oct 4 18:52:53 2011 +0100
tracker-needle: Added tracker-tags-view to edit tags as GtkVBox
Made available through right click on hits
src/tracker-needle/Makefile.am | 17 +-
src/tracker-needle/tracker-needle.ui | 141 ++++++-
src/tracker-needle/tracker-needle.vala | 4 +-
src/tracker-needle/tracker-taglist.vala | 2 +-
src/tracker-needle/tracker-tags-view.vala | 734 +++++++++++++++++++++++++++++
src/tracker-needle/tracker-view.vala | 52 ++-
6 files changed, 917 insertions(+), 33 deletions(-)
---
diff --git a/src/tracker-needle/Makefile.am b/src/tracker-needle/Makefile.am
index 5dbc7eb..0b85ee9 100644
--- a/src/tracker-needle/Makefile.am
+++ b/src/tracker-needle/Makefile.am
@@ -24,14 +24,15 @@ LDADD = \
$(BUILD_LIBS) \
$(TRACKER_NEEDLE_LIBS)
-tracker_needle_SOURCES = \
- tracker-history.vala \
- tracker-query.vala \
- tracker-result-store.vala \
- tracker-stats.vala \
- tracker-taglist.vala \
- tracker-utils.vala \
- tracker-needle.vala \
+tracker_needle_SOURCES = \
+ tracker-history.vala \
+ tracker-query.vala \
+ tracker-result-store.vala \
+ tracker-stats.vala \
+ tracker-taglist.vala \
+ tracker-tags-view.vala \
+ tracker-utils.vala \
+ tracker-needle.vala \
tracker-view.vala
@INTLTOOL_DESKTOP_RULE@
diff --git a/src/tracker-needle/tracker-needle.ui b/src/tracker-needle/tracker-needle.ui
index f0f9142..db73bfe 100644
--- a/src/tracker-needle/tracker-needle.ui
+++ b/src/tracker-needle/tracker-needle.ui
@@ -1,12 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
- <!-- interface-requires gtk+ 2.12 -->
+ <requires lib="gtk+" version="2.24"/>
+ <!-- interface-naming-policy toplevel-contextual -->
<object class="GtkListStore" id="liststore_search">
<columns>
<!-- column-name text -->
<column type="gchararray"/>
</columns>
</object>
+ <object class="GtkVBox" id="vbox_tags">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label_tag">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Set the tags you want to associate with the %d selected items:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">fill</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkEntry" id="entry_tag">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="invisible_char">â</property>
+ <property name="activates_default">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <signal name="activate" handler="tracker_tags_view_entry_tag_activate_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="button_add">
+ <property name="label">gtk-add</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_remove">
+ <property name="label">gtk-remove</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow_tags">
+ <property name="width_request">200</property>
+ <property name="height_request">300</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="treeview_tags">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="headers_clickable">False</property>
+ <property name="enable_search">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
<object class="GtkWindow" id="window_needle">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Desktop Search</property>
@@ -113,6 +237,8 @@
</child>
<child>
<object class="GtkRadioToolButton" id="toolbutton_find_in_all">
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-select-all</property>
<property name="active">True</property>
@@ -164,17 +290,16 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="yscale">0</property>
- <property name="left_padding">4</property>
- <property name="right_padding">4</property>
+ <property name="left_padding">6</property>
+ <property name="right_padding">6</property>
<child>
- <object class="GtkComboBoxText" id="comboboxtext_search">
+ <object class="GtkComboBox" id="combobox_search">
<property name="visible">True</property>
- <property name="can_focus">True</property>
+ <property name="can_focus">False</property>
<property name="has_focus">True</property>
- <property name="has_entry">True</property>
<property name="model">liststore_search</property>
- <accelerator key="s" signal="grab-focus" modifiers="GDK_CONTROL_MASK"/>
- <accelerator key="f" signal="grab-focus" modifiers="GDK_CONTROL_MASK"/>
+ <property name="has_entry">True</property>
+ <property name="entry_text_column">0</property>
</object>
</child>
</object>
diff --git a/src/tracker-needle/tracker-needle.vala b/src/tracker-needle/tracker-needle.vala
index 24a25e8..2655bf1 100644
--- a/src/tracker-needle/tracker-needle.vala
+++ b/src/tracker-needle/tracker-needle.vala
@@ -37,7 +37,7 @@ public class Tracker.Needle {
private ToggleToolButton find_in_titles;
private ToggleToolButton find_in_all;
private ToolItem search_entry;
- private ComboBoxText search_list;
+ private ComboBox search_list;
private Entry search;
private Spinner spinner;
private ToolItem spinner_shell;
@@ -270,7 +270,7 @@ public class Tracker.Needle {
find_in_all.toggled.connect (find_in_toggled);
search_entry = builder.get_object ("toolitem_search_entry") as ToolItem;
- search_list = builder.get_object ("comboboxtext_search") as ComboBoxText;
+ search_list = builder.get_object ("combobox_search") as ComboBox;
search = search_list.get_child () as Entry;
search.changed.connect (search_changed);
search.activate.connect (search_activated);
diff --git a/src/tracker-needle/tracker-taglist.vala b/src/tracker-needle/tracker-taglist.vala
index c5c9796..6f8c328 100644
--- a/src/tracker-needle/tracker-taglist.vala
+++ b/src/tracker-needle/tracker-taglist.vala
@@ -20,7 +20,7 @@
using Gtk;
public class Tracker.TagList : ScrolledWindow {
- static Sparql.Connection connection;
+ private static Sparql.Connection connection;
private TreeView treeview;
private ListStore store;
private int offset;
diff --git a/src/tracker-needle/tracker-tags-view.vala b/src/tracker-needle/tracker-tags-view.vala
new file mode 100644
index 0000000..ad73c9f
--- /dev/null
+++ b/src/tracker-needle/tracker-tags-view.vala
@@ -0,0 +1,734 @@
+/*
+ * Copyright (C) 2011, Martyn Russell <martyn lanedo com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+using Gtk;
+using Tracker;
+
+private class TagData {
+ public TrackerTagsView tv;
+ public Cancellable cancellable;
+ public string tag_id;
+ public TreeIter iter;
+ public int items;
+ public bool update;
+ public bool selected;
+
+ public TagData (string? _tag_id, TreeIter? _iter, bool _update, bool _selected, int _items, TrackerTagsView _tv) {
+ debug ("Creating tag data");
+
+ tv = _tv;
+ cancellable = new Cancellable ();
+ tag_id = _tag_id;
+
+ if (_iter != null) {
+ iter = _iter;
+ }
+
+ items = _items;
+ update = _update;
+ selected = _selected;
+ }
+
+ ~TagData () {
+ if (cancellable != null) {
+ cancellable.cancel ();
+ cancellable = null;
+ }
+ }
+}
+
+public class TrackerTagsView : VBox {
+ private Sparql.Connection connection;
+ private Cancellable cancellable;
+
+ private List<TagData> tag_data_requests;
+ private List<string> files;
+
+ private ListStore store;
+
+ private const string UI_FILE = "tracker-needle.ui";
+
+ private VBox vbox;
+ private Label label;
+ private Entry entry;
+ private Button button_add;
+ private Button button_remove;
+ private ScrolledWindow scrolled_window;
+ private TreeView view;
+
+ enum Col {
+ SELECTION,
+ TAG_ID,
+ TAG_NAME,
+ TAG_COUNT,
+ TAG_COUNT_VALUE,
+ N_COLUMNS
+ }
+
+ enum Selection {
+ INCONSISTENT = -1,
+ FALSE = 0,
+ TRUE = 1
+ }
+
+ public TrackerTagsView (List<string> _files) requires (_files != null) {
+ try {
+ connection = Sparql.Connection.get ();
+ } catch (GLib.Error e) {
+ warning ("Could not get Sparql connection: %s", e.message);
+ }
+
+ files = _files.copy ();
+
+ cancellable = new Cancellable ();
+
+ store = new ListStore (Col.N_COLUMNS,
+ typeof (int), /* Selection type */
+ typeof (string), /* Tag ID */
+ typeof (string), /* Tag Name */
+ typeof (string), /* Tag Count String */
+ typeof (int)); /* Tag Count */
+
+ create_ui ();
+ }
+
+ ~TrackerTagsView () {
+ if (cancellable != null) {
+ cancellable.cancel ();
+ cancellable = null;
+ }
+
+ if (files != null) {
+ foreach (string url in files) {
+ url = null;
+ }
+
+ files = null;
+ }
+
+ if (tag_data_requests != null) {
+ foreach (TagData td in tag_data_requests) {
+ td = null;
+ };
+
+ tag_data_requests = null;
+ }
+ }
+
+ private void show_error_dialog (Error e) {
+ string str = e.message != null ? e.message : _("No error was given");
+
+ var msg = new MessageDialog (null,
+ DialogFlags.MODAL,
+ MessageType.ERROR,
+ ButtonsType.CLOSE,
+ "%s",
+ str);
+ msg.run ();
+ }
+
+ [CCode (instance_pos = -1)]
+ public void button_remove_clicked_cb (Button source) {
+ debug ("Remove clicked");
+
+ TreeIter iter;
+ TreeModel model;
+
+ TreeSelection selection = view.get_selection ();
+
+ if (selection.get_selected (out model, out iter)) {
+ string id;
+
+ model.get (iter, Col.TAG_ID, out id, -1);
+
+ TagData td = new TagData (id, iter, false, true, 1, this);
+ tag_data_requests.prepend (td);
+
+ remove_tag (td);
+ }
+ }
+
+ [CCode (instance_pos = -1)]
+ public void button_add_clicked_cb (Button source) {
+ debug ("Add clicked");
+ unowned string tag = entry.get_text ();
+ add_tag (tag);
+ }
+
+ [CCode (instance_pos = -1)]
+ public void entry_tag_activated_cb (Entry source) {
+ debug ("Entry activated");
+ ((Widget) button_add).activate ();
+ }
+
+ [CCode (instance_pos = -1)]
+ public void entry_tag_changed_cb (Editable source) {
+ debug ("Entry changed");
+
+ unowned string tag = entry.get_text ();
+ TreeIter iter;
+
+ if (find_tag (tag, out iter)) {
+ ((Widget) button_add).set_sensitive (false);
+ } else {
+ ((Widget) button_add).set_sensitive ((tag != null && tag != ""));
+ }
+ }
+
+ [CCode (instance_pos = -1)]
+ public void treeview_tags_cell_toggled_cb (CellRendererToggle source, string path_string) {
+ debug ("Treeview row cell toggled");
+ TreePath path = new TreePath.from_string (path_string);
+ model_toggle_row (path);
+ }
+
+ [CCode (instance_pos = -1)]
+ public void treeview_tags_row_selected_cb (TreeSelection selection) {
+ debug ("Treeview row selected");
+
+ TreeIter iter;
+ TreeModel model;
+
+ if (selection.get_selected (out model, out iter)) {
+ button_remove.set_sensitive (true);
+ } else {
+ button_remove.set_sensitive (false);
+ }
+ }
+
+ [CCode (instance_pos = -1)]
+ public void treeview_tags_row_activated_cb (TreeView source, TreePath path, TreeViewColumn column) {
+ debug ("Treeview row activated");
+ model_toggle_row (path);
+ }
+
+ [CCode (instance_pos = -1)]
+ private void treeview_tags_toggle_cell_data_func (Gtk.CellLayout layout, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) {
+ int selection;
+
+ model.get (iter, Col.SELECTION, out selection, -1);
+ ((Gtk.CellRendererToggle) cell).set_active (selection == Selection.TRUE);
+ ((Gtk.CellRendererToggle) cell).inconsistent = (selection == Selection.INCONSISTENT);
+ }
+
+ private void create_ui () {
+ var builder = new Gtk.Builder ();
+
+ try {
+ debug ("Trying to use UI file:'%s'", SRCDIR + UI_FILE);
+ builder.add_from_file (SRCDIR + UI_FILE);
+ } catch (GLib.Error e) {
+ //now the install location
+ try {
+ debug ("Trying to use UI file:'%s'", UIDIR + UI_FILE);
+ builder.add_from_file (UIDIR + UI_FILE);
+ } catch (GLib.Error e) {
+ var msg = new MessageDialog (null,
+ DialogFlags.MODAL,
+ MessageType.ERROR,
+ ButtonsType.CANCEL,
+ "Failed to load UI file, %s\n",
+ e.message);
+ msg.run ();
+ Gtk.main_quit();
+ }
+ }
+
+ // Get widgets from .ui file
+ vbox = builder.get_object ("vbox_tags") as VBox;
+ label = builder.get_object ("label_tag") as Label;
+ entry = builder.get_object ("entry_tag") as Entry;
+ button_add = builder.get_object ("button_add") as Button;
+ button_remove = builder.get_object ("button_remove") as Button;
+ scrolled_window = builder.get_object ("scrolled_window_tags") as ScrolledWindow;
+ view = builder.get_object ("treeview_tags") as TreeView;
+
+ // Set label based on files selected
+ string str = dngettext (null,
+ "_Set the tags you want to associate with the %d selected item:",
+ "_Set the tags you want to associate with the %d selected items:",
+ files.length ()).printf (files.length ());
+ label.set_text_with_mnemonic (str);
+
+ // Set up signal handlers (didn't work from glade)
+ ((Editable) entry).changed.connect (entry_tag_changed_cb);
+ button_add.clicked.connect (button_add_clicked_cb);
+ button_remove.clicked.connect (button_remove_clicked_cb);
+
+ // Set up treeview
+ Gtk.TreeViewColumn col;
+ Gtk.CellRenderer renderer;
+
+ // List column: Tag
+ renderer = new CellRendererToggle ();
+ renderer.xpad = 5;
+ renderer.ypad = 5;
+ ((CellRendererToggle) renderer).toggled.connect (treeview_tags_cell_toggled_cb);
+ ((CellRendererToggle) renderer).set_radio (false);
+
+ col = new Gtk.TreeViewColumn ();
+ col.set_title ("-");
+ col.set_resizable (false);
+ col.set_sizing (Gtk.TreeViewColumnSizing.FIXED);
+ col.set_fixed_width (50);
+ col.pack_start (renderer, false);
+ col.set_cell_data_func (renderer, treeview_tags_toggle_cell_data_func);
+ view.append_column (col);
+
+ // List column: Name
+ renderer = new CellRendererText ();
+ renderer.xpad = 5;
+ renderer.ypad = 5;
+ ((CellRendererText) renderer).ellipsize = Pango.EllipsizeMode.END;
+ ((CellRendererText) renderer).ellipsize_set = true;
+
+ col = new Gtk.TreeViewColumn ();
+ col.set_title (_("Name"));
+ col.set_resizable (true);
+ col.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
+ col.set_expand (true);
+ col.pack_start (renderer, true);
+ col.add_attribute (renderer, "text", Col.TAG_NAME);
+
+ view.append_column (col);
+
+ // List coumnn: Count
+ renderer = new CellRendererText ();
+ renderer.xpad = 5;
+ renderer.ypad = 5;
+
+ col = new Gtk.TreeViewColumn ();
+ col.set_title ("-");
+ col.set_resizable (false);
+ col.set_sizing (Gtk.TreeViewColumnSizing.FIXED);
+ col.set_fixed_width (50);
+ col.pack_end (renderer, false);
+ col.add_attribute (renderer, "text", Col.TAG_COUNT);
+
+ view.append_column (col);
+
+ // Selection signals
+ var selection = view.get_selection ();
+ selection.changed.connect (treeview_tags_row_selected_cb);
+
+ // Model details
+ TreeModel model = store as TreeModel;
+ TreeSortable sortable = model as TreeSortable;
+
+ view.set_model (model);
+ view.row_activated.connect (treeview_tags_row_activated_cb);
+ sortable.set_sort_column_id (2, SortType.ASCENDING);
+
+ // Add vbox to this widget's vbox
+ base.pack_start (vbox, true, true, 0);
+
+ query_tags ();
+ }
+
+ private async void model_toggle_row (TreePath path) {
+ TreeModel model;
+ TreeIter iter;
+ string id, tag;
+ int selection;
+
+ model = view.get_model ();
+
+ if (model.get_iter (out iter, path) == false) {
+ return;
+ }
+
+ model.get (iter,
+ Col.SELECTION, out selection,
+ Col.TAG_ID, out id,
+ Col.TAG_NAME, out tag,
+ -1);
+
+ int new_value = selection == Selection.FALSE ? Selection.TRUE : Selection.FALSE;
+
+ string tag_escaped = sparql_get_escaped_string (tag);
+ string filter = sparql_get_filter_string (null);
+ string query = null;
+
+ TagData td;
+
+ // NOTE: Was if (selection) ...
+ if (new_value != Selection.FALSE) {
+ // NB: ?f is used in filter.
+ query = "INSERT {
+ ?urn nao:hasTag ?label
+ } WHERE {
+ ?urn nie:url ?f .
+ ?label nao:prefLabel %s .
+ %s
+ }".printf (tag_escaped, filter);
+ } else {
+ // NB: ?f is used in filter.
+ query = "DELETE {
+ ?urn nao:hasTag ?label
+ } WHERE {
+ ?urn nie:url ?f .
+ ?label nao:prefLabel %s .
+ %s
+ }".printf (tag_escaped, filter);
+
+ /* Check if there are any files left with this tag and
+ * remove tag if not.
+ */
+ td = new TagData (id, iter, false, true, 1, this);
+ tag_data_requests.prepend (td);
+
+ query_files_for_tag_id (td);
+ }
+
+ filter = null;
+ tag_escaped = null;
+
+ if (connection == null) {
+ warning ("Can't update tags, no SPARQL connection available");
+ return;
+ }
+
+ debug ("Updating tags for uris");
+
+ entry.set_sensitive (false);
+
+ td = new TagData (id, iter, true, (new_value != Selection.FALSE), 1, this);
+ tag_data_requests.prepend (td);
+
+ try {
+ yield connection.update_async (query, Priority.DEFAULT, td.cancellable);
+
+ debug ("Updated tags");
+ update_tag_data (td);
+
+ entry.set_text ("");
+ } catch (GLib.Error e) {
+ warning ("Could not run Sparql update query: %s", e.message);
+ show_error_dialog (e);
+ }
+
+ tag_data_requests.remove (td);
+ td = null;
+
+ entry.set_sensitive (true);
+ }
+
+ private bool find_tag (string tag, out TreeIter iter) {
+ if (tag == null || tag == "") {
+ return false;
+ }
+
+ TreeModel model = view.get_model ();
+ TreeIter found_iter = { 0 };
+ bool found = false;
+
+ model.foreach ((model, path, foreach_iter) => {
+ string foreach_tag;
+
+ model.get (foreach_iter, Col.TAG_NAME, out foreach_tag, -1);
+
+ if (foreach_tag != null && foreach_tag == tag) {
+ found = true;
+ found_iter = foreach_iter;
+ return true;
+ }
+
+ return false;
+ });
+
+ if (found == true) {
+ iter = found_iter;
+ return true;
+ }
+
+ return false;
+ }
+
+ private async void remove_tag (TagData td) {
+ if (connection == null) {
+ warning ("Can't remove tag '%s', no SPARQL connection available", td.tag_id);
+ tag_data_requests.remove (td);
+ td = null;
+ return;
+ }
+
+ string query = "DELETE { <%s> a rdfs:Resource }".printf (td.tag_id);
+
+ try {
+ yield connection.update_async (query, Priority.DEFAULT, td.cancellable);
+
+ debug ("Tag removed");
+ store.remove (td.iter);
+ } catch (GLib.Error e) {
+ warning ("Could not run Sparql update query: %s", e.message);
+ show_error_dialog (e);
+ }
+
+ tag_data_requests.remove (td);
+ td = null;
+ }
+
+ private async void add_tag (string tag) {
+ string query = null;
+
+ if (connection == null) {
+ warning ("Can't add tag '%s', no SPARQL connection available", tag);
+ return;
+ }
+
+ entry.set_sensitive (false);
+
+ if (files.length () > 0) {
+ query = "";
+
+ string filter = sparql_get_filter_string (null);
+ string tag_escaped = sparql_get_escaped_string (tag);
+
+ foreach (string url in files) {
+ query += "INSERT {
+ _:file a nie:DataObject ;
+ nie:url '%s'
+ } WHERE {
+ OPTIONAL {
+ ?file a nie:DataObject ;
+ nie:url '%s'
+ } .
+ FILTER (!bound(?file))
+ }".printf (url, url);
+ }
+
+ query += "INSERT {
+ _:tag a nao:Tag;
+ nao:prefLabel %s .
+ } WHERE {
+ OPTIONAL {
+ ?tag a nao:Tag ;
+ nao:prefLabel %s
+ } .
+ FILTER (!bound(?tag))
+ }
+ INSERT {
+ ?urn nao:hasTag ?label
+ } WHERE {
+ ?urn nie:url ?f .
+ ?label nao:prefLabel %s
+ %s
+ }".printf (tag_escaped, tag_escaped, tag_escaped, filter);
+ } else {
+ string tag_label_escaped = sparql_get_escaped_string (tag);
+
+ query = "INSERT {
+ _:tag a nao:Tag ;
+ nao:prefLabel %s .
+ } WHERE {
+ OPTIONAL {
+ ?tag a nao:Tag ;
+ nao:prefLabel %s
+ } .
+ FILTER (!bound(?tag))
+ }".printf (tag_label_escaped, tag_label_escaped);
+ }
+
+ TagData td = new TagData (null, null, false, true, (int) files.length (), this);
+ tag_data_requests.prepend (td);
+
+ try {
+ yield connection.update_async (query, Priority.DEFAULT, td.cancellable);
+
+ debug ("Updated tags");
+ update_tag_data (td);
+
+ // Only do this on success
+ entry.set_text ("");
+ } catch (GLib.Error e) {
+ warning ("Could not run Sparql update query: %s", e.message);
+ show_error_dialog (e);
+ }
+
+ tag_data_requests.remove (td);
+ td = null;
+
+ entry.set_sensitive (true);
+ }
+
+ private void update_tag_data (TagData td) {
+ unowned string tag = entry.get_text ();
+
+ if (td.update == false) {
+ TreeIter iter;
+
+ debug ("Setting tag selection state to ON (new)");
+
+ store.append (out iter);
+ store.set (iter,
+ Col.TAG_ID, td.tag_id,
+ Col.TAG_NAME, tag,
+ Col.TAG_COUNT, "%d".printf (td.items),
+ Col.TAG_COUNT_VALUE, td.items,
+ Col.SELECTION, Selection.TRUE,
+ -1);
+ } else if (td.selected == true) {
+ debug ("Setting tag selection state to ON");
+
+ store.set (td.iter, Col.SELECTION, Selection.TRUE, -1);
+
+ tag_data_requests.prepend (td);
+ query_files_for_tag_id (td);
+ } else {
+ debug ("Setting tag selection state to FALSE");
+
+ store.set (td.iter, Col.SELECTION, Selection.FALSE, -1);
+
+ tag_data_requests.prepend (td);
+ query_files_for_tag_id (td);
+ }
+ }
+
+ private async void query_tags () {
+ // Get all tags
+ string query = "SELECT ?urn ?label WHERE { ?urn a nao:Tag ; nao:prefLabel ?label . } ORDER BY ?label";
+
+ debug ("Clearing tags in store");
+ store.clear ();
+
+ try {
+ Sparql.Cursor cursor = yield connection.query_async (query, null);
+
+ while (yield cursor.next_async ()) {
+
+ debug ("Adding all tags...");
+
+ unowned string id = cursor.get_string (0);
+ unowned string label = cursor.get_string (1);
+
+ debug (" Adding tag id:'%s' with label:'%s' to store", id, label);
+
+ TreeIter iter;
+ store.append (out iter);
+
+ store.set (iter,
+ Col.TAG_ID, id,
+ Col.TAG_NAME, label,
+ Col.SELECTION, Selection.FALSE,
+ -1);
+
+ TagData td = new TagData (id, iter, false, true, 1, this);
+ tag_data_requests.prepend (td);
+
+ query_files_for_tag_id (td);
+ }
+ } catch (GLib.Error e) {
+ warning ("Could not run Sparql query: %s", e.message);
+ show_error_dialog (e);
+ }
+ }
+
+ private async void query_files_for_tag_id (TagData td) {
+ if (connection == null) {
+ warning ("Can't query files for tag id '%s', no SPARQL connection available", td.tag_id);
+ tag_data_requests.remove (td);
+ td = null;
+ return;
+ }
+
+ string query = "SELECT ?url WHERE { ?urn a rdfs:Resource ; nie:url ?url ; nao:hasTag <%s> . }".printf (td.tag_id);
+
+ try {
+ Sparql.Cursor cursor = yield connection.query_async (query, td.cancellable);
+
+ uint has_tag_in_selection = 0;
+ uint files_with_tag = 0;
+ uint files_selected = files.length ();
+
+ while (yield cursor.next_async ()) {
+ files_with_tag++;
+
+ foreach (string url in files) {
+ unowned string url_returned = cursor.get_string (0);
+
+ debug ("--> '%s' vs '%s'", url, url_returned);
+
+ if (url_returned == null) {
+ continue;
+ }
+
+ if (url_returned == url) {
+ has_tag_in_selection++;
+ break;
+ }
+ }
+ }
+
+ debug ("Querying files with tag, in selection:%ld, in total:%ld, selected:%ld",
+ has_tag_in_selection, files_with_tag, files_selected);
+
+ if (has_tag_in_selection == 0) {
+ store.set (td.iter, Col.SELECTION, Selection.FALSE, -1);
+ } else if (files_selected != has_tag_in_selection) {
+ store.set (td.iter, Col.SELECTION, Selection.INCONSISTENT, -1);
+ } else {
+ store.set (td.iter, Col.SELECTION, Selection.TRUE, -1);
+ }
+
+ string str = "%ld".printf (files_with_tag);
+ store.set (td.iter, Col.TAG_COUNT, str, Col.TAG_COUNT_VALUE, files_with_tag, -1);
+
+ debug ("Tags for file updated");
+ } catch (GLib.Error e) {
+ warning ("Could not run Sparql query: %s", e.message);
+ show_error_dialog (e);
+ }
+
+ tag_data_requests.remove (td);
+ td = null;
+ }
+
+ private string sparql_get_filter_string (string? tag) requires (files != null) {
+ string filter = "FILTER (";
+
+ if (tag != null && tag != "") {
+ filter += "(";
+ }
+
+ bool first = true;
+
+ foreach (string url in files) {
+ if (!first) {
+ filter += " || ";
+ }
+
+ filter += "?f = \"%s\"".printf (url);
+ first = false;
+ }
+
+ if (tag != null && tag != "") {
+ filter += ") && ?t = <%s>".printf (tag);
+ }
+
+ filter += ")";
+
+ return filter;
+ }
+
+ private string sparql_get_escaped_string (string str) requires (str != null) {
+ string escaped = Sparql.escape_string (str);
+ return "\"%s\"".printf (escaped);
+ }
+}
+
diff --git a/src/tracker-needle/tracker-view.vala b/src/tracker-needle/tracker-view.vala
index 0c51af3..f9ff95d 100644
--- a/src/tracker-needle/tracker-view.vala
+++ b/src/tracker-needle/tracker-view.vala
@@ -452,14 +452,8 @@ public class Tracker.View : ScrolledWindow {
var separator = new SeparatorMenuItem ();
context_menu.append (separator);
- item = new MenuItem.with_mnemonic (_("_Add Tag..."));
- item.activate.connect (context_menu_tag_add_clicked);
- item.sensitive = false;
- context_menu.append (item);
-
- item = new MenuItem.with_mnemonic (_("_Remove Tag..."));
- item.activate.connect (context_menu_tag_remove_clicked);
- item.sensitive = false;
+ item = new MenuItem.with_mnemonic (_("_Tags..."));
+ item.activate.connect (context_menu_tags_clicked);
context_menu.append (item);
context_menu.show_all ();
@@ -522,12 +516,42 @@ public class Tracker.View : ScrolledWindow {
tracker_model_launch_selected_parent_dir (model, path, 1);
}
- private void context_menu_tag_add_clicked () {
- warning ("Not yet implemented");
- }
+ private void context_menu_tags_clicked () {
+ TreeModel model = get_model ();
+ TreePath path = get_selected_path ();
+ TreeIter iter;
+ model.get_iter (out iter, path);
- private void context_menu_tag_remove_clicked () {
- warning ("Not yet implemented");
- }
+ weak string uri;
+ model.get (iter, 1, out uri);
+ if (uri == null) {
+ return;
+ }
+
+ debug ("Showing tags dialog for uri:'%s'", uri);
+
+ // Create dialog and embed vbox.
+ Dialog dialog = new Dialog.with_buttons (_("Tags"),
+ (Window) this.get_toplevel (),
+ DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT,
+ Stock.CLOSE, ResponseType.CLOSE,
+ null);
+ dialog.set_default_size (400, 300);
+ dialog.border_width = 12;
+ dialog.response.connect (() => {
+ dialog.destroy ();
+ });
+
+ List<string> files = null;
+ files.prepend (uri);
+ VBox vbox = new TrackerTagsView (files);
+
+ var content = dialog.get_content_area () as Box;
+ content.pack_start (vbox, true, true, 6);
+ content.spacing = 10;
+
+ ((Widget) dialog).show_all ();
+ dialog.run ();
+ }
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]