[seahorse] Common: Port KeyManagerStore to Vala.



commit 0c299f6d21f9909bca80f3df850b1b477167229c
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Sun Jan 28 23:38:12 2018 +0100

    Common: Port KeyManagerStore to Vala.

 common/collection.vala                   |    4 +-
 common/config.vapi                       |    6 +
 common/key-manager-store.vala            |  505 ++++++++++++++++++
 common/meson.build                       |    2 +
 gkr/meson.build                          |    1 -
 libseahorse/meson.build                  |    1 -
 libseahorse/seahorse-key-manager-store.c |  818 ------------------------------
 libseahorse/seahorse-key-manager-store.h |   77 ---
 pgp/meson.build                          |    1 -
 pgp/seahorse-keyserver-results.c         |    1 -
 pkcs11/meson.build                       |    1 -
 po/POTFILES.in                           |    2 +-
 po/POTFILES.skip                         |    1 +
 src/seahorse-key-manager.c               |    1 -
 ssh/meson.build                          |    1 -
 15 files changed, 517 insertions(+), 905 deletions(-)
---
diff --git a/common/collection.vala b/common/collection.vala
index ae63e37..bdab17f 100644
--- a/common/collection.vala
+++ b/common/collection.vala
@@ -21,7 +21,7 @@
 public class Seahorse.Collection : Gcr.Collection, GLib.Object {
     private GenericSet<GLib.Object?> objects = new GenericSet<GLib.Object?>(direct_hash, direct_equal);
 
-    private DestroyNotify destroy_func;
+    private DestroyNotify? destroy_func;
 
     /**
      * Base_collection collection
@@ -33,7 +33,7 @@ public class Seahorse.Collection : Gcr.Collection, GLib.Object {
      */
     public Predicate? predicate { get; private set; }
 
-    public Collection.for_predicate (Gcr.Collection base_collection, Predicate? pred, DestroyNotify 
destroy_func) {
+    public Collection.for_predicate (Gcr.Collection base_collection, Predicate? pred, DestroyNotify? 
destroy_func) {
         GLib.Object (base_collection: base_collection);
         this.predicate = pred;
 
diff --git a/common/config.vapi b/common/config.vapi
index 0e4ed5a..b722504 100644
--- a/common/config.vapi
+++ b/common/config.vapi
@@ -46,3 +46,9 @@ namespace Progress {
 }
 
 }
+
+namespace Egg {
+       namespace TreeMultiDrag {
+               public void add_drag_support(Gtk.TreeView view);
+       }
+}
diff --git a/common/key-manager-store.vala b/common/key-manager-store.vala
new file mode 100644
index 0000000..9f216e2
--- /dev/null
+++ b/common/key-manager-store.vala
@@ -0,0 +1,505 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2003 Jacob Perkins
+ * Copyright (C) 2004, 2005, 2006 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2018 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+public class Seahorse.KeyManagerStore : Gcr.CollectionModel {
+
+    private const string XDS_FILENAME = "xds.txt";
+    private const size_t MAX_XDS_ATOM_VAL_LEN = 4096;
+    private static Gdk.Atom XDS_ATOM = Gdk.Atom.intern("XdndDirectSave0", false);
+    private static Gdk.Atom TEXT_ATOM = Gdk.Atom.intern("text/plain", false);
+
+    public enum Mode {
+        ALL,
+        FILTERED
+    }
+
+    private enum DragInfo {
+        TEXT,
+        XDS,
+    }
+
+    private const Gtk.TargetEntry[] store_targets = {
+        { "text/plain", 0, DragInfo.TEXT },
+        { "XdndDirectSave0", 0, DragInfo.XDS }
+    };
+
+    private uint filter_stag;
+
+    private string? drag_destination;
+    private GLib.Error? drag_error;
+    private List<GLib.Object>? drag_objects;
+
+    /**
+     * Manager Settings
+     */
+    public GLib.Settings settings { get; construct set; }
+
+    /**
+     * Key store mode controls which keys to display
+     */
+    public Mode filter_mode {
+        get { return this._filter_mode; }
+        set {
+            if (this._filter_mode != value) {
+                this._filter_mode = value;
+                refilter_later();
+            }
+        }
+    }
+    private Mode _filter_mode;
+
+    /**
+     * Key store filter for when in filtered mode
+     */
+    public string? filter {
+        get { return (this.filter_mode == Mode.FILTERED)? this._filter : ""; }
+        set {
+            // If we're not in filtered mode and there is text OR
+            // we're in filtered mode (regardless of text or not)
+            // then update the filter
+            if ((this._filter_mode != Mode.FILTERED && value != null && value != "")
+                || (this._filter_mode == Mode.FILTERED)) {
+                this._filter_mode = Mode.FILTERED;
+
+                // We always use lower case text (see filter_callback)
+                this._filter = value.down();
+                refilter_later ();
+            }
+        }
+    }
+    private string? _filter;
+
+    private enum Column {
+        ICON,
+        MARKUP,
+        LABEL,
+        DESCRIPTION,
+        N_COLS;
+    }
+
+    // This is a bit ugly, but we can't use const here due to the typeof-s
+    private static Gcr.Column[] _columns = {
+        Gcr.Column() { property_name = "icon", property_type = typeof(Icon), column_type = typeof(Icon) },
+        Gcr.Column() { property_name = "markup", property_type = typeof(string), column_type = 
typeof(string) },
+        Gcr.Column() { property_name = "label", property_type = typeof(string), column_type = 
typeof(string), flags = Gcr.ColumnFlags.SORTABLE },
+        Gcr.Column() { property_name = "description", property_type = typeof(string), column_type = 
typeof(string) },
+        Gcr.Column()
+    };
+
+
+    construct {
+        // FIXME: this is (again) due to broken bindings.
+        set("columns", _columns,
+            "mode", Gcr.CollectionModelMode.LIST,
+            null);
+    }
+
+    public KeyManagerStore (Gcr.Collection? collection, Gtk.TreeView? view, Predicate? pred, GLib.Settings 
settings) {
+        Collection filtered = new Collection.for_predicate (collection, pred, null);
+        pred.custom = on_filter_visible;
+        pred.custom_target = this;
+
+        GLib.Object (
+            collection: filtered,
+            settings: settings
+        );
+
+        // The sorted model is the top level model
+        view.model = this;
+
+        // add the icon column
+        Gtk.CellRendererPixbuf icon_renderer = new Gtk.CellRendererPixbuf();
+        icon_renderer.stock_size = Gtk.IconSize.DND;
+        icon_renderer.ypad = 6;
+        icon_renderer.yalign = 0.0f;
+        Gtk.TreeViewColumn? col = new Gtk.TreeViewColumn.with_attributes ("", icon_renderer, "gicon", 
Column.ICON, null);
+        col.set_resizable(false);
+        view.append_column(col);
+
+        // Name column
+        col = new Gtk.TreeViewColumn();
+
+        Gtk.CellRendererText text_renderer = new Gtk.CellRendererText();
+        text_renderer.ypad = 6;
+        text_renderer.yalign = 0.0f;
+        text_renderer.ellipsize = Pango.EllipsizeMode.END;
+        col.pack_start(text_renderer, true);
+        col.set_attributes(text_renderer, "markup", Column.MARKUP, null);
+
+        text_renderer = new Gtk.CellRendererText();
+        text_renderer.ypad = 6;
+        text_renderer.xpad = 3;
+        text_renderer.yalign = 0.0f;
+        text_renderer.xalign = 1.0f;
+        text_renderer.scale = Pango.Scale.SMALL;
+        text_renderer.alignment = Pango.Alignment.RIGHT;
+        col.pack_start(text_renderer, false);
+        col.set_attributes(text_renderer, "markup", Column.DESCRIPTION, null);
+        col.set_resizable(true);
+        col.set_expand(true);
+        view.append_column(col);
+        col.set_sort_column_id(Column.LABEL);
+
+        // Use predicate to figure out which columns to add
+        if (collection is Collection)
+            pred = ((Collection) collection).predicate;
+        else
+            pred = null;
+
+        // Also watch for sort-changed on the store
+        this.sort_column_changed.connect(on_sort_column_changed);
+
+        // Update sort order in case the sorted column was added
+        string? sort_by = settings.get_string("sort-by");
+        if (sort_by != null)
+            set_sort_to(sort_by);
+
+        view.set_enable_search(false);
+        view.set_show_expanders(false);
+        view.set_rules_hint(true);
+        view.set_headers_visible(false);
+
+        set_sort_column_id (Column.LABEL, Gtk.SortType.ASCENDING);
+
+        // Tree drag
+        Egg.TreeMultiDrag.add_drag_support (view);
+
+        view.drag_data_get.connect(drag_data_get);
+        view.drag_begin.connect(drag_begin);
+        view.drag_end.connect(drag_end);
+
+        Gtk.drag_source_set (view, Gdk.ModifierType.BUTTON1_MASK, store_targets, Gdk.DragAction.COPY);
+    }
+
+    ~KeyManagerStore() {
+        SignalHandler.disconnect_by_func((void*) this, (void*) on_sort_column_changed, (void*) this);
+    }
+
+    // Search through row for text
+    private bool object_contains_filtered_text (GLib.Object? object, string? text) {
+        // Empty search text results in a match
+        if (text == null || text == "")
+            return true;
+
+        string? name = null;
+        object.get("label", out name, null);
+        if (name != null && (text in name.down()))
+            return true;
+
+        if (object.get_class().find_property("description") != null) {
+            string? description = null;
+            object.get("description", out description, null);
+            if (description != null && (text in description.down()))
+                return true;
+        }
+
+        return false;
+    }
+
+    // Called to filter each row
+    private static bool on_filter_visible (GLib.Object? obj, void* custom_target) {
+        KeyManagerStore self = (KeyManagerStore) custom_target;
+
+        // Check the row requested
+        switch (self.filter_mode) {
+            case Mode.FILTERED:
+                return self.object_contains_filtered_text (obj, self.filter);
+            case Mode.ALL:
+                return true;
+            default:
+                assert_not_reached();
+        };
+    }
+
+    public void refilter() {
+        ((Collection) get_collection()).refresh();
+    }
+
+    // Refilter the tree after a timeout has passed
+    private void refilter_later() {
+        if (this.filter_stag != 0)
+            Source.remove (this.filter_stag);
+
+        this.filter_stag = Timeout.add (200, () => {
+            this.filter_stag = 0;
+            refilter();
+            return false;
+        });
+    }
+
+    // Update the sort order for a column
+    private void set_sort_to(string name) {
+        // Prefix with a minus means descending
+        Gtk.SortType ord = (name[0] == '-')? Gtk.SortType.DESCENDING : Gtk.SortType.ASCENDING;
+
+        string without_sign = name;
+        if (name[0] == '-' || name[0] == '+')
+            without_sign = name.substring(1);
+
+        // Find the column sort id
+        int id = -1;
+        for (int i = Column.N_COLS - 1; i >= 0 ; i--) {
+            string? n = (string?) _columns[i].user_data;
+            if (n != null && name == n) {
+                id = i;
+                break;
+            }
+        }
+
+        if (id != -1)
+            set_sort_column_id(id, ord);
+    }
+
+    // Called when the column sort is changed
+    private void on_sort_column_changed (Gtk.TreeSortable sort) {
+        if (this.settings == null)
+            return;
+
+        // We have a sort so save it
+        int column_id;
+        Gtk.SortType ord;
+        if (get_sort_column_id (out column_id, out ord)) {
+            if (column_id >= 0 && column_id < Column.N_COLS) {
+                if (_columns[column_id].user_data != null) {
+                    string sign = (ord == Gtk.SortType.DESCENDING)? "-" : "";
+                    this.settings.set_string("sort-by", sign + (string) _columns[column_id].user_data);
+                }
+            }
+
+        // No sort so save blank
+        } else if (this.settings != null) {
+            this.settings.set_string ("sort-by", "");
+        }
+    }
+
+    /* The following three functions taken from bugzilla
+     * (http://bugzilla.gnome.org/attachment.cgi?id=49362&action=view)
+     * Author: Christian Neumair
+     * Copyright: 2005 Free Software Foundation, Inc
+     * License: GPL */
+    private string? xds_get_atom_value (Gdk.DragContext? context) {
+        if (context == null)
+            return null;
+
+        Gdk.Window source_window = context.get_source_window();
+
+        uint8[] ret;
+        int len;
+        if (Gdk.property_get (source_window, XDS_ATOM, TEXT_ATOM, 0, MAX_XDS_ATOM_VAL_LEN,
+                              0, null, out len, out ret)) {
+            ret[len]='\0';
+            return (string) ret;
+        }
+
+        return null;
+    }
+
+    private bool xds_context_offers_target (Gdk.DragContext? context, Gdk.Atom target) {
+        return context.list_targets().find(target) != null;
+    }
+
+    private bool xds_is_dnd_valid_context (Gdk.DragContext? context) {
+        if (context == null)
+            return false;
+
+        string? tmp = null;
+        if (xds_context_offers_target (context, XDS_ATOM))
+            tmp = xds_get_atom_value (context);
+
+        return (tmp != null);
+    }
+
+    private void drag_begin (Gtk.Widget widget, Gdk.DragContext context) {
+        debug("drag_begin -.");
+
+        this.drag_destination = null;
+        this.drag_error = null;
+        this.drag_objects = null;
+
+        this.drag_objects = KeyManagerStore.get_selected_objects((Gtk.TreeView) widget);
+
+        if (this.drag_objects != null) {
+            Gdk.Window? source_window = context.get_source_window();
+            Gdk.property_change (source_window, XDS_ATOM, TEXT_ATOM, 8, Gdk.PropMode.REPLACE,
+                                 (uint8[]) XDS_FILENAME, XDS_FILENAME.length);
+        }
+
+        debug("drag_begin <--");
+    }
+
+    private bool export_to_text(Gtk.SelectionData selection_data) {
+        if (this.drag_objects == null)
+            return false;
+        debug("exporting to text");
+
+        uint8[] output;
+        uint count = 0;
+        try {
+            count = Exportable.export_to_text_wait(this.drag_objects, out output);
+        } catch (Error e) {
+            this.drag_error = e;
+            message ("error occurred on export: %s", this.drag_error.message);
+            return false;
+        }
+
+        // TODO: Need to print status if only partially exported
+
+        if (count > 0) {
+            debug("setting selection text");
+            selection_data.set_text ((string) output, output.length);
+            return true;
+        }
+
+        message ("no objects exported");
+        return false;
+    }
+
+    private bool export_to_directory (string directory) {
+        debug("exporting to %s", directory);
+        try {
+            Exportable.export_to_directory_wait(this.drag_objects, directory);
+        } catch (Error e) {
+            this.drag_error = e;
+            return false;
+        }
+        return true;
+    }
+
+    private void drag_data_get (Gtk.Widget widget, Gdk.DragContext context, Gtk.SelectionData selection_data,
+                   uint info, uint time) {
+        if (this.drag_objects == null)
+            return;
+
+        debug("drag_data_get %u -.", info);
+
+        // The caller wants plain text
+        if (info == DragInfo.TEXT) {
+            debug("returning object text");
+            export_to_text(selection_data);
+
+        // The caller wants XDS
+        } else if (info == DragInfo.XDS) {
+
+            if (xds_is_dnd_valid_context (context)) {
+                string? destination = xds_get_atom_value (context);
+                if (destination == null)
+                    return;
+                this.drag_destination = Path.get_dirname (destination);
+
+                selection_data.set(selection_data.get_target(), 8, (uint8[]) "S");
+            }
+
+        // Unrecognized format
+        } else {
+            debug("Unrecognized format: %u", info);
+        }
+
+        debug("drag_data_get <--");
+    }
+
+    private void drag_end(Gtk.Widget? widget, Gdk.DragContext? context) {
+        debug("drag_end -.");
+
+        if (this.drag_destination != null && this.drag_error == null)
+            export_to_directory(this.drag_destination);
+
+        if (this.drag_error != null) {
+            DBusError.strip_remote_error (this.drag_error);
+            Util.show_error (widget, _("Couldn’t export keys"), this.drag_error.message);
+        }
+
+        this.drag_error = null;
+        this.drag_objects = null;
+        this.drag_destination = null;
+
+        debug("drag_end <--");
+    }
+
+    public static GLib.Object? get_object_from_path (Gtk.TreeView view, Gtk.TreePath? path) {
+        Gtk.TreeIter? iter;
+        if (view.model.get_iter (out iter, path))
+            return null;
+        return ((Gcr.CollectionModel) view.model).object_for_iter(iter);
+    }
+
+    public List<GLib.Object> get_all_objects() {
+        return get_collection().get_objects();
+    }
+
+    public static List<GLib.Object> get_selected_objects (Gtk.TreeView view) {
+        List<Gtk.TreePath> paths = view.get_selection().get_selected_rows(null);
+
+        // make object list
+        List<GLib.Object> objects = new List<GLib.Object>();
+        foreach (Gtk.TreePath path in paths) {
+            GLib.Object? obj = KeyManagerStore.get_object_from_path (view, path);
+            if (obj != null)
+                objects.append(obj);
+        }
+
+        // Remove duplicates
+        objects.sort((a, b) => {
+            if (a == b)
+                return 0;
+            return ((void*) a > (void*) b)? 1 : -1;
+        });
+
+        unowned List<GLib.Object>? l = null;
+        for (l = objects; l != null; l = l.next) {
+            while (l.next != null && l.data == l.next.data)
+                objects.delete_link(l.next);
+        }
+
+        return objects;
+    }
+
+    public static void set_selected_objects(Gtk.TreeView view, List<GLib.Object> objects) {
+        Gtk.TreeSelection? selection = view.get_selection();
+        selection.unselect_all();
+
+        bool first = true;
+        foreach (GLib.Object obj in objects) {
+            Gtk.TreeIter iter = Gtk.TreeIter();
+            if (((Gcr.CollectionModel) view.model).iter_for_object (obj, iter)) {
+                selection.select_iter(iter);
+
+                // Scroll the first row selected into view
+                if (first) {
+                    Gtk.TreePath? path = view.model.get_path(iter);
+                    view.scroll_to_cell(path, null, false, 0.0f, 0.0f);
+                    first = false;
+                }
+            }
+        }
+    }
+
+    public GLib.Object? get_selected_object (Gtk.TreeView? view) {
+        List<Gtk.TreePath>? paths = view.get_selection().get_selected_rows(null);
+
+        // choose first object
+        GLib.Object? obj = null;
+        if (paths != null)
+            obj = get_object_from_path(view, paths.data);
+
+        return obj;
+    }
+}
diff --git a/common/meson.build b/common/meson.build
index e2eca83..3154305 100644
--- a/common/meson.build
+++ b/common/meson.build
@@ -10,6 +10,7 @@ common_sources = [
   'exportable.vala',
   'exporter.vala',
   'icons.vala',
+  'key-manager-store.vala',
   'lockable.vala',
   'object.vala',
   'passphrase-prompt.vala',
@@ -50,4 +51,5 @@ common_lib = static_library('common',
 common_dep = declare_dependency(
   link_with: common_lib,
   include_directories: include_directories('.'),
+  dependencies: common_deps,
 )
diff --git a/gkr/meson.build b/gkr/meson.build
index 9898004..10b0e11 100644
--- a/gkr/meson.build
+++ b/gkr/meson.build
@@ -16,7 +16,6 @@ gkr_dependencies = [
   gcr,
   gcr_ui,
   libsecret,
-  config,
   common_dep,
 ]
 
diff --git a/libseahorse/meson.build b/libseahorse/meson.build
index 5c85791..c1a7bbc 100644
--- a/libseahorse/meson.build
+++ b/libseahorse/meson.build
@@ -14,7 +14,6 @@ libseahorse_sources = [
   'seahorse-application.c',
   'seahorse-bind.c',
   'seahorse-interaction.c',
-  'seahorse-key-manager-store.c',
   'seahorse-object-list.c',
   'seahorse-object-model.c',
   'seahorse-object-widget.c',
diff --git a/pgp/meson.build b/pgp/meson.build
index 53814f4..c81ad46 100644
--- a/pgp/meson.build
+++ b/pgp/meson.build
@@ -41,7 +41,6 @@ pgp_dependencies = [
   glib_deps,
   gcr,
   gpgme,
-  config,
   common_dep,
 ]
 
diff --git a/pgp/seahorse-keyserver-results.c b/pgp/seahorse-keyserver-results.c
index 92de1e9..330f1fd 100644
--- a/pgp/seahorse-keyserver-results.c
+++ b/pgp/seahorse-keyserver-results.c
@@ -27,7 +27,6 @@
 #include "seahorse-gpgme-keyring.h"
 #include "seahorse-keyserver-search.h"
 
-#include "libseahorse/seahorse-key-manager-store.h"
 #include "libseahorse/seahorse-progress.h"
 #include "libseahorse/seahorse-util.h"
 
diff --git a/pkcs11/meson.build b/pkcs11/meson.build
index 977f964..d5e6518 100644
--- a/pkcs11/meson.build
+++ b/pkcs11/meson.build
@@ -17,7 +17,6 @@ pkcs11_deps = [
   glib_deps,
   gcr_ui,
   pkcs11_dep,
-  config,
   common_dep,
 ]
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 3eb150f..7c3be1e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -4,6 +4,7 @@ common/add-keyserver-dialog.vala
 common/catalog.vala
 common/delete-dialog.vala
 common/exportable.vala
+common/key-manager-store.vala
 common/object.vala
 common/passphrase-prompt.vala
 common/prefs.vala
@@ -27,7 +28,6 @@ gkr/seahorse-gkr-keyring.ui
 libegg/egg-datetime.c
 libseahorse/seahorse-application.c
 libseahorse/seahorse-interaction.c
-libseahorse/seahorse-key-manager-store.c
 libseahorse/seahorse-progress.ui
 libseahorse/seahorse-search-provider.c
 libseahorse/seahorse-util.c
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 92d125a..300652e 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -4,6 +4,7 @@ common/catalog.c
 common/delete-dialog.c
 common/exportable.c
 common/object.c
+common/key-manager-store.c
 common/passphrase-prompt.c
 common/prefs.c
 common/util.c
diff --git a/src/seahorse-key-manager.c b/src/seahorse-key-manager.c
index b574e70..77161cd 100644
--- a/src/seahorse-key-manager.c
+++ b/src/seahorse-key-manager.c
@@ -22,7 +22,6 @@
 #include "config.h"
 
 #include "libseahorse/seahorse-application.h"
-#include "libseahorse/seahorse-key-manager-store.h"
 #include "libseahorse/seahorse-progress.h"
 #include "libseahorse/seahorse-util.h"
 
diff --git a/ssh/meson.build b/ssh/meson.build
index 151f4be..efb0719 100644
--- a/ssh/meson.build
+++ b/ssh/meson.build
@@ -20,7 +20,6 @@ ssh_dependencies = [
   gcr,
   posix,
   gtk,
-  config,
   common_dep,
 ]
 


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