[seahorse/wip/nielsdg/seahorse-listview: 2/3] Use a GtkListBox instead of a GtkTreeView



commit 0b69465aec7c39004cc28fde456e2359617480c5
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Sun Sep 8 12:03:36 2019 +0200

    Use a GtkListBox instead of a GtkTreeView

 common/catalog.vala           |   1 -
 common/key-manager-store.vala | 496 ---------------------
 common/meson.build            |   1 -
 gkr/gkr-backend.vala          |  30 +-
 libseahorse/seahorse.css      |  18 +
 src/key-manager-item-row.vala |   1 +
 src/key-manager.vala          |  15 +-
 src/sidebar.vala              | 977 +++++++++++++-----------------------------
 8 files changed, 327 insertions(+), 1212 deletions(-)
---
diff --git a/common/catalog.vala b/common/catalog.vala
index f8f9a911..2cff02e5 100644
--- a/common/catalog.vala
+++ b/common/catalog.vala
@@ -30,7 +30,6 @@ public abstract class Catalog : Gtk.ApplicationWindow {
     private GLib.Settings _settings;
 
     public abstract GLib.List<weak Backend> get_backends();
-    public abstract Place? get_focused_place();
     public abstract GLib.List<GLib.Object> get_selected_objects();
 
     private const ActionEntry[] ACTION_ENTRIES = {
diff --git a/common/meson.build b/common/meson.build
index 71b80cc3..9a2b622c 100644
--- a/common/meson.build
+++ b/common/meson.build
@@ -10,7 +10,6 @@ common_sources = [
   'exportable.vala',
   'exporter.vala',
   'icons.vala',
-  'key-manager-store.vala',
   'interaction.vala',
   'item-list.vala',
   'lockable.vala',
diff --git a/gkr/gkr-backend.vala b/gkr/gkr-backend.vala
index 44bf065e..ad70bf6c 100644
--- a/gkr/gkr-backend.vala
+++ b/gkr/gkr-backend.vala
@@ -115,18 +115,20 @@ public class Backend: GLib.Object , Gcr.Collection, Seahorse.Backend {
                        if (this._aliases.lookup("session") == object_path)
                                continue;
 
-                       seen.add(object_path);
-                       if (this._keyrings.lookup(object_path) == null) {
-                               this._keyrings.insert(object_path, (Keyring)keyring);
+                       var uri = "secret-service://%s".printf(object_path);
+                       seen.add(uri);
+                       if (this._keyrings.lookup(uri) == null) {
+                               this._keyrings.insert(uri, (Keyring)keyring);
                                emit_added(keyring);
                        }
                }
 
                /* Remove any that we didn't find */
                var iter = GLib.HashTableIter<string, Keyring>(this._keyrings);
-               while (iter.next(out object_path, null)) {
-                       if (!seen.contains(object_path)) {
-                               var keyring = this._keyrings.lookup(object_path);
+               string uri;
+               while (iter.next(out uri, null)) {
+                       if (!seen.contains(uri)) {
+                               var keyring = this._keyrings.lookup(uri);
                                iter.remove();
                                emit_removed(keyring);
                        }
@@ -146,13 +148,13 @@ public class Backend: GLib.Object , Gcr.Collection, Seahorse.Backend {
                return get_keyrings();
        }
 
-       public bool contains(GLib.Object object) {
-               if (object is Keyring) {
-                       var keyring = (Keyring)object;
-                       return this._keyrings.lookup(keyring.uri) == keyring;
-               }
-               return false;
-       }
+    public bool contains(GLib.Object object) {
+        var keyring = object as Gkr.Keyring;
+        if (keyring == null)
+            return false;
+
+        return this._keyrings.lookup(keyring.uri) == keyring;
+    }
 
        public Place? lookup_place(string uri) {
                return this._keyrings.lookup(uri);
@@ -169,7 +171,7 @@ public class Backend: GLib.Object , Gcr.Collection, Seahorse.Backend {
                return Backend._instance;
        }
 
-       public GLib.List<weak Keyring> get_keyrings() {
+       public GLib.List<unowned Keyring> get_keyrings() {
                return this._keyrings.get_values();
        }
 
diff --git a/libseahorse/seahorse.css b/libseahorse/seahorse.css
index b77eaaad..e0dfcbc7 100644
--- a/libseahorse/seahorse.css
+++ b/libseahorse/seahorse.css
@@ -5,6 +5,24 @@
     border-radius: 0;
 }
 
+.seahorse-sidebar-item-header {
+  margin-top: 6px;
+  font-weight: bold;
+}
+
+.seahorse-sidebar-item {
+  margin: 0 3px 0 12px;
+}
+
+.seahorse-sidebar-item > label {
+  margin: 3px 0 3px 0;
+}
+
+.seahorse-sidebar-item > button {
+  padding: 0;
+  margin: 0;
+}
+
 .seahorse-item-listbox {
   border: 1px solid @borders;
   min-width: 400px;
diff --git a/src/key-manager-item-row.vala b/src/key-manager-item-row.vala
index df51825b..0fdf09ac 100644
--- a/src/key-manager-item-row.vala
+++ b/src/key-manager-item-row.vala
@@ -55,6 +55,7 @@ public class Seahorse.KeyManagerItemRow : Gtk.ListBoxRow {
         object.get("description", out description);
         var description_label = new Gtk.Label(description);
         description_label.xalign = 1.0f;
+        description_label.valign = Gtk.Align.START;
         description_label.get_style_context().add_class("seahorse-item-listbox-row-description");
         grid.attach(description_label, 2, 0);
 
diff --git a/src/key-manager.vala b/src/key-manager.vala
index 5fb1c937..4097d953 100644
--- a/src/key-manager.vala
+++ b/src/key-manager.vala
@@ -183,6 +183,7 @@ public class Seahorse.KeyManager : Catalog {
 
     private void check_empty_state() {
         bool empty = (this.item_list.get_n_items() == 0);
+        debug("Checking empty state: %s", empty.to_string());
 
         this.show_search_button.sensitive = !empty;
         if (!empty) {
@@ -193,7 +194,7 @@ public class Seahorse.KeyManager : Catalog {
         // We have an empty page, that might still have 2 reasons:
         // - we really have no items in our collections
         // - we're dealing with a locked keyring
-        Place? place = get_focused_place();
+        Place? place = this.sidebar.get_focused_place();
         if (place != null && place is Lockable && ((Lockable) place).unlockable) {
             this.content_stack.visible_child_name = "locked_keyring_page";
             return;
@@ -384,17 +385,13 @@ public class Seahorse.KeyManager : Catalog {
         }
     }
 
-    public override Place? get_focused_place() {
-        return this.sidebar.get_focused_place();
-    }
-
     private Gcr.Collection setup_sidebar() {
         this.sidebar = new Sidebar();
         sidebar.hexpand = true;
 
         /* Make sure we update the empty state on any change */
-        this.sidebar.get_selection().changed.connect((sel) => check_empty_state());
-        this.sidebar.current_collection_changed.connect (() => check_empty_state ());
+        this.sidebar.selected_rows_changed.connect((sidebar) => { check_empty_state(); });
+        this.sidebar.current_collection_changed.connect((sidebar) => { check_empty_state (); });
 
         this.sidebar_panes.position = this.settings.get_int("sidebar-width");
         this.sidebar_panes.realize.connect(() =>   { this.sidebar_panes.position = 
this.settings.get_int("sidebar-width"); });
@@ -411,7 +408,7 @@ public class Seahorse.KeyManager : Catalog {
 
         this.settings.bind("keyrings-selected", this.sidebar, "selected-uris", SettingsBindFlags.DEFAULT);
 
-        return this.sidebar.collection;
+        return this.sidebar.objects;
     }
 
     public override List<weak Backend> get_backends() {
@@ -426,7 +423,7 @@ public class Seahorse.KeyManager : Catalog {
 
     [GtkCallback]
     private void on_locked_keyring_unlock_button_clicked(Gtk.Button unlock_button) {
-        Lockable? place = get_focused_place() as Lockable;
+        Lockable? place = this.sidebar.get_focused_place() as Lockable;
         return_if_fail(place != null && place.unlockable);
 
         unlock_button.sensitive = false;
diff --git a/src/sidebar.vala b/src/sidebar.vala
index f7a0b2f9..86561d99 100644
--- a/src/sidebar.vala
+++ b/src/sidebar.vala
@@ -18,39 +18,18 @@
  * <http://www.gnu.org/licenses/>.
  */
 
-public class Seahorse.Sidebar : Gtk.TreeView {
+public class Seahorse.Sidebar : Gtk.ListBox {
 
-    private const int ACTION_BUTTON_XPAD = 6;
-
-    private Gtk.ListStore store = new Gtk.ListStore.newv(Column.types());
+    private GLib.ListStore store = new GLib.ListStore(typeof(Seahorse.Place));
     private List<Backend> backends = new List<Backend>();
-    private Gcr.UnionCollection objects = new Gcr.UnionCollection();
-
-    // The selection
-    private HashTable<Gcr.Collection, Gcr.Collection> selection
-         = new HashTable<Gcr.Collection, Gcr.Collection>(direct_hash, direct_equal);
-    private bool updating;
 
     // A set of chosen uris, used with settings
     private GenericSet<string?> chosen = new GenericSet<string?>(str_hash, str_equal);
 
-    // Action icons
-    private Gdk.Pixbuf? pixbuf_lock;
-    private Gdk.Pixbuf? pixbuf_unlock;
-    private Gdk.Pixbuf? pixbuf_lock_l;
-    private Gdk.Pixbuf? pixbuf_unlock_l;
-    private Gtk.TreePath? action_highlight_path;
-    private Gtk.CellRendererPixbuf action_cell_renderer;
-    private int action_button_size;
-
-    private uint update_places_sig;
-
     /**
      * Collection of objects sidebar represents
      */
-    public Gcr.Collection collection {
-        get { return this.objects; }
-    }
+    public Gcr.UnionCollection objects { get; private set; default = new Gcr.UnionCollection(); }
 
     /**
      * The URIs selected by the user
@@ -68,7 +47,7 @@ public class Seahorse.Sidebar : Gtk.TreeView {
         set {
             if (this._combined != value) {
                 this._combined = value;
-                update_objects_in_collection(false);
+                update_objects();
             }
         }
     }
@@ -80,98 +59,12 @@ public class Seahorse.Sidebar : Gtk.TreeView {
      */
     public signal void current_collection_changed();
 
-    private enum RowType {
-        BACKEND,
-        PLACE,
-    }
-
-    private enum Column {
-        ROW_TYPE,
-        ICON,
-        LABEL,
-        TOOLTIP,
-        CATEGORY,
-        COLLECTION,
-        URI,
-        N_COLUMNS;
-
-        public static Type[] types() {
-            return {
-                typeof(uint),
-                typeof(Icon),
-                typeof(string),
-                typeof(string),
-                typeof(string),
-                typeof(Gcr.Collection),
-                typeof(string)
-            };
-        }
-    }
-
     public Sidebar() {
-        /* get_style_context().set_junction_sides(Gtk.JunctionSides.RIGHT | Gtk.JunctionSides.LEFT); */
-
-        // tree view
-        Gtk.TreeViewColumn col = new Gtk.TreeViewColumn();
-
-        // initial padding
-        Gtk.CellRenderer cell = new Gtk.CellRendererText();
-        col.pack_start(cell, false);
-        cell.xpad = 6;
-
-        // headings
-        Gtk.CellRendererText headings_cell = new Gtk.CellRendererText();
-        col.pack_start(headings_cell, false);
-        col.set_attributes(headings_cell, "text", Column.LABEL, null);
-        headings_cell.weight = Pango.Weight.BOLD;
-        headings_cell.weight_set = true;
-        headings_cell.ypad = 6;
-        headings_cell.xpad = 0;
-        col.set_cell_data_func(headings_cell, on_cell_renderer_heading_visible);
-
-        // icon padding
-        cell = new Gtk.CellRendererText();
-        col.pack_start(cell, false);
-        col.set_cell_data_func(cell, on_padding_cell_renderer);
-
-        // icon renderer
-        cell = new Gtk.CellRendererPixbuf();
-        col.pack_start(cell, false);
-        col.set_attributes(cell, "gicon", Column.ICON, null);
-        col.set_cell_data_func(cell, on_cell_renderer_heading_not_visible);
-
-        // normal text renderer
-        Gtk.CellRendererText text_cell = new Gtk.CellRendererText();
-        col.pack_start(text_cell, true);
-        text_cell.editable = false;
-        col.set_attributes(text_cell, "text", Column.LABEL, null);
-        col.set_cell_data_func(text_cell, on_cell_renderer_heading_not_visible);
-        text_cell.ellipsize = Pango.EllipsizeMode.END;
-        text_cell.ellipsize_set = true;
-
-        // lock/unlock icon renderer
-        this.action_cell_renderer = new Gtk.CellRendererPixbuf();
-        this.action_cell_renderer.mode = Gtk.CellRendererMode.ACTIVATABLE;
-        this.action_cell_renderer.stock_size = Gtk.IconSize.MENU;
-        this.action_cell_renderer.xpad = ACTION_BUTTON_XPAD;
-        this.action_cell_renderer.xalign = 1.0f;
-        col.pack_start(this.action_cell_renderer, false);
-        col.set_cell_data_func(this.action_cell_renderer, on_cell_renderer_action_icon);
-        col.set_max_width(24);
-        append_column(col);
-
-        set_headers_visible(false);
-        set_tooltip_column(Column.TOOLTIP);
-        set_model(this.store);
-        this.popup_menu.connect(on_popup_menu);
-        this.button_press_event.connect(on_button_press_event);
-        this.motion_notify_event.connect(on_motion_notify_event);
-        this.button_release_event.connect(on_button_release_event);
-
-        Gtk.TreeSelection selection = get_selection();
-        selection.set_mode(Gtk.SelectionMode.MULTIPLE);
-        selection.set_select_function(on_tree_selection_validate);
-        selection.changed.connect(() => update_objects_for_selection(selection));
+        GLib.Object(selection_mode: Gtk.SelectionMode.BROWSE);
+
+        bind_model(this.store, place_widget_create_cb);
+        set_header_func(place_header_cb);
+        this.selected_rows_changed.connect(update_objects);
 
         load_backends();
     }
@@ -185,11 +78,6 @@ public class Seahorse.Sidebar : Gtk.TreeView {
             foreach (weak GLib.Object obj in backend.get_objects())
                 on_place_removed (backend, (Place) obj);
         }
-
-        invalidate_sidebar_pixbufs();
-
-        if (this.update_places_sig != 0)
-            Source.remove(this.update_places_sig);
     }
 
     private void load_backends() {
@@ -210,20 +98,78 @@ public class Seahorse.Sidebar : Gtk.TreeView {
         });
     }
 
-    private void on_place_added(Gcr.Collection? places, GLib.Object obj) {
-        ((Place) obj).notify.connect(() => update_places_later());
-        update_places_later();
+    private void on_place_added(Gcr.Collection? places, GLib.Object place) {
+        place.notify.connect(() => update_places());
+        update_places();
     }
 
-    private void on_place_removed(Gcr.Collection? places, GLib.Object obj) {
-        SignalHandler.disconnect_by_func((void*) obj, (void*) update_places_later, this);
-        update_places_later();
+    private void on_place_removed(Gcr.Collection? places, GLib.Object place) {
+        SignalHandler.disconnect_by_func((void*) place, (void*) update_places, this);
+        update_places();
     }
 
     private void on_backend_changed(GLib.Object obj, ParamSpec spec) {
-        update_places_later();
+        update_places();
+    }
+
+    private Gtk.Widget place_widget_create_cb(GLib.Object object) {
+        var item = new SidebarItem(object as Seahorse.Place);
+        item.place_changed.connect(on_sidebar_item_changed);
+        return item;
+    }
+
+    private void on_sidebar_item_changed(SidebarItem item) {
+        /* current_collection_changed(); */
     }
 
+    private void place_header_cb(Gtk.ListBoxRow row, Gtk.ListBoxRow? before) {
+        Seahorse.Place place = ((SidebarItem) row).place;
+        string scheme = Uri.parse_scheme(place.uri);
+
+        // We don't need a title iff
+        // * there is no previous row
+        // * the previous row is from another backend
+        if (before != null) {
+            Seahorse.Place before_place = ((SidebarItem) before).place;
+            if (Uri.parse_scheme(before_place.uri) == scheme)
+                return;
+        }
+
+        // Find the backend that has the given scheme
+        foreach (var b in this.backends) {
+            if (place in b) {
+                var label = new Gtk.Label(b.label);
+                label.tooltip_text = b.description;
+                label.get_style_context().add_class("seahorse-sidebar-item-header");
+                label.xalign = 0f;
+                label.margin_start = 6;
+                label.margin_top = 6;
+                label.show();
+                row.set_header(label);
+                return;
+            }
+        }
+
+        warning("Couldn't find backend for place %s", place.label);
+    }
+
+    private int compare_places(GLib.Object obj_a, GLib.Object obj_b) {
+        Seahorse.Place a = (Seahorse.Place) obj_a;
+        Seahorse.Place b = (Seahorse.Place) obj_b;
+
+        // First of all, order the backends (SSH vs GPG)
+        // Since there is no easy way to map a place to its original backend,
+        // we can use the URI scheme
+        var a_scheme = GLib.Uri.parse_scheme(a.uri);
+        var b_scheme = GLib.Uri.parse_scheme(b.uri);
+        if (a_scheme != b_scheme)
+            return order_from_scheme(b_scheme) - order_from_scheme(a_scheme);
+
+        // In the same backend, order alphabetically
+        return a.label.casefold().collate(b.label.casefold());
+    }
+
+    // Keep in sync with order_from_scheme()
     private static int order_from_backend (Backend backend) {
         switch (backend.name) {
             case "gkr":
@@ -239,433 +185,274 @@ public class Seahorse.Sidebar : Gtk.TreeView {
         }
     }
 
-    private void ensure_sidebar_pixbufs() {
-        if (this.pixbuf_lock != null && this.pixbuf_lock_l != null
-            && this.pixbuf_unlock_l != null && this.pixbuf_unlock != null)
-            return;
-
-        Gtk.IconTheme icon_theme = Gtk.IconTheme.get_default();
-        Gtk.StyleContext style = get_style_context();
-
-        int height;
-        if (!Gtk.icon_size_lookup(Gtk.IconSize.MENU, out this.action_button_size, out height))
-            this.action_button_size = 16;
-
-        // Lock icon
-        Icon icon = new ThemedIcon.with_default_fallbacks("changes-prevent-symbolic");
-        Gtk.IconInfo? icon_info = icon_theme.lookup_by_gicon(icon, this.action_button_size, 
Gtk.IconLookupFlags.FORCE_SYMBOLIC);
-        if (icon_info == null)
-            return;
-        try {
-            if (this.pixbuf_lock == null)
-                this.pixbuf_lock = icon_info.load_symbolic_for_context(style, null);
-            if (this.pixbuf_lock_l == null)
-                this.pixbuf_lock_l = create_spotlight_pixbuf(this.pixbuf_lock);
-        } catch (Error e) {
-            debug("Error while looking up lock icon: %s", e.message);
-        }
-
-        // Unlock icon
-        icon = new ThemedIcon.with_default_fallbacks("changes-allow-symbolic");
-        icon_info = icon_theme.lookup_by_gicon(icon, this.action_button_size, 
Gtk.IconLookupFlags.FORCE_SYMBOLIC);
-        if (icon_info == null)
-            return;
-        try {
-            if (this.pixbuf_unlock == null)
-                this.pixbuf_unlock = icon_info.load_symbolic_for_context(style, null);
-            if (this.pixbuf_unlock_l == null)
-                this.pixbuf_unlock_l = create_spotlight_pixbuf(this.pixbuf_unlock);
-        } catch (Error e) {
-            debug("Error while looking up unlock icon: %s", e.message);
+    // Keep in sync with order_from_backend()
+    private static int order_from_scheme (string scheme) {
+        switch (scheme) {
+            case "secret-service":
+                return 0;
+            case "gnupg":
+                return 1;
+            case "pkcs11":
+                return 2;
+            case "openssh":
+                return 3;
+            default:
+                return 10;
         }
     }
 
-    private Gdk.Pixbuf? create_spotlight_pixbuf (Gdk.Pixbuf? src) {
-        Gdk.Pixbuf? dest = new Gdk.Pixbuf(src.colorspace, src.has_alpha, src.bits_per_sample,
-                                          src.width, src.height);
-
-        bool has_alpha = src.has_alpha;
-        int width = src.width;
-        int height = src.height;
-        int dst_row_stride = dest.rowstride;
-        int src_row_stride = src.rowstride;
-        uint8* target_pixels = dest.pixels;
-        uint8* original_pixels = src.pixels;
-
-        for (int i = 0; i < height; i++) {
-            uint8* pixdest = target_pixels + i * dst_row_stride;
-            uint8* pixsrc = original_pixels + i * src_row_stride;
-            for (int j = 0; j < width; j++) {
-                *pixdest++ = lighten_component (*pixsrc++);
-                *pixdest++ = lighten_component (*pixsrc++);
-                *pixdest++ = lighten_component (*pixsrc++);
-                if (has_alpha) {
-                    *pixdest++ = *pixsrc++;
+    private void update_objects() {
+        debug("Updating objects (combined: %s)", this.combined.to_string());
+
+        // Combined overrides and shows all objects
+        if (this.combined) {
+            foreach (Backend backend in this.backends) {
+                foreach (var obj in backend.get_objects()) {
+                    var place = (Place) obj;
+                    if (!this.objects.have(place))
+                        this.objects.add(place);
                 }
             }
-        }
-        return dest;
-    }
-
-    private uint8 lighten_component(uint8 cur_value) {
-        int new_value = cur_value + 24 + (cur_value >> 3);
-        return (new_value > 255)? (uint8)255 : (uint8)new_value;
-    }
-
-    private void invalidate_sidebar_pixbufs() {
-        this.pixbuf_lock = null;
-        this.pixbuf_unlock = null;
-        this.pixbuf_lock_l = null;
-        this.pixbuf_unlock_l = null;
-    }
-
-    private void next_or_append_row(Gtk.ListStore? store, ref Gtk.TreeIter iter, string? category,
-                                    Gcr.Collection? collection) {
-        // We try to keep the same row in order to preserve checked state
-        // and selections. So if the next row matches the values we want to
-        // set on it, then just keep that row.
-        //
-        // This is complicated by the fact that the first row being inserted
-        // doesn't have a valid iter, and we don't have a standard way to
-        // detect that an iter isn't valid.
-
-        // A marker that tells us the iter is not yet valid
-        if (iter.stamp == int.from_pointer(&iter) && iter.user_data3 == (&iter) &&
-            iter.user_data2 == (&iter) && iter.user_data == (&iter)) {
-            if (!store.get_iter_first(out iter))
-                store.append(out iter);
             return;
         }
 
-        if (!store.iter_next(ref iter)) {
-            store.append(out iter);
+        // Only selected ones should be in this.objects
+        var selected = get_focused_place();
+        if (selected == null)
             return;
-        }
 
-        for (;;) {
-            string? row_category;
-            Gcr.Collection? row_collection;
-            store.get(iter, Column.CATEGORY, out row_category,
-                            Column.COLLECTION, out row_collection);
-
-            if (row_category == category && row_collection == collection)
-                return;
-
-            if (!store.remove(ref iter)) {
-                store.append(out iter);
-                return;
-            }
+        foreach (var place in this.objects.elements()) {
+            if (selected != place)
+                this.objects.remove(place);
         }
+        if (!this.objects.have(selected))
+            this.objects.add(selected);
     }
 
-    private void update_objects_in_collection(bool update_chosen) {
-        if (this.updating) // Updating collection is blocked
-            return;
+    private void update_places() {
+        debug("Updating sidebar places");
 
-        bool changed = false;
-        foreach (Backend backend in this.backends) {
-            foreach (weak GLib.Object obj in backend.get_objects()) {
-                Place place = (Place) obj;
-                bool include = this.selection.lookup(place) != null;
-
-                if (update_chosen) {
-                    string? uri = place.uri;
-                    bool have = (uri in this.chosen);
-                    if (include && !have) {
-                        this.chosen.add(uri);
-                        changed = true;
-                    } else if (!include && have) {
-                        this.chosen.remove(uri);
-                        changed = true;
-                    }
-                }
+        foreach (Backend backend in this.backends)
+            update_backend(backend);
 
-                // Combined overrides and shows all objects
-                if (this.combined)
-                    include = true;
+        this.store.sort(compare_places);
 
-                bool have = this.objects.have(place);
-                if (include && !have)
-                    this.objects.add(place);
-                else if (!include && have)
-                    this.objects.remove(place);
-            }
-        }
+        // Update selection
+        /* update_objects(); */
     }
 
-    private void update_objects_for_selection(Gtk.TreeSelection selection) {
-        if (this.updating)
+    private void update_backend(Backend? backend) {
+        if (backend.get_objects() == null) // Ignore categories that have nothing
             return;
 
-        HashTable<Gcr.Collection, Gcr.Collection> selected = new HashTable<Gcr.Collection, 
Gcr.Collection>(direct_hash, direct_equal);
-        selection.selected_foreach((model, path, iter) => {
-            Gcr.Collection? collection = null;
-            model.get(iter, Column.COLLECTION, out collection, -1);
-            if (collection != null)
-                selected.insert(collection, collection);
-        });
+        foreach (weak GLib.Object obj in backend.get_objects()) {
+            unowned Place? place = obj as Place;
+            if (place == null)
+                continue;
 
-        this.selection = selected;
+            bool already_in = false;
+            for (int i = 0; i < this.store.get_n_items(); i++) {
+                if (this.store.get_object(i) == place) {
+                    already_in = true;
+                    break;
+                }
+            }
 
-        if (!this.combined)
-            update_objects_in_collection(true);
+            if (!already_in)
+                this.store.append(place);
+        }
     }
 
-    private void update_objects_for_chosen(GenericSet<string?> chosen) {
-        this.updating = true;
-
-        Gtk.TreeSelection selection = get_selection();
-
-        // Update the display
-        Gtk.TreeIter iter;
-        if (this.store.get_iter_first(out iter)) {
-            do {
-                Gcr.Collection? collection = null;
-                string? uri = null;
-                this.store.get(iter, Column.COLLECTION, out collection,
-                                     Column.URI, out uri, -1);
+    public override bool popup_menu() {
+        if (base.popup_menu())
+            return true;
 
-                if (collection != null && uri != null) {
-                    if (uri in chosen)
-                        selection.select_iter(iter);
-                    else
-                        selection.unselect_iter(iter);
-                }
-            } while (this.store.iter_next(ref iter));
-        }
+        var row = get_selected_row() as SidebarItem;
+        if (row == null)
+            return false;
 
-        this.updating = false;
-        update_objects_for_selection(selection);
+        row.show_popup_menu();
+        return true;
     }
 
-    private void update_places() {
-        Gtk.TreeIter iter = Gtk.TreeIter();
-        iter.stamp = int.from_pointer(&iter); // A marker that tells us the iter is not yet valid
-        iter.user_data3 = iter.user_data2 = iter.user_data = &iter;
+    public override bool button_press_event(Gdk.EventButton event) {
+        if (base.button_press_event(event))
+            return true;
 
-        foreach (Backend backend in this.backends)
-            update_backend(backend, ref iter);
+        if (event.button != 3 || event.type != Gdk.EventType.BUTTON_PRESS)
+            return false;
 
-        // Update selection
-        update_objects_for_chosen(this.chosen);
+        var row = get_row_at_y((int) event.y) as SidebarItem;
+        if (row != null)
+            row.show_popup_menu();
 
-        if (this.combined)
-            update_objects_in_collection(false);
+        return true;
     }
 
-    private void update_backend(Backend? backend, ref Gtk.TreeIter iter) {
-        if (backend.get_objects() == null) // Ignore categories that have nothing
-            return;
-
-        next_or_append_row(this.store, ref iter, backend.name, backend);
-        this.store.set(iter, Column.ROW_TYPE, RowType.BACKEND,
-                             Column.CATEGORY, backend.name,
-                             Column.LABEL, backend.label,
-                             Column.TOOLTIP, backend.description,
-                             Column.COLLECTION, backend);
-
-        foreach (weak GLib.Object obj in backend.get_objects()) {
-            Place place = obj as Place;
-            if (place == null)
-                continue;
+    public string[] chosen_uris_to_array() {
+        string[] results = {};
+        foreach (string? uri in this.chosen)
+            results += uri;
 
-            next_or_append_row(this.store, ref iter, backend.name, place);
-            this.store.set(iter, Column.ROW_TYPE, RowType.PLACE,
-                                 Column.CATEGORY, backend.name,
-                                 Column.LABEL, place.label,
-                                 Column.TOOLTIP, place.description,
-                                 Column.ICON, place.icon,
-                                 Column.COLLECTION, place,
-                                 Column.URI, place.uri);
-        }
-    }
+        results += null;
 
-    private void update_places_later() {
-        if (this.update_places_sig == 0) {
-            this.update_places_sig = Idle.add(() => {
-                this.update_places_sig = 0;
-                update_places();
-                return false; // don't call again
-            });
-        }
+        return results;
     }
 
-    private Lockable? lookup_lockable_for_iter(Gtk.TreeModel? model, Gtk.TreeIter? iter) {
-        Gcr.Collection? collection = null;
-        model.get(iter, Column.COLLECTION, out collection, -1);
+    public void replace_chosen_uris(string[] uris) {
+        // For quick lookups
+        GenericSet<string?> chosen = new GenericSet<string?>(str_hash, str_equal);
+        foreach (string uri in uris)
+            chosen.add(uri);
 
-        return collection as Lockable;
+        /* update_objects(); */
+        this.chosen = chosen;
     }
 
-    private void on_cell_renderer_action_icon(Gtk.CellLayout layout, Gtk.CellRenderer? cell,
-                                              Gtk.TreeModel? model, Gtk.TreeIter? iter) {
-        bool can_lock = false;
-        bool can_unlock = false;
+    public List<weak Gcr.Collection>? get_selected_places() {
+        List<weak Gcr.Collection>? places = null;
 
-        Lockable? lockable = lookup_lockable_for_iter(model, iter);
-        if (lockable != null) {
-            can_lock = lockable.lockable;
-            can_unlock = lockable.unlockable;
+        foreach (var row in get_selected_rows()) {
+            var item = row as SidebarItem;
+            if (item != null)
+                places.append(item.place);
         }
 
-        if (can_lock || can_unlock) {
-            ensure_sidebar_pixbufs();
-
-            bool highlight = false;
-            if (this.action_highlight_path != null) {
-                Gtk.TreePath? path = model.get_path(iter);
-                highlight = path.compare(this.action_highlight_path) == 0;
-            }
+        return places;
+    }
 
-            Gdk.Pixbuf? pixbuf;
-            if (can_lock)
-                pixbuf = highlight ? this.pixbuf_unlock : this.pixbuf_unlock_l;
-            else
-                pixbuf = highlight ? this.pixbuf_lock : this.pixbuf_lock_l;
+    public Place? get_focused_place() {
+        var row = get_selected_row() as SidebarItem;
+        debug("Getting focused place: %p", row);
+        return (row != null)? row.place : null;
+    }
 
-            this.action_cell_renderer.visible = true;
-            this.action_cell_renderer.pixbuf = pixbuf;
-        } else {
-            this.action_cell_renderer.visible = false;
-            this.action_cell_renderer.pixbuf = null;
+    public void set_focused_place(string uri_prefix) {
+        foreach (Backend backend in this.backends) {
+            foreach (weak GLib.Object obj in backend.get_objects()) {
+                Place place = obj as Place;
+                if (place == null)
+                    continue;
+                else if (place.uri.has_prefix(uri_prefix)) {
+                    var chosen = new GenericSet<string?>(str_hash, str_equal);
+                    chosen.add(place.uri);
+                    //XXX
+                    /* update_objects(); */
+                    return;
+                }
+            }
         }
     }
 
-    private void on_cell_renderer_heading_visible(Gtk.CellLayout layout, Gtk.CellRenderer? cell,
-                                                  Gtk.TreeModel? model, Gtk.TreeIter? iter) {
-        RowType type;
-        model.get(iter, Column.ROW_TYPE, out type, -1);
-        cell.visible = (type == RowType.BACKEND);
+    public List<weak Backend>? get_backends() {
+        return this.backends.copy();
     }
+}
 
-    private void on_padding_cell_renderer(Gtk.CellLayout layout, Gtk.CellRenderer? cell,
-                                          Gtk.TreeModel? model, Gtk.TreeIter? iter) {
-        RowType type;
-        model.get(iter, Column.ROW_TYPE, out type, -1);
+internal class Seahorse.SidebarItem : Gtk.ListBoxRow {
 
-        if (type == RowType.BACKEND) {
-            cell.visible = false;
-            cell.xpad = 0;
-            cell.ypad = 0;
-        } else {
-            cell.visible = true;
-            cell.xpad = 3;
-            cell.ypad = 3;
-        }
-    }
+    private Gtk.Button? lock_button = null;
 
-    private void on_cell_renderer_heading_not_visible(Gtk.CellLayout layout, Gtk.CellRenderer? cell,
-                                                      Gtk.TreeModel? model, Gtk.TreeIter? iter) {
-        RowType type;
-        model.get(iter, Column.ROW_TYPE, out type, -1);
-        cell.visible = (type != RowType.BACKEND);
-    }
+    public weak Seahorse.Place place { get; construct set; }
 
-    private bool on_tree_selection_validate(Gtk.TreeSelection selection, Gtk.TreeModel? model,
-                                            Gtk.TreePath? path, bool path_currently_selected) {
-        Gtk.TreeIter iter;
-        model.get_iter(out iter, path);
+    public signal void place_changed();
 
-        RowType row_type;
-        model.get(iter, Column.ROW_TYPE, out row_type, -1);
-        if (row_type == RowType.BACKEND)
-            return false;
+    construct {
+      var grid = new Gtk.Grid();
+      grid.get_style_context().add_class("seahorse-sidebar-item");
+      grid.valign = Gtk.Align.CENTER;
+      grid.row_spacing = 6;
+      grid.column_spacing = 6;
+      add(grid);
 
-        return true;
-    }
+      var icon = new Gtk.Image.from_gicon(place.icon, Gtk.IconSize.BUTTON);
+      grid.attach(icon, 0, 0);
 
-    private void place_lock(Lockable lockable, Gtk.Window? window) {
-        Cancellable cancellable = new Cancellable();
-        TlsInteraction interaction = new Interaction(window);
+      var label = new Gtk.Label(place.label);
+      label.hexpand = true;
+      label.ellipsize = Pango.EllipsizeMode.END;
+      label.xalign = 0f;
+      grid.attach(label, 1, 0);
 
-        lockable.lock.begin(interaction, cancellable, (obj, res) => {
-            try {
-                lockable.lock.end(res);
-                current_collection_changed();
-            } catch (Error e) {
-                Util.show_error(window, _("Couldn’t lock"), e.message);
-            }
-        });
-    }
+      var lockable = place as Lockable;
+      if (lockable != null && (lockable.lockable || lockable.unlockable)) {
+          this.lock_button = new Gtk.Button.from_icon_name(get_lock_icon_name(lockable),
+                                                          Gtk.IconSize.BUTTON);
+          this.lock_button.get_style_context().add_class("flat");
+          this.lock_button.clicked.connect((b) => {
+               if (lockable.unlockable)
+                   place_unlock(lockable, (Gtk.Window) get_toplevel());
+               else if (lockable.lockable)
+                   place_lock(lockable, (Gtk.Window) get_toplevel());
+
+               update_lock_icon(lockable);
+          });
+          grid.attach(this.lock_button, 2, 0);
+      }
 
-    private void on_place_lock(Gtk.MenuItem item, Lockable lockable) {
-        place_lock(lockable, (Gtk.Window) item.get_toplevel());
+      show_all();
     }
 
-    private void place_unlock(Lockable lockable, Gtk.Window? window) {
-        Cancellable cancellable = new Cancellable();
-        TlsInteraction interaction = new Interaction(window);
+    private static unowned string? get_lock_icon_name(Lockable lockable) {
+          if (lockable.unlockable)
+              return "changes-prevent-symbolic";
 
-        lockable.unlock.begin(interaction, cancellable, (obj, res) => {
-            try {
-                lockable.unlock.end(res);
-                current_collection_changed();
-            } catch (Error e) {
-                Util.show_error(window, _("Couldn’t unlock"), e.message);
-            }
-        });
+          if (lockable.lockable)
+              return "changes-allow-symbolic";
+
+          return null;
     }
 
-    private void on_place_unlock(Gtk.MenuItem item, Lockable lockable) {
-        place_unlock(lockable, (Gtk.Window) item.get_toplevel());
+    private void update_lock_icon(Lockable lockable) {
+        ((Gtk.Image) this.lock_button.get_image()).icon_name = get_lock_icon_name(lockable);
     }
 
-    private void on_place_delete(Gtk.MenuItem item, Deletable deletable) {
-        Deleter deleter = deletable.create_deleter();
-        if (deleter.prompt((Gtk.Window) item.get_toplevel())) {
-            deleter.delete.begin(null, (obj, res) => {
-                try {
-                    deleter.delete.end(res);
-                } catch (Error e) {
-                    Util.show_error(parent, _("Couldn’t delete"), e.message);
-                }
-            });
-        }
+    public SidebarItem(Seahorse.Place place) {
+        GLib.Object(place: place);
     }
 
-    private void popup_menu_for_place(Place place) {
-        // Start from the menu model provided by the place (if any)
-        var menu = (place.menu_model != null)? new Gtk.Menu.from_model(place.menu_model)
-                                             : new Gtk.Menu();
+    public void show_popup_menu() {
+        // Start from the menu model provided by the this.place (if any)
+        var menu = (this.place.menu_model != null)? new Gtk.Menu.from_model(this.place.menu_model)
+                                                  : new Gtk.Menu();
 
         // Make sure the actions from the collection
-        if (place.actions != null)
-            menu.insert_action_group(place.action_prefix, place.actions);
+        if (this.place.actions != null)
+            menu.insert_action_group(this.place.action_prefix, this.place.actions);
 
         // Lock and unlock items
-        if (place is Lockable) {
+        if (this.place is Lockable) {
             Gtk.MenuItem item = new Gtk.MenuItem.with_mnemonic(_("_Lock"));
-            item.activate.connect(() => on_place_lock(item, (Lockable) place));
-            place.bind_property("lockable", item, "visible", BindingFlags.SYNC_CREATE);
+            item.activate.connect(() => on_place_lock(item, (Lockable) this.place));
+            this.place.bind_property("lockable", item, "visible", BindingFlags.SYNC_CREATE);
             menu.append(item);
 
             item = new Gtk.MenuItem.with_mnemonic(_("_Unlock"));
-            item.activate.connect(() => on_place_unlock(item, (Lockable) place));
-            place.bind_property("unlockable", item, "visible", BindingFlags.SYNC_CREATE);
+            item.activate.connect(() => on_place_unlock(item, (Lockable) this.place));
+            this.place.bind_property("unlockable", item, "visible", BindingFlags.SYNC_CREATE);
             menu.append(item);
         }
 
         // Delete item
-        if (place is Deletable) {
+        if (this.place is Deletable) {
             Gtk.MenuItem item = new Gtk.MenuItem.with_mnemonic(_("_Delete"));
-            item.activate.connect(() => on_place_delete(item, (Deletable) place));
-            place.bind_property("deletable", item, "sensitive", BindingFlags.SYNC_CREATE);
+            item.activate.connect(() => on_place_delete(item, (Deletable) this.place));
+            this.place.bind_property("deletable", item, "sensitive", BindingFlags.SYNC_CREATE);
             menu.append(item);
             item.show();
         }
 
         // Properties item
-        if (place is Viewable) {
+        if (this.place is Viewable) {
             Gtk.MenuItem item = new Gtk.MenuItem.with_mnemonic(_("_Properties"));
-            item.activate.connect(() => Viewable.view(place, (Gtk.Window) item.get_toplevel()));
+            item.activate.connect(() => Viewable.view(this.place, (Gtk.Window) item.get_toplevel()));
             menu.append(item);
             item.show();
         }
 
         bool visible = false;
         menu.foreach((widget) => {
-            if (widget.visible)
-                visible = true;
+            visible |= widget.visible;
         });
 
         if (visible) {
@@ -677,246 +464,54 @@ public class Seahorse.Sidebar : Gtk.TreeView {
         }
     }
 
-    private bool on_popup_menu(Gtk.Widget? widget) {
-        Gtk.TreePath? path;
-        get_cursor(out path, null);
-        if (path == null)
-            return false;
-
-        Gtk.TreeIter iter;
-        if (!this.store.get_iter(out iter, path))
-            return false;
-
-        Gcr.Collection? collection;
-        this.store.get(iter, Column.COLLECTION, out collection, -1);
-
-        if (collection is Place) {
-            popup_menu_for_place((Place) collection);
-            return true;
-        }
-
-        return false;
-    }
-
-    private void update_action_buttons_take_path(Gtk.TreePath? path) {
-        if (path == this.action_highlight_path)
-            return;
-
-        if (path != null && this.action_highlight_path != null &&
-            this.action_highlight_path.compare(path) == 0) {
-            return;
-        }
-
-        Gtk.TreePath? old_path = this.action_highlight_path;
-        this.action_highlight_path = path;
-
-        Gtk.TreeIter? iter = null;
-        if (this.action_highlight_path != null
-              && this.store.get_iter(out iter, this.action_highlight_path))
-            this.store.row_changed(this.action_highlight_path, iter);
-
-        if (old_path != null && this.store.get_iter(out iter, old_path))
-            this.store.row_changed(old_path, iter);
-    }
-
-    private bool over_action_button(int x, int y, out Gtk.TreePath? path) {
-
-        path = null;
-        Gtk.TreeViewColumn column;
-        if (get_path_at_pos(x, y, out path, out column, null, null)) {
-            Gtk.TreeIter iter;
-            this.store.get_iter(out iter, path);
-
-            int hseparator;
-            style_get("horizontal-separator", out hseparator, null);
-
-            // Reload cell attributes for this particular row
-            column.cell_set_cell_data(this.store, iter, false, false);
-            int width, x_offset;
-            column.cell_get_position(this.action_cell_renderer, out x_offset, out width);
-
-            // This is kinda weird, but we have to do it to workaround gtk+ expanding
-            // the eject cell renderer (even thought we told it not to) and we then
-            // had to set it right-aligned
-            x_offset += width - hseparator - ACTION_BUTTON_XPAD - this.action_button_size;
-
-            if (x - x_offset >= 0 && x - x_offset <= this.action_button_size)
-                return true;
-        }
-
-        if (path != null)
-            path = null;
-
-        return false;
-    }
-
-    private bool on_motion_notify_event(Gtk.Widget? widget, Gdk.EventMotion event) {
-        Gtk.TreePath? path = null;
-        if (over_action_button((int) event.x, (int) event.y, out path)) {
-            update_action_buttons_take_path(path);
-            return true;
-        }
-
-        update_action_buttons_take_path(null);
-        return false;
-    }
-
-    private bool on_button_press_event (Gtk.Widget? widget, Gdk.EventButton event) {
-        if (event.button != 3 || event.type != Gdk.EventType.BUTTON_PRESS)
-            return false;
-
-        Gtk.TreePath? path;
-        if (!get_path_at_pos((int) event.x, (int) event.y, out path, null, null, null))
-            return false;
-
-        set_cursor(path, null, false);
-        Gtk.TreeIter iter;
-        if (!this.store.get_iter(out iter, path))
-            return false;
-
-        Gcr.Collection? collection;
-        this.store.get(iter, Column.COLLECTION, out collection, -1);
-
-        if (collection is Place)
-            popup_menu_for_place((Place) collection);
-
-        return true;
-    }
-
-    private bool on_button_release_event (Gtk.Widget? widget, Gdk.EventButton event) {
-        if (event.type != Gdk.EventType.BUTTON_RELEASE)
-            return true;
-
-        Gtk.TreePath? path;
-        if (!over_action_button((int) event.x, (int) event.y, out path))
-            return false;
-
-        Gtk.TreeIter iter;
-        if (!this.store.get_iter(out iter, path))
-            return false;
-
-        Gtk.Window? window = (Gtk.Window) widget.get_toplevel();
-
-        Lockable? lockable = lookup_lockable_for_iter(this.store, iter);
-        if (lockable != null) {
-            if (lockable.lockable)
-                place_lock(lockable, window);
-            else if (lockable.unlockable)
-                place_unlock(lockable, window);
-        }
-
-        return true;
-    }
-
-    public string[] chosen_uris_to_array() {
-        string[] results = {};
-        foreach (string? uri in this.chosen)
-            results += uri;
-
-        results += null;
+    private void place_lock(Lockable lockable, Gtk.Window? window) {
+        Cancellable cancellable = new Cancellable();
+        TlsInteraction interaction = new Interaction(window);
 
-        return results;
+        lockable.lock.begin(interaction, cancellable, (obj, res) => {
+            try {
+                lockable.lock.end(res);
+                update_lock_icon (lockable);
+                place_changed();
+            } catch (Error e) {
+                Util.show_error(window, _("Couldn’t lock"), e.message);
+            }
+        });
     }
 
-    public void replace_chosen_uris(string[] uris) {
-        // For quick lookups
-        GenericSet<string?> chosen = new GenericSet<string?>(str_hash, str_equal);
-        foreach (string uri in uris)
-            chosen.add(uri);
-
-        update_objects_for_chosen(chosen);
-        this.chosen = chosen;
+    private void on_place_lock(Gtk.Widget widget, Lockable lockable) {
+        place_lock(lockable, (Gtk.Window) widget.get_toplevel());
     }
 
-    public List<weak Gcr.Collection>? get_selected_places() {
-        List<weak Gcr.Collection> places = this.objects.elements();
-
-        Gtk.TreePath? path = null;
-        get_cursor(out path, null);
-        if (path != null) {
-
-            Gtk.TreeIter iter;
-            if (!this.store.get_iter(out iter, path))
-                return null;
-
-            Gcr.Collection? collection;
-            RowType row_type;
-            this.store.get(iter, Column.ROW_TYPE, out row_type,
-                                 Column.COLLECTION, out collection, -1);
+    private void place_unlock(Lockable lockable, Gtk.Window? window) {
+        Cancellable cancellable = new Cancellable();
+        TlsInteraction interaction = new Interaction(window);
 
-            if (collection != null) {
-                if (row_type == RowType.PLACE) {
-                    places.remove(collection);
-                    places.prepend(collection);
-                }
+        lockable.unlock.begin(interaction, cancellable, (obj, res) => {
+            try {
+                lockable.unlock.end(res);
+                update_lock_icon (lockable);
+                place_changed();
+            } catch (Error e) {
+                Util.show_error(window, _("Couldn’t unlock"), e.message);
             }
-        }
-
-        return places;
-    }
-
-    public Place? get_focused_place() {
-        Gtk.TreeIter iter;
-
-        Gtk.TreePath? path = null;
-        get_cursor(out path, null);
-        if (path != null) {
-            if (!this.store.get_iter(out iter, path))
-                return null;
-
-            Gcr.Collection? collection;
-            RowType row_type;
-            this.store.get(iter, Column.ROW_TYPE, out row_type,
-                                 Column.COLLECTION, out collection, -1);
-
-            if (row_type == RowType.PLACE)
-                return (Place) collection;
-        }
-
-        return null;
+        });
     }
 
-    public void set_focused_place(string uri_prefix) {
-        foreach (Backend backend in this.backends) {
-            foreach (weak GLib.Object obj in backend.get_objects()) {
-                Place place = obj as Place;
-                if (place == null)
-                    continue;
-                else if (place.uri.has_prefix(uri_prefix)) {
-                    var chosen = new GenericSet<string?>(str_hash, str_equal);
-                    chosen.add(place.uri);
-                    this.update_objects_for_chosen(chosen);
-                    return;
-                }
-            }
-        }
+    private void on_place_unlock(Gtk.MenuItem widget, Lockable lockable) {
+        place_unlock(lockable, (Gtk.Window) widget.get_toplevel());
     }
 
-    public List<weak Backend>? get_backends() {
-        Gtk.TreeIter iter;
-
-        List<weak Backend> backends = this.backends.copy();
-        backends.reverse();
-
-        Gtk.TreePath? path = null;
-        get_cursor(out path, null);
-        if (path != null) {
-            if (!this.store.get_iter(out iter, path))
-                return null;
-
-            Gcr.Collection? collection;
-            RowType row_type;
-            this.store.get(iter, Column.ROW_TYPE, out row_type,
-                                 Column.COLLECTION, out collection, -1);
-
-            if (collection != null) {
-                if (row_type == RowType.BACKEND) {
-                    backends.remove((Backend) collection);
-                    backends.prepend((Backend) collection);
+    private void on_place_delete(Gtk.MenuItem item, Deletable deletable) {
+        Deleter deleter = deletable.create_deleter();
+        if (deleter.prompt((Gtk.Window) item.get_toplevel())) {
+            deleter.delete.begin(null, (obj, res) => {
+                try {
+                    deleter.delete.end(res);
+                } catch (Error e) {
+                    Util.show_error(parent, _("Couldn’t delete"), e.message);
                 }
-            }
+            });
         }
-
-        return backends;
     }
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]