summaryrefslogtreecommitdiff
path: root/src/tags
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2014-07-23 09:06:59 +0200
committerJörg Frings-Fürst <debian@jff-webhosting.net>2014-07-23 09:06:59 +0200
commit4ea2cc3bd4a7d9b1c54a9d33e6a1cf82e7c8c21d (patch)
treed2e54377d14d604356c86862a326f64ae64dadd6 /src/tags
Imported Upstream version 0.18.1upstream/0.18.1
Diffstat (limited to 'src/tags')
-rw-r--r--src/tags/Branch.vala310
-rw-r--r--src/tags/HierarchicalTagIndex.vala90
-rw-r--r--src/tags/HierarchicalTagUtilities.vala184
-rw-r--r--src/tags/TagPage.vala125
-rw-r--r--src/tags/Tags.vala18
-rw-r--r--src/tags/mk/tags.mk32
6 files changed, 759 insertions, 0 deletions
diff --git a/src/tags/Branch.vala b/src/tags/Branch.vala
new file mode 100644
index 0000000..71bf424
--- /dev/null
+++ b/src/tags/Branch.vala
@@ -0,0 +1,310 @@
+/* Copyright 2011-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+public class Tags.Branch : Sidebar.Branch {
+ private Gee.HashMap<Tag, Tags.SidebarEntry> entry_map = new Gee.HashMap<Tag, Tags.SidebarEntry>();
+
+ public Branch() {
+ base (new Tags.Grouping(),
+ Sidebar.Branch.Options.HIDE_IF_EMPTY
+ | Sidebar.Branch.Options.AUTO_OPEN_ON_NEW_CHILD
+ | Sidebar.Branch.Options.STARTUP_OPEN_GROUPING,
+ comparator);
+
+ // seed the branch with existing tags
+ on_tags_added_removed(Tag.global.get_all(), null);
+
+ // monitor collection for future events
+ Tag.global.contents_altered.connect(on_tags_added_removed);
+ Tag.global.items_altered.connect(on_tags_altered);
+ }
+
+ ~Branch() {
+ Tag.global.contents_altered.disconnect(on_tags_added_removed);
+ Tag.global.items_altered.disconnect(on_tags_altered);
+ }
+
+ public Tags.SidebarEntry? get_entry_for_tag(Tag tag) {
+ return entry_map.get(tag);
+ }
+
+ private static int comparator(Sidebar.Entry a, Sidebar.Entry b) {
+ if (a == b)
+ return 0;
+
+ return Tag.compare_names(((Tags.SidebarEntry) a).for_tag(),
+ ((Tags.SidebarEntry) b).for_tag());
+ }
+
+ private void on_tags_added_removed(Gee.Iterable<DataObject>? added_raw, Gee.Iterable<DataObject>? removed) {
+ // Store the tag whose page we'll eventually want to go to,
+ // since this is lost when a tag is reparented (pruning a currently-
+ // highlighted entry from the tree causes the highlight to go to the library,
+ // and reparenting requires pruning the old location (along with adding the new one)).
+ Tag? restore_point = null;
+
+ if (added_raw != null) {
+ // prepare a collection of tags guaranteed to be sorted; this is critical for
+ // hierarchical tags since it ensures that parent tags must be encountered
+ // before their children
+ Gee.SortedSet<Tag> added = new Gee.TreeSet<Tag>(Tag.compare_names);
+ foreach (DataObject object in added_raw) {
+ Tag tag = (Tag) object;
+ added.add(tag);
+ }
+
+ foreach (Tag tag in added) {
+ // ensure that all parent tags of this tag (if any) already have sidebar
+ // entries
+ Tag? parent_tag = tag.get_hierarchical_parent();
+ while (parent_tag != null) {
+ if (!entry_map.has_key(parent_tag)) {
+ Tags.SidebarEntry parent_entry = new Tags.SidebarEntry(parent_tag);
+ entry_map.set(parent_tag, parent_entry);
+ }
+
+ parent_tag = parent_tag.get_hierarchical_parent();
+
+ }
+
+ Tags.SidebarEntry entry = new Tags.SidebarEntry(tag);
+ entry_map.set(tag, entry);
+
+ parent_tag = tag.get_hierarchical_parent();
+ if (parent_tag != null) {
+ Tags.SidebarEntry parent_entry = entry_map.get(parent_tag);
+ graft(parent_entry, entry);
+ } else {
+ graft(get_root(), entry);
+ }
+
+ // Save the most-recently-processed on tag. During a reparenting,
+ // this will be the only tag processed.
+ restore_point = tag;
+ }
+ }
+
+ if (removed != null) {
+ foreach (DataObject object in removed) {
+ Tag tag = (Tag) object;
+
+ Tags.SidebarEntry? entry = entry_map.get(tag);
+ assert(entry != null);
+
+ bool is_removed = entry_map.unset(tag);
+ assert(is_removed);
+
+ prune(entry);
+ }
+ }
+ }
+
+ private void on_tags_altered(Gee.Map<DataObject, Alteration> altered) {
+ foreach (DataObject object in altered.keys) {
+ if (!altered.get(object).has_detail("metadata", "name"))
+ continue;
+
+ Tag tag = (Tag) object;
+ Tags.SidebarEntry? entry = entry_map.get(tag);
+ assert(entry != null);
+
+ entry.sidebar_name_changed(tag.get_user_visible_name());
+ entry.sidebar_tooltip_changed(tag.get_user_visible_name());
+ reorder(entry);
+ }
+ }
+}
+
+public class Tags.Grouping : Sidebar.Grouping, Sidebar.InternalDropTargetEntry,
+ Sidebar.InternalDragSourceEntry, Sidebar.Contextable {
+ private Gtk.UIManager ui = new Gtk.UIManager();
+ private Gtk.Menu? context_menu = null;
+
+ public Grouping() {
+ base (_("Tags"), new ThemedIcon(Resources.ICON_TAGS));
+ setup_context_menu();
+ }
+
+ private void setup_context_menu() {
+ Gtk.ActionGroup group = new Gtk.ActionGroup("SidebarDefault");
+ Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
+
+ Gtk.ActionEntry new_tag = { "CommonNewTag", null, TRANSLATABLE, null, null, on_new_tag };
+ new_tag.label = Resources.NEW_CHILD_TAG_SIDEBAR_MENU;
+ actions += new_tag;
+
+ group.add_actions(actions, this);
+ ui.insert_action_group(group, 0);
+
+ File ui_file = Resources.get_ui("tag_sidebar_context.ui");
+ try {
+ ui.add_ui_from_file(ui_file.get_path());
+ } catch (Error err) {
+ AppWindow.error_message("Error loading UI file %s: %s".printf(
+ ui_file.get_path(), err.message));
+ Application.get_instance().panic();
+ }
+ context_menu = (Gtk.Menu) ui.get_widget("/SidebarTagContextMenu");
+
+ ui.ensure_update();
+ }
+
+ public bool internal_drop_received(Gee.List<MediaSource> media) {
+ AddTagsDialog dialog = new AddTagsDialog();
+ string[]? names = dialog.execute();
+ if (names == null || names.length == 0)
+ return false;
+
+ AppWindow.get_command_manager().execute(new AddTagsCommand(names, media));
+
+ return true;
+ }
+
+ public bool internal_drop_received_arbitrary(Gtk.SelectionData data) {
+ if (data.get_data_type().name() == LibraryWindow.TAG_PATH_MIME_TYPE) {
+ string old_tag_path = (string) data.get_data();
+ assert (Tag.global.exists(old_tag_path));
+
+ // if this is already a top-level tag, do a short-circuit return
+ if (HierarchicalTagUtilities.enumerate_path_components(old_tag_path).size < 2)
+ return true;
+
+ AppWindow.get_command_manager().execute(
+ new ReparentTagCommand(Tag.for_path(old_tag_path), "/"));
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public void prepare_selection_data(Gtk.SelectionData data) {
+ ;
+ }
+
+ public Gtk.Menu? get_sidebar_context_menu(Gdk.EventButton? event) {
+ return context_menu;
+ }
+
+ private void on_new_tag() {
+ NewRootTagCommand creation_command = new NewRootTagCommand();
+ AppWindow.get_command_manager().execute(creation_command);
+ LibraryWindow.get_app().rename_tag_in_sidebar(creation_command.get_created_tag());
+ }
+}
+
+public class Tags.SidebarEntry : Sidebar.SimplePageEntry, Sidebar.RenameableEntry,
+ Sidebar.DestroyableEntry, Sidebar.InternalDropTargetEntry, Sidebar.ExpandableEntry,
+ Sidebar.InternalDragSourceEntry {
+ private static Icon single_tag_icon;
+
+ private Tag tag;
+
+ public SidebarEntry(Tag tag) {
+ this.tag = tag;
+ }
+
+ internal static void init() {
+ single_tag_icon = new ThemedIcon(Resources.ICON_ONE_TAG);
+ }
+
+ internal static void terminate() {
+ single_tag_icon = null;
+ }
+
+ public Tag for_tag() {
+ return tag;
+ }
+
+ public override string get_sidebar_name() {
+ return tag.get_user_visible_name();
+ }
+
+ public override Icon? get_sidebar_icon() {
+ return single_tag_icon;
+ }
+
+ protected override Page create_page() {
+ return new TagPage(tag);
+ }
+
+ public void rename(string new_name) {
+ string? prepped = Tag.prep_tag_name(new_name);
+ if (prepped == null)
+ return;
+
+ prepped = prepped.replace("/", "");
+
+ if (prepped == tag.get_user_visible_name())
+ return;
+
+ if (prepped == "")
+ return;
+
+ AppWindow.get_command_manager().execute(new RenameTagCommand(tag, prepped));
+ }
+
+ public void destroy_source() {
+ if (Dialogs.confirm_delete_tag(tag))
+ AppWindow.get_command_manager().execute(new DeleteTagCommand(tag));
+ }
+
+ public bool internal_drop_received(Gee.List<MediaSource> media) {
+ AppWindow.get_command_manager().execute(new TagUntagPhotosCommand(tag, media, media.size,
+ true));
+
+ return true;
+ }
+
+ public bool internal_drop_received_arbitrary(Gtk.SelectionData data) {
+ if (data.get_data_type().name() == LibraryWindow.TAG_PATH_MIME_TYPE) {
+ string old_tag_path = (string) data.get_data();
+
+ // if we're dragging onto ourself, it's a no-op
+ if (old_tag_path == tag.get_path())
+ return true;
+
+ // if we're dragging onto one of our children, it's a no-op
+ foreach (string parent_path in HierarchicalTagUtilities.enumerate_parent_paths(tag.get_path())) {
+ if (parent_path == old_tag_path)
+ return true;
+ }
+
+ assert (Tag.global.exists(old_tag_path));
+
+ // if we're dragging onto our parent, it's a no-op
+ Tag old_tag = Tag.for_path(old_tag_path);
+ Tag old_tag_parent = old_tag.get_hierarchical_parent();
+ if (old_tag_parent != null && old_tag_parent.get_path() == tag.get_path())
+ return true;
+
+ AppWindow.get_command_manager().execute(
+ new ReparentTagCommand(old_tag, tag.get_path()));
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public Icon? get_sidebar_open_icon() {
+ return single_tag_icon;
+ }
+
+ public Icon? get_sidebar_closed_icon() {
+ return single_tag_icon;
+ }
+
+ public bool expand_on_select() {
+ return false;
+ }
+
+ public void prepare_selection_data(Gtk.SelectionData data) {
+ data.set(Gdk.Atom.intern_static_string(LibraryWindow.TAG_PATH_MIME_TYPE), 0,
+ tag.get_path().data);
+ }
+}
+
diff --git a/src/tags/HierarchicalTagIndex.vala b/src/tags/HierarchicalTagIndex.vala
new file mode 100644
index 0000000..58b5f89
--- /dev/null
+++ b/src/tags/HierarchicalTagIndex.vala
@@ -0,0 +1,90 @@
+/* Copyright 2011-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+public class HierarchicalTagIndex {
+ private Gee.Map<string, Gee.Collection<string>> tag_table;
+ private Gee.SortedSet<string> known_paths;
+
+ public HierarchicalTagIndex( ) {
+ this.tag_table = new Gee.HashMap<string, Gee.ArrayList<string>>();
+ this.known_paths = new Gee.TreeSet<string>();
+ }
+
+ public static HierarchicalTagIndex from_paths(Gee.Collection<string> client_paths) {
+ Gee.Collection<string> paths = client_paths.read_only_view;
+
+ HierarchicalTagIndex result = new HierarchicalTagIndex();
+
+ foreach (string path in paths) {
+ if (path.has_prefix(Tag.PATH_SEPARATOR_STRING)) {
+ Gee.Collection<string> components =
+ HierarchicalTagUtilities.enumerate_path_components(path);
+
+ foreach (string component in components)
+ result.add_path(component, path);
+ } else {
+ result.add_path(path, path);
+ }
+ }
+
+ return result;
+ }
+
+ public static HierarchicalTagIndex get_global_index() {
+ return HierarchicalTagIndex.from_paths(Tag.global.get_all_names());
+ }
+
+ public void add_path(string tag, string path) {
+ if (!tag_table.has_key(tag)) {
+ tag_table.set(tag, new Gee.ArrayList<string>());
+ }
+
+ tag_table.get(tag).add(path);
+ known_paths.add(path);
+ }
+
+ public Gee.Collection<string> get_all_paths() {
+ return known_paths.read_only_view;
+ }
+
+ public bool is_tag_in_index(string tag) {
+ return tag_table.has_key(tag);
+ }
+
+ public Gee.Collection<string> get_all_tags() {
+ return tag_table.keys;
+ }
+
+ public bool is_path_known(string path) {
+ return known_paths.contains(path);
+ }
+
+ public string get_path_for_name(string name) {
+ if (!is_tag_in_index(name))
+ return name;
+
+ Gee.Collection<string> paths = tag_table.get(name);
+ foreach (string path in paths) {
+ Gee.List<string> components = HierarchicalTagUtilities.enumerate_path_components(path);
+ if (components.get(components.size - 1) == name) {
+ return path;
+ }
+ }
+
+ assert_not_reached();
+ }
+
+ public string[] get_paths_for_names_array(string[] names) {
+ string[] result = new string[0];
+
+ foreach (string name in names)
+ result += get_path_for_name(name);
+
+ return result;
+ }
+
+}
+
diff --git a/src/tags/HierarchicalTagUtilities.vala b/src/tags/HierarchicalTagUtilities.vala
new file mode 100644
index 0000000..985491d
--- /dev/null
+++ b/src/tags/HierarchicalTagUtilities.vala
@@ -0,0 +1,184 @@
+/* Copyright 2011-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+class HierarchicalTagUtilities {
+
+ /**
+ * converts a flat tag name 'name' (e.g., "Animals") to a tag path compatible with the
+ * hierarchical tag data model (e.g., "/Animals"). if 'name' is already a path compatible with
+ * the hierarchical data model, 'name' is returned untouched
+ */
+ public static string flat_to_hierarchical(string name) {
+ if (!name.has_prefix(Tag.PATH_SEPARATOR_STRING))
+ return Tag.PATH_SEPARATOR_STRING + name;
+ else
+ return name;
+ }
+
+ /**
+ * converts a hierarchical tag path 'path' (e.g., "/Animals") to a flat tag name
+ * (e.g., "Animals"); if 'path' is already a flat tag name, 'path' is returned untouched; note
+ * that 'path' must be a top-level path (i.e., "/Animals" not "/Animals/Mammals/...") with
+ * only one path component; invoking this method with a 'path' argument other than a top-level
+ * path will cause an assertion failure.
+ */
+ public static string hierarchical_to_flat(string path) {
+ if (path.has_prefix(Tag.PATH_SEPARATOR_STRING)) {
+ assert(enumerate_path_components(path).size == 1);
+
+ return path.substring(1);
+ } else {
+ return path;
+ }
+ }
+
+ /**
+ * given a path 'path', generate all parent paths of 'path' and return them in sorted order,
+ * from most basic to most derived. For example, if 'path' == "/Animals/Mammals/Elephant",
+ * the list { "/Animals", "/Animals/Mammals" } is returned
+ */
+ public static Gee.List<string> enumerate_parent_paths(string in_path) {
+ string path = flat_to_hierarchical(in_path);
+
+ Gee.List<string> result = new Gee.ArrayList<string>();
+
+ string accumulator = "";
+ foreach (string component in enumerate_path_components(path)) {
+ accumulator += (Tag.PATH_SEPARATOR_STRING + component);
+ if (accumulator != path)
+ result.add(accumulator);
+ }
+
+ return result;
+ }
+
+ /**
+ * given a path 'path', enumerate all of the components of 'path' and return them in
+ * order, excluding the path component separator. For example if
+ * 'path' == "/Animals/Mammals/Elephant" the list { "Animals", "Mammals", "Elephant" } will
+ * be returned
+ */
+ public static Gee.List<string> enumerate_path_components(string in_path) {
+ string path = flat_to_hierarchical(in_path);
+
+ Gee.ArrayList<string> components = new Gee.ArrayList<string>();
+
+ string[] raw_components = path.split(Tag.PATH_SEPARATOR_STRING);
+
+ foreach (string component in raw_components) {
+ if (component != "")
+ components.add(component);
+ }
+
+ assert(components.size > 0);
+
+ return components;
+ }
+
+ /**
+ * given a list of path elements, create a fully qualified path string.
+ * For example if 'path_elements' is the list { "Animals", "Mammals", "Elephant" }
+ * the path "/Animals/Mammals/Elephant" will be returned
+ */
+ public static string? join_path_components(string[] path_components) {
+ if (path_components.length <= 0)
+ return null;
+ string tmp = string.joinv(Tag.PATH_SEPARATOR_STRING, path_components);
+ return string.joinv(Tag.PATH_SEPARATOR_STRING, { "", tmp });
+ }
+
+ public static string get_basename(string in_path) {
+ string path = flat_to_hierarchical(in_path);
+
+ Gee.List<string> components = enumerate_path_components(path);
+
+ string basename = components.get(components.size - 1);
+
+ return basename;
+ }
+
+ public static string? canonicalize(string in_tag, string foreign_separator) {
+ string result = in_tag.replace(foreign_separator, Tag.PATH_SEPARATOR_STRING);
+
+ if (!result.has_prefix(Tag.PATH_SEPARATOR_STRING))
+ result = Tag.PATH_SEPARATOR_STRING + result;
+
+ // ensure the result has text other than separators in it
+ bool is_valid = false;
+ for (int i = 0; i < result.length; i++) {
+ if (result[i] != Tag.PATH_SEPARATOR_STRING[0]) {
+ is_valid = true;
+ break;
+ }
+ }
+
+ return (is_valid) ? result : null;
+ }
+
+ public static string make_flat_tag_safe(string in_tag) {
+ return in_tag.replace(Tag.PATH_SEPARATOR_STRING, "-");
+ }
+
+ public static HierarchicalTagIndex process_hierarchical_import_keywords(Gee.Collection<string> h_keywords) {
+ HierarchicalTagIndex index = new HierarchicalTagIndex();
+
+ foreach (string keyword in h_keywords) {
+ Gee.List<string> parent_paths =
+ HierarchicalTagUtilities.enumerate_parent_paths(keyword);
+ Gee.List<string> path_components =
+ HierarchicalTagUtilities.enumerate_path_components(keyword);
+
+ assert(parent_paths.size <= path_components.size);
+
+ for (int i = 0; i < parent_paths.size; i++) {
+ if (!index.is_path_known(path_components[i]))
+ index.add_path(path_components[i], parent_paths[i]);
+ }
+
+ index.add_path(HierarchicalTagUtilities.get_basename(keyword), keyword);
+ }
+
+ return index;
+ }
+
+ public static string? get_root_path_form(string? client_path) {
+ if (client_path == null)
+ return null;
+
+ if (HierarchicalTagUtilities.enumerate_parent_paths(client_path).size != 0)
+ return client_path;
+
+ string path = client_path;
+
+ if (!Tag.global.exists(path)) {
+ if (path.has_prefix(Tag.PATH_SEPARATOR_STRING))
+ path = HierarchicalTagUtilities.hierarchical_to_flat(path);
+ else
+ path = HierarchicalTagUtilities.flat_to_hierarchical(path);
+ }
+
+ return (Tag.global.exists(path)) ? path : null;
+ }
+
+ public static void cleanup_root_path(string path) {
+ Gee.List<string> paths = HierarchicalTagUtilities.enumerate_parent_paths(path);
+
+ if (paths.size == 0) {
+ string? actual_path = HierarchicalTagUtilities.get_root_path_form(path);
+
+ if (actual_path == null)
+ return;
+
+ Tag? t = null;
+ if (Tag.global.exists(actual_path));
+ t = Tag.for_path(actual_path);
+
+ if (t != null && t.get_hierarchical_children().size == 0)
+ t.flatten();
+ }
+ }
+}
+
diff --git a/src/tags/TagPage.vala b/src/tags/TagPage.vala
new file mode 100644
index 0000000..f3ef237
--- /dev/null
+++ b/src/tags/TagPage.vala
@@ -0,0 +1,125 @@
+/* Copyright 2010-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+public class TagPage : CollectionPage {
+ private Tag tag;
+
+ public TagPage(Tag tag) {
+ base (tag.get_name());
+
+ this.tag = tag;
+
+ Tag.global.items_altered.connect(on_tags_altered);
+ tag.mirror_sources(get_view(), create_thumbnail);
+
+ init_page_context_menu("/TagsContextMenu");
+ }
+
+ ~TagPage() {
+ get_view().halt_mirroring();
+ Tag.global.items_altered.disconnect(on_tags_altered);
+ }
+
+ protected override void init_collect_ui_filenames(Gee.List<string> ui_filenames) {
+ base.init_collect_ui_filenames(ui_filenames);
+ ui_filenames.add("tags.ui");
+ }
+
+ public Tag get_tag() {
+ return tag;
+ }
+
+ protected override void get_config_photos_sort(out bool sort_order, out int sort_by) {
+ Config.Facade.get_instance().get_event_photos_sort(out sort_order, out sort_by);
+ }
+
+ protected override void set_config_photos_sort(bool sort_order, int sort_by) {
+ Config.Facade.get_instance().set_event_photos_sort(sort_order, sort_by);
+ }
+
+ protected override Gtk.ActionEntry[] init_collect_action_entries() {
+ Gtk.ActionEntry[] actions = base.init_collect_action_entries();
+
+ Gtk.ActionEntry delete_tag = { "DeleteTag", null, TRANSLATABLE, null, null, on_delete_tag };
+ // label and tooltip are assigned when the menu is displayed
+ actions += delete_tag;
+
+ Gtk.ActionEntry rename_tag = { "RenameTag", null, TRANSLATABLE, null, null, on_rename_tag };
+ // label and tooltip are assigned when the menu is displayed
+ actions += rename_tag;
+
+ Gtk.ActionEntry remove_tag = { "RemoveTagFromPhotos", null, TRANSLATABLE, null, null,
+ on_remove_tag_from_photos };
+ // label and tooltip are assigned when the menu is displayed
+ actions += remove_tag;
+
+ Gtk.ActionEntry delete_tag_sidebar = { "DeleteTagSidebar", null, Resources.DELETE_TAG_SIDEBAR_MENU,
+ null, null, on_delete_tag };
+ actions += delete_tag_sidebar;
+
+ Gtk.ActionEntry rename_tag_sidebar = { "RenameTagSidebar", null, Resources.RENAME_TAG_SIDEBAR_MENU,
+ null, null, on_rename_tag };
+ actions += rename_tag_sidebar;
+
+ Gtk.ActionEntry new_child_tag_sidebar = { "NewChildTagSidebar", null, Resources.NEW_CHILD_TAG_SIDEBAR_MENU,
+ null, null, on_new_child_tag_sidebar };
+ actions += new_child_tag_sidebar;
+
+ return actions;
+ }
+
+ private void on_tags_altered(Gee.Map<DataObject, Alteration> map) {
+ if (map.has_key(tag)) {
+ set_page_name(tag.get_name());
+ update_actions(get_view().get_selected_count(), get_view().get_count());
+ }
+ }
+
+ protected override void update_actions(int selected_count, int count) {
+ set_action_details("DeleteTag",
+ Resources.delete_tag_menu(tag.get_user_visible_name()),
+ null,
+ true);
+
+ set_action_details("RenameTag",
+ Resources.rename_tag_menu(tag.get_user_visible_name()),
+ null,
+ true);
+
+ set_action_details("RemoveTagFromPhotos",
+ Resources.untag_photos_menu(tag.get_user_visible_name(), selected_count),
+ null,
+ selected_count > 0);
+
+ base.update_actions(selected_count, count);
+ }
+
+ private void on_new_child_tag_sidebar() {
+ NewChildTagCommand creation_command = new NewChildTagCommand(tag);
+
+ AppWindow.get_command_manager().execute(creation_command);
+
+ LibraryWindow.get_app().rename_tag_in_sidebar(creation_command.get_created_child());
+ }
+
+ private void on_rename_tag() {
+ LibraryWindow.get_app().rename_tag_in_sidebar(tag);
+ }
+
+ private void on_delete_tag() {
+ if (Dialogs.confirm_delete_tag(tag))
+ AppWindow.get_command_manager().execute(new DeleteTagCommand(tag));
+ }
+
+ private void on_remove_tag_from_photos() {
+ if (get_view().get_selected_count() > 0) {
+ get_command_manager().execute(new TagUntagPhotosCommand(tag,
+ (Gee.Collection<MediaSource>) get_view().get_selected_sources(),
+ get_view().get_selected_count(), false));
+ }
+ }
+}
+
diff --git a/src/tags/Tags.vala b/src/tags/Tags.vala
new file mode 100644
index 0000000..7d02a2f
--- /dev/null
+++ b/src/tags/Tags.vala
@@ -0,0 +1,18 @@
+/* Copyright 2011-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+namespace Tags {
+
+public void init() throws Error {
+ Tags.SidebarEntry.init();
+}
+
+public void terminate() {
+ Tags.SidebarEntry.terminate();
+}
+
+}
+
diff --git a/src/tags/mk/tags.mk b/src/tags/mk/tags.mk
new file mode 100644
index 0000000..6b2e193
--- /dev/null
+++ b/src/tags/mk/tags.mk
@@ -0,0 +1,32 @@
+
+# UNIT_NAME is the Vala namespace. A file named UNIT_NAME.vala must be in this directory with
+# a init() and terminate() function declared in the namespace.
+UNIT_NAME := Tags
+
+# UNIT_DIR should match the subdirectory the files are located in. Generally UNIT_NAME in all
+# lowercase. The name of this file should be UNIT_DIR.mk.
+UNIT_DIR := tags
+
+# All Vala files in the unit should be listed here with no subdirectory prefix.
+#
+# NOTE: Do *not* include the unit's master file, i.e. UNIT_NAME.vala.
+UNIT_FILES := \
+ Branch.vala \
+ TagPage.vala \
+ HierarchicalTagIndex.vala \
+ HierarchicalTagUtilities.vala
+
+# Any unit this unit relies upon (and should be initialized before it's initialized) should
+# be listed here using its Vala namespace.
+#
+# NOTE: All units are assumed to rely upon the unit-unit. Do not include that here.
+UNIT_USES := \
+ Sidebar
+
+# List any additional files that are used in the build process as a part of this unit that should
+# be packaged in the tarball. File names should be relative to the unit's home directory.
+UNIT_RC :=
+
+# unitize.mk must be called at the end of each UNIT_DIR.mk file.
+include unitize.mk
+