summaryrefslogtreecommitdiff
path: root/src/sidebar/Tree.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/sidebar/Tree.vala')
-rw-r--r--src/sidebar/Tree.vala295
1 files changed, 167 insertions, 128 deletions
diff --git a/src/sidebar/Tree.vala b/src/sidebar/Tree.vala
index 37da7e0..a020b18 100644
--- a/src/sidebar/Tree.vala
+++ b/src/sidebar/Tree.vala
@@ -1,4 +1,4 @@
-/* Copyright 2011-2014 Yorba Foundation
+/* Copyright 2011-2015 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.
@@ -37,8 +37,7 @@ public class Sidebar.Tree : Gtk.TreeView {
private class RootWrapper : EntryWrapper {
public int root_position;
- public RootWrapper(Gtk.TreeModel model, Sidebar.Entry entry, Gtk.TreePath path, int root_position)
- requires (root_position >= 0) {
+ public RootWrapper(Gtk.TreeModel model, Sidebar.Entry entry, Gtk.TreePath path, int root_position) {
base (model, entry, path);
this.root_position = root_position;
@@ -49,9 +48,7 @@ public class Sidebar.Tree : Gtk.TreeView {
NAME,
TOOLTIP,
WRAPPER,
- PIXBUF,
- CLOSED_PIXBUF,
- OPEN_PIXBUF,
+ ICON,
N_COLUMNS
}
@@ -59,17 +56,13 @@ public class Sidebar.Tree : Gtk.TreeView {
typeof (string), // NAME
typeof (string?), // TOOLTIP
typeof (EntryWrapper), // WRAPPER
- typeof (Gdk.Pixbuf?), // PIXBUF
- typeof (Gdk.Pixbuf?), // CLOSED_PIXBUF
- typeof (Gdk.Pixbuf?) // OPEN_PIXBUF
+ typeof (string?) // ICON
);
private Gtk.UIManager ui = new Gtk.UIManager();
- private Gtk.IconTheme icon_theme;
private Gtk.CellRendererText text_renderer;
private unowned ExternalDropHandler drop_handler;
private Gtk.Entry? text_entry = null;
- private Gee.HashMap<string, Gdk.Pixbuf> icon_cache = new Gee.HashMap<string, Gdk.Pixbuf>();
private Gee.HashMap<Sidebar.Entry, EntryWrapper> entry_map =
new Gee.HashMap<Sidebar.Entry, EntryWrapper>();
private Gee.HashMap<Sidebar.Branch, int> branches = new Gee.HashMap<Sidebar.Branch, int>();
@@ -77,8 +70,11 @@ public class Sidebar.Tree : Gtk.TreeView {
private bool mask_entry_selected_signal = false;
private weak EntryWrapper? selected_wrapper = null;
private Gtk.Menu? default_context_menu = null;
+ private bool expander_called_manually = false;
+ private int expander_special_count = 0;
private bool is_internal_drag_in_progress = false;
private Sidebar.Entry? internal_drag_source_entry = null;
+ private Gtk.TreeRowReference? old_path_ref = null;
public signal void entry_selected(Sidebar.SelectableEntry selectable);
@@ -97,15 +93,17 @@ public class Sidebar.Tree : Gtk.TreeView {
public Tree(Gtk.TargetEntry[] target_entries, Gdk.DragAction actions,
ExternalDropHandler drop_handler) {
set_model(store);
+ get_style_context().add_class("sidebar");
Gtk.TreeViewColumn text_column = new Gtk.TreeViewColumn();
- text_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED);
+ text_column.set_expand(true);
Gtk.CellRendererPixbuf icon_renderer = new Gtk.CellRendererPixbuf();
+ icon_renderer.follow_state = true;
text_column.pack_start(icon_renderer, false);
- text_column.add_attribute(icon_renderer, "pixbuf", Columns.PIXBUF);
- text_column.add_attribute(icon_renderer, "pixbuf_expander_closed", Columns.CLOSED_PIXBUF);
- text_column.add_attribute(icon_renderer, "pixbuf_expander_open", Columns.OPEN_PIXBUF);
+ text_column.add_attribute(icon_renderer, "icon_name", Columns.ICON);
+ text_column.set_cell_data_func(icon_renderer, icon_renderer_function);
text_renderer = new Gtk.CellRendererText();
+ text_renderer.ellipsize = Pango.EllipsizeMode.END;
text_renderer.editing_canceled.connect(on_editing_canceled);
text_renderer.editing_started.connect(on_editing_started);
text_column.pack_start(text_renderer, true);
@@ -131,6 +129,9 @@ public class Sidebar.Tree : Gtk.TreeView {
selection.set_mode(Gtk.SelectionMode.BROWSE);
selection.set_select_function(on_selection);
+ test_expand_row.connect(on_toggle_row);
+ test_collapse_row.connect(on_toggle_row);
+
// It Would Be Nice if the target entries and actions were gleaned by querying each
// Sidebar.Entry as it was added, but that's a tad too complicated for our needs
// currently
@@ -145,9 +146,6 @@ public class Sidebar.Tree : Gtk.TreeView {
popup_menu.connect(on_context_menu_keypress);
- icon_theme = Resources.get_icon_theme_engine();
- icon_theme.changed.connect(on_theme_change);
-
setup_default_context_menu();
drag_begin.connect(on_drag_begin);
@@ -158,7 +156,14 @@ public class Sidebar.Tree : Gtk.TreeView {
~Tree() {
text_renderer.editing_canceled.disconnect(on_editing_canceled);
text_renderer.editing_started.disconnect(on_editing_started);
- icon_theme.changed.disconnect(on_theme_change);
+ }
+
+ public void icon_renderer_function(Gtk.CellLayout layout, Gtk.CellRenderer renderer, Gtk.TreeModel model, Gtk.TreeIter iter) {
+ EntryWrapper? wrapper = get_wrapper_at_iter(iter);
+ if (wrapper == null) {
+ return;
+ }
+ renderer.visible = !(wrapper.entry is Sidebar.Header);
}
private void on_drag_begin(Gdk.DragContext ctx) {
@@ -270,41 +275,72 @@ public class Sidebar.Tree : Gtk.TreeView {
public bool is_selected(Sidebar.Entry entry) {
EntryWrapper? wrapper = get_wrapper(entry);
- return (wrapper != null) ? get_selection().path_is_selected(wrapper.get_path()) : false;
+ // Even though get_selection() does not report its return type as nullable, it can be null
+ // if the window has been destroyed.
+ Gtk.TreeSelection selection = get_selection();
+ if (selection == null)
+ return false;
+
+ return (wrapper != null) ? selection.path_is_selected(wrapper.get_path()) : false;
}
public bool is_any_selected() {
return get_selection().count_selected_rows() != 0;
}
-
+
private Gtk.TreePath? get_selected_path() {
Gtk.TreeModel model;
- GLib.List<Gtk.TreePath> rows = get_selection().get_selected_rows(out model);
+ Gtk.TreeSelection? selection = get_selection();
+ if (selection == null){
+ return null;
+ }
+ GLib.List<Gtk.TreePath> rows = selection.get_selected_rows(out model);
assert(rows.length() == 0 || rows.length() == 1);
-
+
return rows.length() != 0 ? rows.nth_data(0) : null;
}
+
+ private string get_name_for_entry(Sidebar.Entry entry) {
+ string name = guarded_markup_escape_text(entry.get_sidebar_name());
+
+ Sidebar.EmphasizableEntry? emphasizable_entry = entry as Sidebar.EmphasizableEntry;
+ if (emphasizable_entry != null && emphasizable_entry.is_emphasized())
+ name = "<b>%s</b>".printf(name);
+
+ return name;
+ }
+
+ public virtual bool accept_cursor_changed() {
+ return true;
+ }
public override void cursor_changed() {
Gtk.TreePath? path = get_selected_path();
if (path == null) {
if (base.cursor_changed != null)
base.cursor_changed();
-
return;
}
EntryWrapper? wrapper = get_wrapper_at_path(path);
-
- selected_wrapper = wrapper;
-
- if (editing_disabled == 0 && wrapper != null)
- text_renderer.editable = wrapper.entry is Sidebar.RenameableEntry;
- if (wrapper != null && !mask_entry_selected_signal) {
- Sidebar.SelectableEntry? selectable = wrapper.entry as Sidebar.SelectableEntry;
- if (selectable != null)
- entry_selected(selectable);
+ if (selected_wrapper != wrapper) {
+ EntryWrapper old_wrapper = selected_wrapper;
+ selected_wrapper = wrapper;
+
+ if (editing_disabled == 0 && wrapper != null && wrapper.entry is Sidebar.RenameableEntry)
+ text_renderer.editable = ((Sidebar.RenameableEntry) wrapper.entry).is_user_renameable();
+
+ if (wrapper != null && !mask_entry_selected_signal) {
+ Sidebar.SelectableEntry? selectable = wrapper.entry as Sidebar.SelectableEntry;
+ if (selectable != null) {
+ if (accept_cursor_changed()) {
+ entry_selected(selectable);
+ } else {
+ place_cursor(old_wrapper.entry, true);
+ }
+ }
+ }
}
if (base.cursor_changed != null)
@@ -320,11 +356,14 @@ public class Sidebar.Tree : Gtk.TreeView {
Gtk.TreePath? path = get_selected_path();
if (path != null && editing_disabled > 0 && --editing_disabled == 0) {
EntryWrapper? wrapper = get_wrapper_at_path(path);
- text_renderer.editable = (wrapper != null && (wrapper.entry is Sidebar.RenameableEntry));
+ if (wrapper != null && (wrapper.entry is Sidebar.RenameableEntry))
+ text_renderer.editable = ((Sidebar.RenameableEntry) wrapper.entry).
+ is_user_renameable();
}
}
public void toggle_branch_expansion(Gtk.TreePath path, bool expand_all) {
+ expander_called_manually = true;
if (is_row_expanded(path))
collapse_row(path);
else
@@ -332,6 +371,7 @@ public class Sidebar.Tree : Gtk.TreeView {
}
public bool expand_to_entry(Sidebar.Entry entry) {
+ expander_called_manually = true;
EntryWrapper? wrapper = get_wrapper(entry);
if (wrapper == null)
return false;
@@ -342,6 +382,7 @@ public class Sidebar.Tree : Gtk.TreeView {
}
public void expand_to_first_child(Sidebar.Entry entry) {
+ expander_called_manually = true;
EntryWrapper? wrapper = get_wrapper(entry);
if (wrapper == null)
return;
@@ -445,7 +486,7 @@ public class Sidebar.Tree : Gtk.TreeView {
assert(!entry_map.has_key(entry));
entry_map.set(entry, wrapper);
- store.set(assoc_iter, Columns.NAME, guarded_markup_escape_text(entry.get_sidebar_name()));
+ store.set(assoc_iter, Columns.NAME, get_name_for_entry(entry));
store.set(assoc_iter, Columns.TOOLTIP, guarded_markup_escape_text(entry.get_sidebar_tooltip()));
store.set(assoc_iter, Columns.WRAPPER, wrapper);
load_entry_icons(assoc_iter);
@@ -458,15 +499,15 @@ public class Sidebar.Tree : Gtk.TreeView {
pageable.page_created.connect(on_sidebar_page_created);
pageable.destroying_page.connect(on_sidebar_destroying_page);
}
+
+ Sidebar.EmphasizableEntry? emphasizable = entry as Sidebar.EmphasizableEntry;
+ if (emphasizable != null)
+ emphasizable.is_emphasized_changed.connect(on_is_emphasized_changed);
Sidebar.RenameableEntry? renameable = entry as Sidebar.RenameableEntry;
if (renameable != null)
renameable.sidebar_name_changed.connect(on_sidebar_name_changed);
-
- Sidebar.ExpandableEntry? expandable = entry as Sidebar.ExpandableEntry;
- if (expandable != null)
- expandable.sidebar_open_closed_icons_changed.connect(on_sidebar_open_closed_icons_changed);
-
+
entry.grafted(this);
}
@@ -479,7 +520,7 @@ public class Sidebar.Tree : Gtk.TreeView {
EntryWrapper new_wrapper = new EntryWrapper(store, entry, store.get_path(new_iter));
entry_map.set(entry, new_wrapper);
- store.set(new_iter, Columns.NAME, guarded_markup_escape_text(entry.get_sidebar_name()));
+ store.set(new_iter, Columns.NAME, get_name_for_entry(entry));
store.set(new_iter, Columns.TOOLTIP, guarded_markup_escape_text(entry.get_sidebar_tooltip()));
store.set(new_iter, Columns.WRAPPER, new_wrapper);
load_entry_icons(new_iter);
@@ -571,9 +612,9 @@ public class Sidebar.Tree : Gtk.TreeView {
if (renameable != null)
renameable.sidebar_name_changed.disconnect(on_sidebar_name_changed);
- Sidebar.ExpandableEntry? expandable = entry as Sidebar.ExpandableEntry;
- if (expandable != null)
- expandable.sidebar_open_closed_icons_changed.disconnect(on_sidebar_open_closed_icons_changed);
+ Sidebar.EmphasizableEntry? emphasizable = entry as Sidebar.EmphasizableEntry;
+ if (emphasizable != null)
+ emphasizable.is_emphasized_changed.disconnect(on_is_emphasized_changed);
bool removed = entry_map.unset(entry);
assert(removed);
@@ -703,88 +744,42 @@ public class Sidebar.Tree : Gtk.TreeView {
store.set(wrapper.get_iter(), Columns.TOOLTIP, guarded_markup_escape_text(tooltip));
}
- private void on_sidebar_icon_changed(Sidebar.Entry entry, Icon? icon) {
+ private void on_sidebar_icon_changed(Sidebar.Entry entry, string? icon) {
EntryWrapper? wrapper = get_wrapper(entry);
assert(wrapper != null);
- store.set(wrapper.get_iter(), Columns.PIXBUF, fetch_icon_pixbuf(icon));
+ store.set(wrapper.get_iter(), Columns.ICON, icon);
}
-
- private void on_sidebar_page_created(Sidebar.PageRepresentative entry, Page page) {
- page_created(entry, page);
+
+ private void rename_entry(Sidebar.Entry entry) {
+ EntryWrapper? wrapper = get_wrapper(entry);
+ assert(wrapper != null);
+
+ store.set(wrapper.get_iter(), Columns.NAME, get_name_for_entry(entry));
}
- private void on_sidebar_destroying_page(Sidebar.PageRepresentative entry, Page page) {
- destroying_page(entry, page);
+ private void on_sidebar_name_changed(Sidebar.Entry entry, string name) {
+ rename_entry(entry);
}
- private void on_sidebar_open_closed_icons_changed(Sidebar.ExpandableEntry entry, Icon? open,
- Icon? closed) {
- EntryWrapper? wrapper = get_wrapper(entry);
- assert(wrapper != null);
-
- store.set(wrapper.get_iter(), Columns.OPEN_PIXBUF, fetch_icon_pixbuf(open));
- store.set(wrapper.get_iter(), Columns.CLOSED_PIXBUF, fetch_icon_pixbuf(closed));
+ private void on_sidebar_page_created(Sidebar.PageRepresentative entry, Page page) {
+ page_created(entry, page);
}
- private void on_sidebar_name_changed(Sidebar.RenameableEntry entry, string name) {
- EntryWrapper? wrapper = get_wrapper(entry);
- assert(wrapper != null);
-
- store.set(wrapper.get_iter(), Columns.NAME, guarded_markup_escape_text(name));
+ private void on_is_emphasized_changed(Sidebar.EmphasizableEntry entry, bool is_emphasized) {
+ rename_entry(entry);
}
- private Gdk.Pixbuf? fetch_icon_pixbuf(GLib.Icon? gicon) {
- if (gicon == null)
- return null;
-
- try {
- Gdk.Pixbuf? icon = icon_cache.get(gicon.to_string());
- if (icon != null)
- return icon;
-
- Gtk.IconInfo? info = icon_theme.lookup_by_gicon(gicon, ICON_SIZE, 0);
- if (info == null)
- return null;
-
- icon = info.load_icon();
- if (icon == null)
- return null;
-
- icon_cache.set(gicon.to_string(), icon);
-
- return icon;
- } catch (Error err) {
- warning("Unable to load icon %s: %s", gicon.to_string(), err.message);
-
- return null;
- }
+ private void on_sidebar_destroying_page(Sidebar.PageRepresentative entry, Page page) {
+ destroying_page(entry, page);
}
private void load_entry_icons(Gtk.TreeIter iter) {
EntryWrapper? wrapper = get_wrapper_at_iter(iter);
if (wrapper == null)
return;
-
- Icon? icon = wrapper.entry.get_sidebar_icon();
- Icon? open = null;
- Icon? closed = null;
-
- Sidebar.ExpandableEntry? expandable = wrapper.entry as Sidebar.ExpandableEntry;
- if (expandable != null) {
- open = expandable.get_sidebar_open_icon();
- closed = expandable.get_sidebar_closed_icon();
- }
-
- if (open == null)
- open = icon;
-
- if (closed == null)
- closed = icon;
-
- store.set(iter, Columns.PIXBUF, fetch_icon_pixbuf(icon));
- store.set(iter, Columns.OPEN_PIXBUF, fetch_icon_pixbuf(open));
- store.set(iter, Columns.CLOSED_PIXBUF, fetch_icon_pixbuf(closed));
+ string? icon = wrapper.entry.get_sidebar_icon();
+ store.set(iter, Columns.ICON, icon);
}
private void load_branch_icons(Gtk.TreeIter iter) {
@@ -798,15 +793,6 @@ public class Sidebar.Tree : Gtk.TreeView {
}
}
- private void on_theme_change() {
- Gtk.TreeIter iter;
- if (store.get_iter_first(out iter)) {
- do {
- load_branch_icons(iter);
- } while (store.iter_next(ref iter));
- }
- }
-
private bool on_selection(Gtk.TreeSelection selection, Gtk.TreeModel model, Gtk.TreePath path,
bool path_currently_selected) {
// only allow selection if a page is selectable
@@ -879,6 +865,42 @@ public class Sidebar.Tree : Gtk.TreeView {
return true;
}
+ public bool on_toggle_row(Gtk.TreeIter iter, Gtk.TreePath path) {
+ // Determine whether to allow the row to toggle
+ EntryWrapper? wrapper = get_wrapper_at_iter(iter);
+ if (wrapper == null) {
+ return false; // don't affect things
+ }
+
+ // Most of the time, only allow manual toggles
+ bool should_allow_toggle = expander_called_manually;
+
+ // Cancel out the manual flag
+ expander_called_manually = false;
+
+ // If we are an expanded parent entry with content
+ if (is_row_expanded(path) && store.iter_has_child(iter) && wrapper.entry is Sidebar.SelectableEntry) {
+ // We are taking a special action
+ expander_special_count++;
+ if (expander_special_count == 1) {
+ // Workaround that prevents arrows from double-toggling
+ return true;
+ } else {
+ // Toggle only if non-manual, as opposed to the usual behavior
+ should_allow_toggle = !should_allow_toggle;
+ }
+ } else {
+ // Reset the special behavior count
+ expander_special_count = 0;
+ }
+
+ if (should_allow_toggle) {
+ return false;
+ }
+ // Prevent branch expansion toggle
+ return true;
+ }
+
public override bool button_press_event(Gdk.EventButton event) {
Gtk.TreePath? path = get_path_from_event(event);
@@ -893,19 +915,29 @@ public class Sidebar.Tree : Gtk.TreeView {
popup_context_menu(path, event);
else
popup_default_context_menu(event);
- } else if (event.button == 1 && event.type == Gdk.EventType.2BUTTON_PRESS) {
- // double left click
- if (path != null) {
+ } else if (event.button == 1 && event.type == Gdk.EventType.BUTTON_PRESS) {
+ if (path == null) {
+ old_path_ref = null;
+ return base.button_press_event(event);
+ }
+
+ EntryWrapper? wrapper = get_wrapper_at_path(path);
+
+ if (wrapper == null) {
+ old_path_ref = null;
+ return base.button_press_event(event);
+ }
+
+ // Enable single click to toggle tree entries (bug 4985)
+ if (wrapper.entry is Sidebar.ExpandableEntry
+ || wrapper.entry is Sidebar.InternalDropTargetEntry) {
+ // all labels are InternalDropTargetEntries
toggle_branch_expansion(path, false);
-
- if (can_rename_path(path))
- return false;
}
- } else if (event.button == 1 && event.type == Gdk.EventType.BUTTON_PRESS) {
+
// Is this a click on an already-highlighted tree item?
- Gtk.TreePath? cursor_path = null;
- get_cursor(out cursor_path, null);
- if ((cursor_path != null) && (cursor_path.compare(path) == 0)) {
+ if ((old_path_ref != null) && (old_path_ref.get_path() != null)
+ && (old_path_ref.get_path().compare(path) == 0)) {
// yes, don't allow single-click editing, but
// pass the event on for dragging.
text_renderer.editable = false;
@@ -914,9 +946,13 @@ public class Sidebar.Tree : Gtk.TreeView {
// Got click on different tree item, make sure it is editable
// if it needs to be.
- if (path != null && get_wrapper_at_path(path).entry is Sidebar.RenameableEntry) {
+ if (wrapper.entry is Sidebar.RenameableEntry &&
+ ((Sidebar.RenameableEntry) wrapper.entry).is_user_renameable()) {
text_renderer.editable = true;
}
+
+ // Remember what tree item is highlighted for next time.
+ old_path_ref = new Gtk.TreeRowReference(store, path);
}
return base.button_press_event(event);
@@ -1106,6 +1142,9 @@ public class Sidebar.Tree : Gtk.TreeView {
if (renameable == null)
return false;
+ if (wrapper.entry is Sidebar.Header)
+ return false;
+
get_selection().select_path(path);
return true;