geary/src/client/sidebar/sidebar-branch.vala
Charles Lindsay 910e1c3d0b Update copyright headers; fix #6195
Also removing the erroneous space that had crept in at the end of the
line in most of our header comments.
2013-04-12 12:32:39 -07:00

437 lines
14 KiB
Vala

/* Copyright 2011-2013 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 delegate bool Locator<G>(G item);
public class Sidebar.Branch : Object {
[Flags]
public enum Options {
NONE = 0,
HIDE_IF_EMPTY,
AUTO_OPEN_ON_NEW_CHILD,
STARTUP_EXPAND_TO_FIRST_CHILD,
STARTUP_OPEN_GROUPING;
public bool is_hide_if_empty() {
return (this & HIDE_IF_EMPTY) != 0;
}
public bool is_auto_open_on_new_child() {
return (this & AUTO_OPEN_ON_NEW_CHILD) != 0;
}
public bool is_startup_expand_to_first_child() {
return (this & STARTUP_EXPAND_TO_FIRST_CHILD) != 0;
}
public bool is_startup_open_grouping() {
return (this & STARTUP_OPEN_GROUPING) != 0;
}
}
private class Node {
public delegate void PruneCallback(Node node);
public delegate void ChildrenReorderedCallback(Node node);
public Sidebar.Entry entry;
public weak Node? parent;
public CompareFunc<Sidebar.Entry> comparator;
public Gee.SortedSet<Node>? children = null;
public Node(Sidebar.Entry entry, Node? parent, CompareFunc<Sidebar.Entry> comparator) {
this.entry = entry;
this.parent = parent;
this.comparator = comparator;
}
private static int comparator_wrapper(void *a, void *b) {
if (a == b)
return 0;
Node *anode = (Node *) a;
Node *bnode = (Node *) b;
assert(anode->parent == bnode->parent);
return anode->parent.comparator(anode->entry, bnode->entry);
}
public bool has_children() {
return (children != null && children.size > 0);
}
public void add_child(Node child) {
child.parent = this;
if (children == null)
children = new Geary.Collection.FixedTreeSet<Node>(comparator_wrapper);
bool added = children.add(child);
assert(added);
}
public void remove_child(Node child) {
assert(children != null);
Gee.SortedSet<Node> new_children = new Geary.Collection.FixedTreeSet<Node>(comparator_wrapper);
// For similar reasons as in reorder_child(), can't rely on TreeSet to locate this
// node because we need reference equality.
bool found = false;
foreach (Node c in children) {
if (c != child)
new_children.add(c);
else
found = true;
}
assert(found);
if (new_children.size != 0)
children = new_children;
else
children = null;
child.parent = null;
}
public void prune_children(PruneCallback cb) {
if (children == null)
return;
foreach (Node child in children)
child.prune_children(cb);
Gee.SortedSet<Node> old_children = children;
children = null;
// Although this could've been done in the prior loop, it means notifying that
// a child has been removed prior to it being removed; this can cause problem
// if a signal handler calls back into the Tree to examine/add/remove nodes.
foreach (Node child in old_children)
cb(child);
}
// This returns the index of the Node purely by reference equality, making it useful if
// the criteria the Node is sorted upon has changed.
public int index_of_by_reference(Node child) {
if (children == null)
return -1;
int index = 0;
foreach (Node c in children) {
if (child == c)
return index;
index++;
}
return -1;
}
// Returns true if child moved when reordered.
public bool reorder_child(Node child) {
assert(children != null);
int old_index = index_of_by_reference(child);
assert(old_index >= 0);
// Because Gee.SortedSet uses the comparator for equality, if the Node's entry state
// has changed in such a way that the item is no longer sorted properly, the SortedSet's
// search and remove methods are useless. Makes no difference if children.remove() is
// called or the set is manually iterated over and removed via the Iterator -- a
// tree search is performed and the child will not be found. Only easy solution is
// to rebuild a new SortedSet and see if the child has moved.
Gee.SortedSet<Node> new_children = new Geary.Collection.FixedTreeSet<Node>(comparator_wrapper);
bool added = new_children.add_all(children);
assert(added);
children = new_children;
int new_index = index_of_by_reference(child);
assert(new_index >= 0);
return (old_index != new_index);
}
public void reorder_children(bool recursive, ChildrenReorderedCallback cb) {
if (children == null)
return;
Gee.SortedSet<Node> reordered = new Geary.Collection.FixedTreeSet<Node>(comparator_wrapper);
reordered.add_all(children);
children = reordered;
if (recursive) {
foreach (Node child in children)
child.reorder_children(true, cb);
}
cb(this);
}
public void change_comparator(CompareFunc<Sidebar.Entry> comparator, bool recursive,
ChildrenReorderedCallback cb) {
this.comparator = comparator;
// reorder children, but need to do manual recursion to set comparator
reorder_children(false, cb);
if (recursive) {
foreach (Node child in children)
child.change_comparator(comparator, true, cb);
}
}
}
private Node root;
private Options options;
private bool shown = true;
private CompareFunc<Sidebar.Entry> default_comparator;
private Gee.HashMap<Sidebar.Entry, Node> map = new Gee.HashMap<Sidebar.Entry, Node>();
public signal void entry_added(Sidebar.Entry entry);
public signal void entry_removed(Sidebar.Entry entry);
public signal void entry_moved(Sidebar.Entry entry);
public signal void entry_reparented(Sidebar.Entry entry, Sidebar.Entry old_parent);
public signal void children_reordered(Sidebar.Entry entry);
public signal void show_branch(bool show);
public Branch(Sidebar.Entry root, Options options, CompareFunc<Sidebar.Entry> default_comparator,
CompareFunc<Sidebar.Entry>? root_comparator = null) {
this.default_comparator = default_comparator;
this.root = new Node(root, null,
(root_comparator != null) ? root_comparator : default_comparator);
this.options = options;
map.set(root, this.root);
if (options.is_hide_if_empty())
set_show_branch(false);
}
public Sidebar.Entry get_root() {
return root.entry;
}
public void set_show_branch(bool shown) {
if (this.shown == shown)
return;
this.shown = shown;
show_branch(shown);
}
public bool get_show_branch() {
return shown;
}
public bool is_auto_open_on_new_child() {
return options.is_auto_open_on_new_child();
}
public bool is_startup_expand_to_first_child() {
return options.is_startup_expand_to_first_child();
}
public bool is_startup_open_grouping() {
return options.is_startup_open_grouping();
}
public void graft(Sidebar.Entry parent, Sidebar.Entry entry,
CompareFunc<Sidebar.Entry>? comparator = null) {
assert(map.has_key(parent));
assert(!map.has_key(entry));
if (options.is_hide_if_empty())
set_show_branch(true);
Node parent_node = map.get(parent);
Node entry_node = new Node(entry, parent_node,
(comparator != null) ? comparator : default_comparator);
parent_node.add_child(entry_node);
map.set(entry, entry_node);
entry_added(entry);
}
// Cannot prune the root. The Branch should simply be removed from the Tree.
public void prune(Sidebar.Entry entry) {
assert(entry != root.entry);
assert(map.has_key(entry));
Node entry_node = map.get(entry);
entry_node.prune_children(prune_callback);
assert(entry_node.parent != null);
entry_node.parent.remove_child(entry_node);
bool removed = map.unset(entry);
assert(removed);
entry_removed(entry);
if (options.is_hide_if_empty() && !root.has_children())
set_show_branch(false);
}
// Cannot reparent the root.
public void reparent(Sidebar.Entry new_parent, Sidebar.Entry entry) {
assert(entry != root.entry);
assert(map.has_key(entry));
assert(map.has_key(new_parent));
Node entry_node = map.get(entry);
Node new_parent_node = map.get(new_parent);
assert(entry_node.parent != null);
Sidebar.Entry old_parent = entry_node.parent.entry;
entry_node.parent.remove_child(entry_node);
new_parent_node.add_child(entry_node);
entry_reparented(entry, old_parent);
}
public bool has_entry(Sidebar.Entry entry) {
return (root.entry == entry || map.has_key(entry));
}
// Call when a value related to the comparison of this entry has changed. The root cannot be
// reordered.
public void reorder(Sidebar.Entry entry) {
assert(entry != root.entry);
Node? entry_node = map.get(entry);
assert(entry_node != null);
assert(entry_node.parent != null);
if (entry_node.parent.reorder_child(entry_node))
entry_moved(entry);
}
// Call when the entire tree needs to be reordered.
public void reorder_all() {
root.reorder_children(true, children_reordered_callback);
}
// Call when the children of the entry need to be reordered.
public void reorder_children(Sidebar.Entry entry, bool recursive) {
Node? entry_node = map.get(entry);
assert(entry_node != null);
entry_node.reorder_children(recursive, children_reordered_callback);
}
public void change_all_comparators(CompareFunc<Sidebar.Entry>? comparator) {
root.change_comparator(comparator, true, children_reordered_callback);
}
public void change_comparator(Sidebar.Entry entry, bool recursive,
CompareFunc<Sidebar.Entry>? comparator) {
Node? entry_node = map.get(entry);
assert(entry_node != null);
entry_node.change_comparator(comparator, recursive, children_reordered_callback);
}
public int get_child_count(Sidebar.Entry parent) {
Node? parent_node = map.get(parent);
assert(parent_node != null);
return (parent_node.children != null) ? parent_node.children.size : 0;
}
// Gets a snapshot of the children of the entry; this list will not be changed as the
// branch is updated.
public Gee.List<Sidebar.Entry>? get_children(Sidebar.Entry parent) {
assert(map.has_key(parent));
Node parent_node = map.get(parent);
if (parent_node.children == null)
return null;
Gee.List<Sidebar.Entry> child_entries = new Gee.ArrayList<Sidebar.Entry>();
foreach (Node child in parent_node.children)
child_entries.add(child.entry);
return child_entries;
}
public Sidebar.Entry? find_first_child(Sidebar.Entry parent, Locator<Sidebar.Entry> locator) {
Node? parent_node = map.get(parent);
assert(parent_node != null);
if (parent_node.children == null)
return null;
foreach (Node child in parent_node.children) {
if (locator(child.entry))
return child.entry;
}
return null;
}
// Returns null if entry is root;
public Sidebar.Entry? get_parent(Sidebar.Entry entry) {
if (entry == root.entry)
return null;
Node? entry_node = map.get(entry);
assert(entry_node != null);
assert(entry_node.parent != null);
return entry_node.parent.entry;
}
// Returns null if entry is root;
public Sidebar.Entry? get_previous_sibling(Sidebar.Entry entry) {
if (entry == root.entry)
return null;
Node? entry_node = map.get(entry);
assert(entry_node != null);
assert(entry_node.parent != null);
assert(entry_node.parent.children != null);
Node? sibling = entry_node.parent.children.lower(entry_node);
return (sibling != null) ? sibling.entry : null;
}
// Returns null if entry is root;
public Sidebar.Entry? get_next_sibling(Sidebar.Entry entry) {
if (entry == root.entry)
return null;
Node? entry_node = map.get(entry);
assert(entry_node != null);
assert(entry_node.parent != null);
assert(entry_node.parent.children != null);
Node? sibling = entry_node.parent.children.higher(entry_node);
return (sibling != null) ? sibling.entry : null;
}
private void prune_callback(Node node) {
entry_removed(node.entry);
}
private void children_reordered_callback(Node node) {
children_reordered(node.entry);
}
}