From 4ea2cc3bd4a7d9b1c54a9d33e6a1cf82e7c8c21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Wed, 23 Jul 2014 09:06:59 +0200 Subject: Imported Upstream version 0.18.1 --- src/tags/Branch.vala | 310 +++++++++++++++++++++++++++++++++ src/tags/HierarchicalTagIndex.vala | 90 ++++++++++ src/tags/HierarchicalTagUtilities.vala | 184 +++++++++++++++++++ src/tags/TagPage.vala | 125 +++++++++++++ src/tags/Tags.vala | 18 ++ src/tags/mk/tags.mk | 32 ++++ 6 files changed, 759 insertions(+) create mode 100644 src/tags/Branch.vala create mode 100644 src/tags/HierarchicalTagIndex.vala create mode 100644 src/tags/HierarchicalTagUtilities.vala create mode 100644 src/tags/TagPage.vala create mode 100644 src/tags/Tags.vala create mode 100644 src/tags/mk/tags.mk (limited to 'src/tags') 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 entry_map = new Gee.HashMap(); + + 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? added_raw, Gee.Iterable? 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 added = new Gee.TreeSet(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 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 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 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> tag_table; + private Gee.SortedSet known_paths; + + public HierarchicalTagIndex( ) { + this.tag_table = new Gee.HashMap>(); + this.known_paths = new Gee.TreeSet(); + } + + public static HierarchicalTagIndex from_paths(Gee.Collection client_paths) { + Gee.Collection 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 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()); + } + + tag_table.get(tag).add(path); + known_paths.add(path); + } + + public Gee.Collection 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 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 paths = tag_table.get(name); + foreach (string path in paths) { + Gee.List 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 enumerate_parent_paths(string in_path) { + string path = flat_to_hierarchical(in_path); + + Gee.List result = new Gee.ArrayList(); + + 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 enumerate_path_components(string in_path) { + string path = flat_to_hierarchical(in_path); + + Gee.ArrayList components = new Gee.ArrayList(); + + 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 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 h_keywords) { + HierarchicalTagIndex index = new HierarchicalTagIndex(); + + foreach (string keyword in h_keywords) { + Gee.List parent_paths = + HierarchicalTagUtilities.enumerate_parent_paths(keyword); + Gee.List 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 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 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 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) 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 + -- cgit v1.2.3