[seahorse/wip/nielsdg/src-port] WIP: port /src to Vala.
- From: Niels De Graef <nielsdg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [seahorse/wip/nielsdg/src-port] WIP: port /src to Vala.
- Date: Sat, 10 Feb 2018 09:57:59 +0000 (UTC)
commit 628a049cc0d1fd0dc4a236e93e3823fa9fad8bd4
Author: Niels De Graef <nielsdegraef gmail com>
Date: Sun Jan 28 14:40:37 2018 +0100
WIP: port /src to Vala.
common/catalog.vala | 3 -
common/config.vapi | 16 +-
gkr/meson.build | 5 +
libseahorse/meson.build | 9 -
libseahorse/seahorse-application.c | 248 ---
libseahorse/seahorse-application.h | 49 -
libseahorse/seahorse-search-provider.c | 589 -------
libseahorse/seahorse-search-provider.h | 49 -
libseahorse/seahorse-widget.h | 2 -
pkcs11/meson.build | 5 +
src/application.vala | 144 ++
src/generate-select.vala | 140 ++
src/import-dialog.vala | 78 +
src/key-manager.vala | 502 ++++++
src/main.vala | 36 +
src/meson.build | 38 +-
.../org.gnome.ShellSearchProvider2.xml | 0
src/seahorse-generate-select.c | 298 ----
src/seahorse-generate-select.h | 33 -
src/seahorse-import-dialog.c | 188 ---
src/seahorse-import-dialog.h | 50 -
src/seahorse-key-manager.c | 855 -----------
src/seahorse-key-manager.h | 61 -
src/seahorse-main.c | 100 --
src/seahorse-sidebar.c | 1598 --------------------
src/seahorse-sidebar.h | 61 -
src/search-provider.vala | 296 ++++
src/sidebar.vala | 925 +++++++++++
ssh/meson.build | 1 +
29 files changed, 2170 insertions(+), 4209 deletions(-)
---
diff --git a/common/catalog.vala b/common/catalog.vala
index 394adc0..deef377 100644
--- a/common/catalog.vala
+++ b/common/catalog.vala
@@ -107,8 +107,6 @@ public abstract class Catalog : Gtk.Window {
this._edit_copy = actions.get_action("edit-export-clipboard");
this._file_export = actions.get_action("file-export");
this._ui_manager.insert_action_group (actions, 0);
-
- Seahorse.Application.get().add_window(this);
}
public override void dispose() {
@@ -131,7 +129,6 @@ public abstract class Catalog : Gtk.Window {
this.get_size(out width, out height);
this._settings.set_int("width", width);
this._settings.set_int("height", height);
- Seahorse.Application.get().remove_window (this);
}
base.dispose();
diff --git a/common/config.vapi b/common/config.vapi
index 02ccb37..6e93f44 100644
--- a/common/config.vapi
+++ b/common/config.vapi
@@ -4,13 +4,20 @@ namespace Config
public const string PKGDATADIR;
public const string EXECDIR;
+ public const string LOCALEDIR;
public const string VERSION;
public const string PACKAGE;
+ public const string PACKAGE_STRING;
public const string GETTEXT_PACKAGE;
public const string SSH_PATH;
public const string SSH_KEYGEN_PATH;
+
+ public const string GNUPG;
+ public const int GPG_MAJOR;
+ public const int GPG_MINOR;
+ public const int GPG_MICRO;
}
/*
@@ -21,10 +28,7 @@ namespace Config
namespace Seahorse {
-[CCode (cheader_filename = "libseahorse/seahorse-application.h")]
-namespace Application {
- public unowned Gtk.Application @get();
-}
+public void register_resource();
[CCode (cheader_filename = "libseahorse/seahorse-util.h")]
public static GLib.HashFunc<ulong?> ulong_hash;
@@ -43,6 +47,10 @@ namespace Progress {
public void show(GLib.Cancellable? cancellable, string title, bool delayed);
}
+namespace Pgp.Backend {
+ public void initialize();
+}
+
}
namespace Egg {
diff --git a/gkr/meson.build b/gkr/meson.build
index 10b0e11..2c61028 100644
--- a/gkr/meson.build
+++ b/gkr/meson.build
@@ -23,3 +23,8 @@ gkr_lib = static_library('seahorse-gkr',
gkr_sources,
dependencies: gkr_dependencies,
)
+
+gkr_dep = declare_dependency(
+ link_with: gkr_lib,
+ include_directories: include_directories('.'),
+)
diff --git a/libseahorse/meson.build b/libseahorse/meson.build
index c1a7bbc..b1e8045 100644
--- a/libseahorse/meson.build
+++ b/libseahorse/meson.build
@@ -3,27 +3,18 @@ marshaller = gnome.genmarshal('seahorse-marshal',
prefix: 'seahorse_marshal',
)
-search_provider_src = gnome.gdbus_codegen('seahorse-shell-search-provider-generated',
- 'org.gnome.ShellSearchProvider2.xml',
- interface_prefix : 'org.gnome.',
- namespace : 'Seahorse',
-)
-
libseahorse_sources = [
- 'seahorse-application.c',
'seahorse-bind.c',
'seahorse-interaction.c',
'seahorse-object-list.c',
'seahorse-object-model.c',
'seahorse-object-widget.c',
'seahorse-progress.c',
- 'seahorse-search-provider.c',
'seahorse-util.c',
'seahorse-widget.c',
marshaller,
resources_src,
- search_provider_src,
]
libseahorse_deps = [
diff --git a/libseahorse/seahorse-widget.h b/libseahorse/seahorse-widget.h
index 068c104..f4ab2c5 100644
--- a/libseahorse/seahorse-widget.h
+++ b/libseahorse/seahorse-widget.h
@@ -24,8 +24,6 @@
#include <glib.h>
#include <gtk/gtk.h>
-#include "seahorse-application.h"
-
#define SEAHORSE_TYPE_WIDGET (seahorse_widget_get_type ())
#define SEAHORSE_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAHORSE_TYPE_WIDGET,
SeahorseWidget))
#define SEAHORSE_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SEAHORSE_TYPE_WIDGET,
SeahorseWidgetClass))
diff --git a/pkcs11/meson.build b/pkcs11/meson.build
index d5e6518..760b7bf 100644
--- a/pkcs11/meson.build
+++ b/pkcs11/meson.build
@@ -24,3 +24,8 @@ pkcs11_lib = static_library('seahorse-pkcs11',
pkcs11_sources,
dependencies: pkcs11_deps,
)
+
+pkcs11_dep = declare_dependency(
+ link_with: pkcs11_lib,
+ include_directories: include_directories('.'),
+)
diff --git a/src/application.vala b/src/application.vala
new file mode 100644
index 0000000..446a7ea
--- /dev/null
+++ b/src/application.vala
@@ -0,0 +1,144 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2003 Jacob Perkins
+ * Copyright (C) 2005, 2006 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2012 Stefan Walter
+ * 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.Application : Gtk.Application {
+ /* private SearchProvider? search_provider; */
+
+ private const int INACTIVITY_TIMEOUT = 120 * 1000; // Two minutes, in milliseconds
+
+ public Application () {
+ GLib.Object (
+ application_id: "org.gnome.seahorse.Application",
+ flags: ApplicationFlags.HANDLES_COMMAND_LINE
+ );
+
+ /* this.search_provider = new SearchProvider(); */
+ }
+
+ public override void startup() {
+ // Insert Icons into Stock
+ icons_init ();
+
+ // Initialize the backends
+ Gkr.Backend.initialize();
+ Ssh.Backend.initialize();
+#if WITH_PGP
+ Pgp.Backend.initialize();
+#endif
+#if WITH_PKCS11
+ Pkcs11.Backend.initialize();
+#endif
+ }
+
+ public override void activate() {
+ KeyManager key_mgr = new Seahorse.KeyManager(this);
+ key_mgr.show();
+ }
+
+ static bool show_version = false;
+ const OptionEntry[] local_options = {
+ { "version", 'v', 0, OptionArg.NONE, out show_version, N_("Version of this application"), null },
+ { null }
+ };
+
+ public override bool local_command_line (ref weak string[] arguments, out int exit_status) {
+ OptionContext context = new OptionContext(N_("- System Settings"));
+ context.set_ignore_unknown_options(true);
+ context.add_main_entries(local_options, Config.GETTEXT_PACKAGE);
+ context.set_translation_domain(Config.GETTEXT_PACKAGE);
+ context.add_group(Gtk.get_option_group (true));
+
+ try {
+ unowned string[] tmp = arguments;
+ context.parse (ref tmp);
+ } catch (Error e) {
+ printerr ("seahorse: %s\n", e.message);
+ exit_status = 1;
+ return true;
+ }
+
+ if (show_version) {
+ print ("%s\n", Config.PACKAGE_STRING);
+#if WITH_PGP
+ print ("GNUPG: %s (%d.%d.%d)\n", Config.GNUPG, Config.GPG_MAJOR, Config.GPG_MINOR,
Config.GPG_MICRO);
+#endif
+ exit_status = 0;
+ return true;
+ }
+
+ exit_status = 0;
+ /* return base.local_command_line(ref arguments, out exit_status); */
+ return false;
+ }
+
+ static bool no_window = false;
+ const OptionEntry[] options = {
+ { "no-window", 0, 0, OptionArg.NONE, out no_window, N_("Don't display a window"), null },
+ { null }
+ };
+
+ public override int command_line (ApplicationCommandLine command_line) {
+ OptionContext context = new OptionContext(N_("- System Settings"));
+ context.set_ignore_unknown_options (true);
+ context.add_main_entries (options, Config.GETTEXT_PACKAGE);
+ context.set_translation_domain(Config.GETTEXT_PACKAGE);
+
+ string[] arguments = command_line.get_arguments();
+ try {
+ unowned string[] tmp = arguments;
+ context.parse (ref tmp);
+ } catch (Error e) {
+ printerr ("seahorse: %s\n", e.message);
+ return 1;
+ }
+
+ if (no_window) {
+ hold();
+ set_inactivity_timeout(INACTIVITY_TIMEOUT);
+ release();
+ return 0;
+ }
+
+ activate ();
+ return 0;
+ }
+
+ public override bool dbus_register (DBusConnection connection, string object_path) throws Error {
+ if (!base.dbus_register(connection, object_path))
+ return false;
+
+ return true;
+ /* return this.search_provider.dbus_register (connection, object_path); */
+ }
+
+ public override void dbus_unregister (DBusConnection connection, string object_path) {
+ /* if (this.search_provider != null) */
+ /* this.search_provider.dbus_unregister(connection, object_path); */
+
+ base.dbus_unregister(connection, object_path);
+ }
+
+ public void initialize_search () {
+ /* this.search_provider.initialize(); */
+ }
+}
diff --git a/src/generate-select.vala b/src/generate-select.vala
new file mode 100644
index 0000000..1652c4f
--- /dev/null
+++ b/src/generate-select.vala
@@ -0,0 +1,140 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+public class Seahorse.GenerateSelect : Gtk.Dialog {
+ private Gtk.ListStore store;
+ private Gtk.TreeView view;
+ private List<Gtk.ActionGroup>? action_groups;
+
+ private enum Column {
+ ICON,
+ TEXT,
+ ACTION,
+ N_COLUMNS
+ }
+
+ public GenerateSelect(Gtk.Window? parent) {
+ GLib.Object(
+ transient_for: parent,
+ modal: true
+ );
+ this.store = new Gtk.ListStore(Column.N_COLUMNS, typeof(Icon), typeof(string), typeof(Gtk.Action));
+ this.store.set_default_sort_func(on_list_sort);
+ this.store.set_sort_column_id(Gtk.TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, Gtk.SortType.ASCENDING);
+
+ this.action_groups = (List<Gtk.ActionGroup>) Registry.object_instances("generator");
+ foreach (Gtk.ActionGroup action_group in this.action_groups) {
+ foreach (weak Gtk.Action action in action_group.list_actions()) {
+ string text = "<span size=\"larger\" weight=\"bold\">%s</span>\n%s"
+ .printf(action.label, action.tooltip);
+
+ Icon? icon = action.gicon;
+ if (icon == null) {
+ if (action.icon_name != null)
+ icon = new ThemedIcon(action.icon_name);
+ }
+
+ Gtk.TreeIter iter;
+ this.store.append(out iter);
+ this.store.set(iter, Column.TEXT, text,
+ Column.ICON, icon,
+ Column.ACTION, action,
+ -1);
+ }
+ }
+
+ Gtk.Builder builder = new Gtk.Builder();
+ try {
+ string path = "/org/gnome/Seahorse/seahorse-ssh-generate.ui";
+ builder.add_from_resource(path);
+ } catch (GLib.Error err) {
+ critical("%s", err.message);
+ }
+
+ // Setup the dialog
+ set_default_size(-1, 410);
+ get_content_area().pack_start((Gtk.Widget) builder.get_object("generate-select"),
+ true, true, 0);
+ add_buttons(Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL,
+ _("Continue"), Gtk.ResponseType.OK,
+ null);
+
+ // Hook it into the view
+ this.view = (Gtk.TreeView) builder.get_object("keytype-tree");
+
+ Gtk.CellRendererPixbuf pixcell = new Gtk.CellRendererPixbuf();
+ pixcell.stock_size = Gtk.IconSize.DND;
+ this.view.insert_column_with_attributes(-1, "", pixcell, "gicon", Column.ICON, null);
+ this.view.insert_column_with_attributes(-1, "", new Gtk.CellRendererText(), "markup", Column.TEXT,
null);
+ this.view.set_model(this.store);
+
+ // Setup selection, select first item
+ Gtk.TreeSelection selection = this.view.get_selection();
+ selection.set_mode(Gtk.SelectionMode.BROWSE);
+
+ Gtk.TreeIter iter;
+ this.store.get_iter_first(out iter);
+ selection.select_iter(iter);
+
+ this.view.row_activated.connect(on_row_activated);
+ this.view.height_request = 410;
+ }
+
+ private Gtk.Action? get_selected_action() {
+ Gtk.TreeSelection selection = this.view.get_selection();
+
+ Gtk.TreeIter iter;
+ Gtk.TreeModel? model;
+ if (!selection.get_selected(out model, out iter))
+ return null;
+
+ Gtk.Action? action;
+ this.store.get(iter, Column.ACTION, out action, -1);
+ assert (action != null);
+
+ return action;
+ }
+
+ private void on_row_activated(Gtk.TreeView view, Gtk.TreePath path, Gtk.TreeViewColumn col) {
+ Gtk.Action? action = get_selected_action();
+ if (action != null) {
+ Action.activate_with_window(action, null, this.transient_for);
+ destroy();
+ }
+ }
+
+ public override void response(int response) {
+ Gtk.Action? action = (response == Gtk.ResponseType.OK)? get_selected_action() : null;
+ Gtk.Window? parent = (action != null)? this.transient_for : null;
+
+ if (action != null)
+ Action.activate_with_window(action, null, parent);
+
+ destroy();
+ }
+
+ private int on_list_sort (Gtk.TreeModel? model, Gtk.TreeIter a, Gtk.TreeIter b) {
+ string? a_text = null, b_text = null;
+ model.get(a, Column.TEXT, out a_text, -1);
+ model.get(b, Column.TEXT, out b_text, -1);
+
+ return a_text.collate(b_text);
+ }
+}
diff --git a/src/import-dialog.vala b/src/import-dialog.vala
new file mode 100644
index 0000000..9d228a5
--- /dev/null
+++ b/src/import-dialog.vala
@@ -0,0 +1,78 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * 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/>.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+public class Seahorse.ImportDialog : Gtk.Dialog {
+
+ private Gcr.ViewerWidget viewer;
+ private Gcr.ImportButton import;
+
+ public ImportDialog(Gtk.Window? parent) {
+ GLib.Object(transient_for: parent);
+
+ Gtk.Widget button = new Gtk.Button.from_stock(Gtk.Stock.CANCEL);
+ button.show();
+ add_action_widget(button, Gtk.ResponseType.CANCEL);
+
+ this.import = new Gcr.ImportButton(_("Import"));
+ this.import.importing.connect(() => this.viewer.clear_error());
+ this.import.imported.connect(on_import_button_imported);
+ this.import.show();
+ ((Gtk.Box) get_action_area()).pack_start(this.import, false, true, 0);
+
+ Gtk.Frame frame = new Gtk.Frame(_("<b>Data to be imported:</b>"));
+ ((Gtk.Label) frame.label_widget).use_markup = true;
+ ((Gtk.Box) get_content_area()).pack_start(frame, true, true, 0);
+ frame.set_border_width(6);
+ frame.show();
+
+ this.viewer = new Gcr.ViewerWidget();
+ this.viewer.added.connect((v, r, parsed) => this.import.add_parsed(parsed));
+ this.viewer.show();
+
+ frame.add(this.viewer);
+ }
+
+ public void add_uris(string[] uris) {
+ foreach (string uri in uris)
+ this.viewer.load_file(File.new_for_uri(uri));
+ }
+
+ public void add_text(string? display_name, string text) {
+ this.viewer.load_data(display_name, (uint8[]) text);
+ }
+
+ private void on_import_button_imported(GLib.Object importer, Error? error) {
+ if (error == null) {
+ response(Gtk.ResponseType.OK);
+
+ string uri = ((Gcr.Importer) importer).uri;
+ foreach (Backend backend in Backend.get_registered()) {
+ Place? place = backend.lookup_place(uri);
+ if (place != null)
+ place.load.begin(null);
+ }
+
+ } else {
+ if (!(error is GLib.IOError.CANCELLED))
+ this.viewer.show_error(_("Import failed"), error);
+ }
+ }
+}
diff --git a/src/key-manager.vala b/src/key-manager.vala
new file mode 100644
index 0000000..c22813c
--- /dev/null
+++ b/src/key-manager.vala
@@ -0,0 +1,502 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+public class Seahorse.KeyManager : Catalog {
+
+ private Gtk.ActionGroup view_actions;
+ private Gtk.RadioAction show_action;
+ private Gtk.Entry filter_entry;
+ private Predicate pred;
+ private Sidebar sidebar;
+
+ private Gtk.TreeView view;
+ private Gcr.Collection collection; // owned by the sidebar
+ private KeyManagerStore store;
+
+ private GLib.Settings settings;
+ private int sidebar_width;
+ private uint sidebar_width_sig;
+
+ private enum ShowFilter {
+ ANY,
+ PERSONAL,
+ TRUSTED,
+ }
+
+ private enum DndTarget { // Drag 'n Drop target type
+ PLAIN,
+ URIS
+ }
+
+ private const Gtk.ActionEntry[] GENERAL_ACTIONS = {
+ // TRANSLATORS: The "Remote" menu contains key operations on remote systems.
+ { "remote-menu", null, N_("_Remote") },
+ { "new-menu", null, N_("_New") },
+ { "app-quit", Gtk.Stock.QUIT, null, "<control>Q", N_("Close this program"), on_app_quit },
+ { "file-new", Gtk.Stock.NEW, N_("_New…"), "<control>N", N_("Create a new key or item"), on_file_new
},
+ { "new-object", Gtk.Stock.ADD, N_("_New…"), null, N_("Add a new key or item"), on_file_new },
+ { "file-import", Gtk.Stock.OPEN, N_("_Import…"), "<control>I", N_("Import from a file"),
on_key_import_file },
+ { "edit-import-clipboard", Gtk.Stock.PASTE, null, "<control>V", N_("Import from the clipboard"),
on_key_import_clipboard }
+ };
+
+ private const Gtk.ToggleActionEntry[] SIDEBAR_ACTIONS = {
+ { "view-sidebar", null, N_("By _Keyring"), null, N_("Show sidebar listing keyrings"), null, false },
+ };
+
+ private const Gtk.RadioActionEntry[] VIEW_RADIO_ACTIONS = {
+ { "view-personal", null, N_("Show _Personal"), null, N_("Only show personal keys, certificates and
passwords"), ShowFilter.PERSONAL },
+ { "view-trusted", null, N_("Show _Trusted"), null, N_("Only show trusted keys, certificates and
passwords"), ShowFilter.TRUSTED },
+ { "view-any", null, N_("Show _Any"), null, N_("Show all keys, certificates and passwords"),
ShowFilter.ANY },
+ };
+
+ public KeyManager(Application app) {
+ GLib.Object(
+ ui_name: "key-manager",
+ application: app
+ );
+ this.settings = new GLib.Settings("org.gnome.seahorse.manager");
+
+ Gtk.Window window = this.window;
+ window.set_default_geometry(640, 476);
+ window.set_events(Gdk.EventMask.POINTER_MOTION_MASK
+ | Gdk.EventMask.POINTER_MOTION_HINT_MASK
+ | Gdk.EventMask.BUTTON_PRESS_MASK
+ | Gdk.EventMask.BUTTON_RELEASE_MASK);
+ window.set_title(_("Passwords and Keys"));
+
+ this.collection = setup_sidebar();
+
+ // Init key list & selection settings
+ Gtk.Builder builder = get_builder();
+ this.view = (Gtk.TreeView) builder.get_object("key-list");
+ assert (this.view != null);
+
+ Gtk.TreeSelection selection = this.view.get_selection();
+ selection.set_mode(Gtk.SelectionMode.MULTIPLE);
+ selection.changed.connect(on_view_selection_changed);
+ this.view.realize();
+
+ // Add new key store and associate it
+ this.store = new KeyManagerStore(this.collection, this.view, this.pred, this.settings);
+
+ Gtk.ActionGroup actions = new Gtk.ActionGroup("general");
+ actions.set_translation_domain(Config.GETTEXT_PACKAGE);
+ actions.add_actions(GENERAL_ACTIONS, null);
+ include_actions(actions);
+
+ this.view_actions = new Gtk.ActionGroup("view");
+ this.view_actions.set_translation_domain(Config.GETTEXT_PACKAGE);
+ this.view_actions.add_radio_actions(VIEW_RADIO_ACTIONS, -1, () => {
+ this.settings.set_string("item-filter", update_view_filter());
+ });
+ Gtk.RadioAction action = (Gtk.RadioAction) this.view_actions.get_action("view-personal");
+ include_actions(this.view_actions);
+ this.show_action = action;
+
+ // Notify us when settings change
+ this.settings.changed["item-filter"].connect(on_item_filter_changed);
+ on_item_filter_changed(this.settings, "item-filter");
+
+ // first time signals
+ ((Gtk.Button) builder.get_object("import-button")).clicked.connect(on_keymanager_import_button);
+ ((Gtk.Button) builder.get_object("new-button")).clicked.connect(on_keymanager_new_button);
+
+ // Make sure import is only available with clipboard content
+ Gtk.Action import_clipboard_action = actions.get_action("edit-import-clipboard");
+ Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY);
+ clipboard.owner_change.connect((c, e) => update_clipboard_state(c, e, actions));
+ update_clipboard_state(clipboard, null, actions);
+
+ // Flush all updates
+ ensure_updated();
+
+ // Find the toolbar
+ Gtk.Container placeholder = (Gtk.Container) builder.get_object("toolbar-placeholder");
+ if (placeholder != null) {
+ List<weak Gtk.Widget> children = placeholder.get_children();
+ if (children != null && children.data != null) {
+ // The toolbar is the first (and only) element
+ Gtk.Toolbar toolbar = (Gtk.Toolbar) children.data;
+ if (toolbar != null) {
+ toolbar.get_style_context().add_class("sidebar");
+ toolbar.reset_style();
+
+ // Insert a separator to right align the filter
+ Gtk.SeparatorToolItem sep = new Gtk.SeparatorToolItem();
+ sep.set_draw(false);
+ sep.set_expand(true);
+ sep.show_all();
+ toolbar.insert(sep, -1);
+
+ // Insert a filter bar
+ Gtk.Box box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
+
+ this.filter_entry = new Gtk.Entry();
+ this.filter_entry.set_placeholder_text(_("Filter"));
+ box.pack_start(this.filter_entry, false, true, 0);
+
+ box.pack_start(new Gtk.Label(null), false, false, 0);
+ box.show_all();
+
+ Gtk.ToolItem item = new Gtk.ToolItem();
+ item.add(box);
+ item.show_all();
+ toolbar.insert(item, -1);
+ }
+ }
+ }
+
+ on_filter_changed(this.filter_entry);
+ this.filter_entry.set_width_chars(30);
+ this.filter_entry.icon_release.connect(() => this.filter_entry.set_text(""));
+
+ // For the filtering
+ this.filter_entry.changed.connect(on_filter_changed);
+ this.view.start_interactive_search.connect(() => {
+ this.filter_entry.grab_focus();
+ return false;
+ });
+
+ // Set focus to the current key list
+ this.view.grab_focus();
+ selection_changed();
+
+ // To avoid flicker
+ show();
+
+ // Setup drops
+ Gtk.drag_dest_set(window, Gtk.DestDefaults.ALL, {}, Gdk.DragAction.COPY);
+ Gtk.TargetList targets = new Gtk.TargetList(null);
+ targets.add_uri_targets(DndTarget.URIS);
+ targets.add_text_targets(DndTarget.PLAIN);
+ Gtk.drag_dest_set_target_list(window, targets);
+
+ window.drag_data_received.connect(on_target_drag_data_received);
+
+ this.view.button_press_event.connect(on_keymanager_key_list_button_pressed);
+ this.view.row_activated.connect(on_keymanager_row_activated);
+ this.view.popup_menu.connect(on_keymanager_key_list_popup_menu);
+
+#if REFACTOR_FIRST
+ // To show first time dialog
+ Timeout.add_seconds(1, on_first_timer);
+#endif
+ }
+
+ ~KeyManager() {
+ if (this.sidebar_width_sig != 0) {
+ Source.remove(this.sidebar_width_sig);
+ this.sidebar_width_sig = 0;
+ }
+ }
+
+ private void on_view_selection_changed(Gtk.TreeSelection selection) {
+ Idle.add(() => {
+ // Fire the signal
+ selection_changed();
+ // Return false so we don't get run again
+ return false;
+ });
+ }
+
+ private void on_keymanager_row_activated(Gtk.TreeView view, Gtk.TreePath? path, Gtk.TreeViewColumn
column) {
+ if (path == null)
+ return;
+
+ GLib.Object obj = KeyManagerStore.get_object_from_path(view, path);
+ if (obj != null)
+ show_properties(obj);
+ }
+
+ private bool on_keymanager_key_list_button_pressed(Gdk.EventButton event) {
+ if (event.button == 3)
+ show_context_menu(Catalog.MENU_OBJECT, event.button, event.time);
+
+ return false;
+ }
+
+ private bool on_keymanager_key_list_popup_menu() {
+ GLib.List<weak GLib.Object> objects = get_selected_objects();
+ if (objects != null)
+ show_context_menu(Catalog.MENU_OBJECT, 0, Gtk.get_current_event_time());
+ return false;
+ }
+
+ private void on_file_new(Gtk.Action action) {
+ GenerateSelect dialog = new GenerateSelect(this.window);
+ dialog.run();
+ dialog.destroy();
+ }
+
+ private void on_keymanager_new_button(Gtk.Button button) {
+ GenerateSelect dialog = new GenerateSelect(this.window);
+ dialog.run();
+ dialog.destroy();
+ }
+
+#if REFACTOR_FIRST
+ private bool on_first_timer() {
+ // Although not all the keys have completed we'll know whether we have
+ // any or not at this point
+ if (this.collection.get_length() == 0) {
+ Gtk.Widget widget = xxwidget_get_widget (XX_WIDGET (self), "first-time-box");
+ widget.show();
+ }
+
+ return false;
+ }
+#endif
+
+ private void on_filter_changed(Gtk.Editable entry) {
+ string? text = this.filter_entry.get_text();
+ this.store.filter = text;
+
+ if (text == null || text == "") {
+ this.filter_entry.secondary_icon_name = "edit-find-symbolic";
+ this.filter_entry.secondary_icon_activatable = false;
+ this.filter_entry.secondary_icon_sensitive = false;
+ } else {
+ this.filter_entry.secondary_icon_name = "edit-clear-symbolic";
+ this.filter_entry.secondary_icon_activatable = true;
+ this.filter_entry.secondary_icon_sensitive = true;
+ }
+ }
+
+ private void import_files(string[]? uris) {
+ ImportDialog dialog = new ImportDialog(this.window);
+ dialog.add_uris(uris);
+ dialog.run();
+ dialog.destroy();
+ }
+
+ private void import_prompt() {
+ Gtk.FileChooserDialog dialog =
+ new Gtk.FileChooserDialog(_("Import Key"), this.window,
+ Gtk.FileChooserAction.OPEN,
+ _("_Cancel"), Gtk.ResponseType.CANCEL,
+ _("_Open"), Gtk.ResponseType.ACCEPT,
+ null);
+
+ dialog.set_default_response(Gtk.ResponseType.ACCEPT);
+ dialog.set_local_only(false);
+
+ // TODO: This should come from libgcr somehow
+ Gtk.FileFilter filter = new Gtk.FileFilter();
+ filter.set_name(_("All key files"));
+ filter.add_mime_type("application/pgp-keys");
+ filter.add_mime_type("application/x-ssh-key");
+ filter.add_mime_type("application/pkcs12");
+ filter.add_mime_type("application/pkcs12+pem");
+ filter.add_mime_type("application/pkcs7-mime");
+ filter.add_mime_type("application/pkcs7-mime+pem");
+ filter.add_mime_type("application/pkcs8");
+ filter.add_mime_type("application/pkcs8+pem");
+ filter.add_mime_type("application/pkix-cert");
+ filter.add_mime_type("application/pkix-cert+pem");
+ filter.add_mime_type("application/pkix-crl");
+ filter.add_mime_type("application/pkix-crl+pem");
+ filter.add_mime_type("application/x-pem-file");
+ filter.add_mime_type("application/x-pem-key");
+ filter.add_mime_type("application/x-pkcs12");
+ filter.add_mime_type("application/x-pkcs7-certificates");
+ filter.add_mime_type("application/x-x509-ca-cert");
+ filter.add_mime_type("application/x-x509-user-cert");
+ filter.add_mime_type("application/pkcs10");
+ filter.add_mime_type("application/pkcs10+pem");
+ filter.add_mime_type("application/x-spkac");
+ filter.add_mime_type("application/x-spkac+base64");
+ filter.add_pattern("*.asc");
+ filter.add_pattern("*.gpg");
+ filter.add_pattern("*.pub");
+ filter.add_pattern("*.pfx");
+ filter.add_pattern("*.cer");
+ filter.add_pattern("*.crt");
+ filter.add_pattern("*.pem");
+ dialog.add_filter(filter);
+ dialog.set_filter(filter);
+
+ filter = new Gtk.FileFilter();
+ filter.set_name(_("All files"));
+ filter.add_pattern("*");
+ dialog.add_filter(filter);
+
+ string? uri = null;
+ if (dialog.run() == Gtk.ResponseType.ACCEPT) {
+ uri = dialog.get_uri();
+ }
+
+ dialog.destroy();
+
+ if (uri != null) {
+ string? uris[2];
+ uris[0] = uri;
+ uris[1] = null;
+ import_files(uris);
+ }
+ }
+
+ private void on_key_import_file(Gtk.Action action) {
+ import_prompt();
+ }
+
+ private void on_keymanager_import_button(Gtk.Button button) {
+ import_prompt();
+ }
+
+ private void import_text(string? display_name, string? text) {
+ ImportDialog dialog = new ImportDialog(this.window);
+ dialog.add_text(display_name, text);
+ dialog.run();
+ dialog.destroy();
+ }
+
+ private void on_target_drag_data_received(Gdk.DragContext context, int x, int y,
+ Gtk.SelectionData? selection_data, uint info, uint time) {
+ if (selection_data == null)
+ return;
+
+ if (info == DndTarget.PLAIN) {
+ string? text = selection_data.get_text();
+ import_text(_("Dropped text"), text);
+ } else if (info == DndTarget.URIS) {
+ string[]? uris = selection_data.get_uris();
+ foreach (string uri in uris)
+ uri._strip();
+ import_files(uris);
+ }
+ }
+
+ private void update_clipboard_state(Gtk.Clipboard clipboard, Gdk.Event? event, Gtk.ActionGroup group) {
+ Gtk.Action action = group.get_action("edit-import-clipboard");
+ action.set_sensitive(clipboard.wait_is_text_available());
+ }
+
+ private void on_clipboard_received(Gtk.Clipboard board, string? text) {
+ if (text == null)
+ return;
+
+ assert(this.filter_entry != null);
+ if (this.filter_entry.is_focus)
+ this.filter_entry.paste_clipboard();
+ else
+ if (text != null && text.char_count() > 0)
+ import_text(_("Clipboard text"), text);
+ }
+
+ private void on_key_import_clipboard(Gtk.Action action) {
+ Gdk.Atom atom = Gdk.Atom.intern("CLIPBOARD", false);
+ Gtk.Clipboard clipboard = Gtk.Clipboard.get(atom);
+ clipboard.request_text(on_clipboard_received);
+ }
+
+ private void on_app_quit(Gtk.Action action) {
+ this.application.quit();
+ }
+
+ private string update_view_filter() {
+ string val = "";
+
+ switch (this.show_action.get_current_value()) {
+ case ShowFilter.PERSONAL:
+ this.pred.flags = Seahorse.Flags.PERSONAL;
+ val = "personal";
+ break;
+ case ShowFilter.TRUSTED:
+ this.pred.flags = Seahorse.Flags.TRUSTED;
+ val = "trusted";
+ break;
+ case ShowFilter.ANY:
+ this.pred.flags = 0;
+ val = "";
+ break;
+ }
+
+ this.store.refilter();
+ return val;
+ }
+
+ private void on_item_filter_changed(GLib.Settings settings, string? key) {
+ int radio;
+
+ string? value = settings.get_string(key);
+ if (value == null || value == "")
+ radio = ShowFilter.ANY;
+ else if (value == "personal")
+ radio = ShowFilter.PERSONAL;
+ else if (value == "trusted")
+ radio = ShowFilter.TRUSTED;
+ else
+ radio = -1;
+
+ this.show_action.set_current_value(radio);
+ update_view_filter();
+ }
+
+ public override GLib.List<weak GLib.Object> get_selected_objects() {
+ return KeyManagerStore.get_selected_objects(this.view);
+ }
+
+ public override Place? get_focused_place() {
+ return this.sidebar.get_focused_place();
+ }
+
+ private Gcr.Collection setup_sidebar() {
+ this.sidebar = new Sidebar();
+
+ this.sidebar_width = this.settings.get_int("sidebar-width");
+ Gtk.Builder builder = get_builder();
+
+ Gtk.Paned panes = (Gtk.Paned) builder.get_object("sidebar-panes");
+ panes.position = this.sidebar_width;
+ panes.realize.connect(() => { panes.position = this.settings.get_int("sidebar-width"); });
+ panes.unrealize.connect(() => { this.settings.set_int("sidebar-width", panes.position); });
+
+ panes.get_child1().set_size_request(50, -1);
+ panes.get_child2().set_size_request(150, -1);
+
+ foreach (weak Backend backend in get_backends()) {
+ if (backend.actions != null)
+ include_actions(backend.actions);
+ }
+
+ Gtk.Container area = (Gtk.Container) builder.get_object("sidebar-area");
+ area.add(this.sidebar);
+ this.sidebar.show();
+
+ Gtk.ActionGroup actions = new Gtk.ActionGroup("sidebar");
+ actions.set_translation_domain(Config.GETTEXT_PACKAGE);
+ actions.add_toggle_actions(SIDEBAR_ACTIONS, null);
+ Gtk.Action action = actions.get_action("view-sidebar");
+ this.settings.bind("sidebar-visible", action, "active", SettingsBindFlags.DEFAULT);
+ action.bind_property("active", area, "visible", BindingFlags.SYNC_CREATE);
+ action.bind_property("active", this.sidebar, "combined", BindingFlags.INVERT_BOOLEAN |
BindingFlags.SYNC_CREATE);
+ include_actions(actions);
+
+ this.settings.bind("keyrings-selected", this.sidebar, "selected-uris", SettingsBindFlags.DEFAULT);
+
+ return this.sidebar.collection;
+ }
+
+ public override List<weak Backend> get_backends() {
+ return this.sidebar.get_backends();
+ }
+}
diff --git a/src/main.vala b/src/main.vala
new file mode 100644
index 0000000..6746b2d
--- /dev/null
+++ b/src/main.vala
@@ -0,0 +1,36 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2003 Jacob Perkins
+ * Copyright (C) 2004,2005 Stefan Walter
+ * Copyright (C) 2017 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 int main (string[] args) {
+ Intl.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
+ Intl.bind_textdomain_codeset(Config.GETTEXT_PACKAGE, "UTF-8");
+ Intl.textdomain(Config.GETTEXT_PACKAGE);
+
+ Seahorse.register_resource();
+
+ Seahorse.Application app = new Seahorse.Application();
+ int status = app.run(args);
+
+ Seahorse.Registry.cleanup();
+ Seahorse.Servers.cleanup();
+
+ return status;
+}
diff --git a/src/meson.build b/src/meson.build
index a4c73a6..5a68ba1 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,39 +1,53 @@
+# search_provider_src = gnome.gdbus_codegen('seahorse-shell-search-provider-generated',
+# 'org.gnome.ShellSearchProvider2.xml',
+# interface_prefix : 'org.gnome.',
+# namespace : 'Seahorse',
+# )
+
seahorse_sources = [
- 'seahorse-generate-select.c',
- 'seahorse-import-dialog.c',
- 'seahorse-key-manager.c',
- 'seahorse-main.c',
- 'seahorse-sidebar.c',
- resources_src,
+ 'application.vala',
+ 'generate-select.vala',
+ 'import-dialog.vala',
+ 'key-manager.vala',
+ 'main.vala',
+ # 'search-provider.vala',
+ 'sidebar.vala',
+
+ # resources_src,
+ # search_provider_src,
]
seahorse_dependencies = [
glib_deps,
gtk,
- gcr,
- config,
+ libsecret,
common_dep,
libseahorse_dep,
+ gkr_dep,
+ ssh_dep,
]
seahorse_linkedlibs = [
libeggdatetime_lib,
libtreemultidnd_lib,
- gkr_lib,
- ssh_lib,
+]
+
+seahorse_vala_flags = [
]
if with_pgp
- seahorse_linkedlibs += pgp_lib
+ seahorse_dependencies += pgp_dep
+ seahorse_vala_flags += [ '-D', 'WITH_PGP' ]
endif
if with_pkcs11
- seahorse_linkedlibs += pkcs11_lib
+ seahorse_dependencies += pkcs11_dep
endif
seahorse_exe = executable('seahorse',
seahorse_sources,
dependencies: seahorse_dependencies,
+ vala_args: seahorse_vala_flags,
link_with: seahorse_linkedlibs,
install: true,
)
diff --git a/libseahorse/org.gnome.ShellSearchProvider2.xml b/src/org.gnome.ShellSearchProvider2.xml
similarity index 100%
rename from libseahorse/org.gnome.ShellSearchProvider2.xml
rename to src/org.gnome.ShellSearchProvider2.xml
diff --git a/src/search-provider.vala b/src/search-provider.vala
new file mode 100644
index 0000000..3cfa5da
--- /dev/null
+++ b/src/search-provider.vala
@@ -0,0 +1,296 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2013 Giovanni Campagna <scampa giovanni gmail com>
+ *
+ * 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/>.
+ */
+
+//#define SECRET_API_SUBJECT_TO_CHANGE
+
+ /* SeahorseShellSearchProvider2SkeletonClass parent_class; */
+[DBus (name = "org.gnome.Shell.SearchProvider2")]
+public class Seahorse.SearchProvider {
+ private Gcr.UnionCollection union_collection;
+
+ private Predicate base_predicate;
+ private Gcr.Collection collection;
+ private HashTable<string, weak Seahorse.Object> handles;
+ private List<QueuedRequest?> queued_requests;
+ private int n_loading;
+
+ private struct QueuedRequest {
+ public DBusMethodInvocation invocation;
+ public string[] terms;
+ }
+
+ public SearchProvider() {
+ this.union_collection = new Gcr.UnionCollection();
+
+ this.base_predicate.flags = Flags.PERSONAL;
+ this.base_predicate.custom = check_object_type;
+ this.collection = new Seahorse.Collection.for_predicate (this.union_collection, this.base_predicate,
null);
+ this.handles = new HashTable.full<string, weak Seahorse.Object> (str_hash, str_equal, free, null);
+ }
+
+ ~SearchProvider() {
+ this.handles.foreach( (__, obj) => {
+ obj.weak_unref (on_object_gone, this.handles);
+ });
+ }
+
+ public void initialize() {
+ foreach (Backend backend in Backend.get_registered()) {
+ this.n_loading++;
+
+ backend.notify["loaded"].connect(on_backend_loaded);
+ backend.added.connect(on_place_added);
+ backend.removed.connect(on_place_removed);
+
+ foreach (GLib.Object place in backend.get_objects())
+ on_place_added (backend, place);
+ }
+ }
+
+ public async string[] GetInitialResultSet (string[] terms) {
+ hold_app ();
+
+ if (queue_request_if_not_loaded(invocation, terms))
+ return true;
+
+ Predicate predicate = Predicate () {
+ custom = object_matches_search,
+ custom_target = terms
+ };
+
+ string?[] results = {};
+ foreach (GLib.Object obj in this.collection.get_objects()) {
+ if (predicate.match(obj)) {
+ string str = "%p".printf(obj);
+
+ if (!(str in this.handles)) {
+ this.handles.insert(str, (Object) obj);
+ obj.weak_ref(on_object_gone, this.handles);
+ }
+ results += str;
+ }
+ }
+
+ release_app ();
+ results += null;
+ return results;
+ }
+
+ public async string[] GetSubsearchResultSet (string[] previous_results, string[] new_terms) {
+ if (error_request_if_not_loaded(invocation))
+ return true;
+
+ hold_app ();
+
+ Predicate predicate = Predicate () {
+ custom = object_matches_search,
+ custom_target = new_terms
+ };
+
+ string?[] results = {};
+ foreach (string previous_result in previous_results) {
+ GLib.Object? object = this.handles.lookup(previous_result);
+ if (object == null || !this.collection.contains(object)) {
+ continue; // Bogus value
+ }
+
+ if (predicate.match(object))
+ results += previous_result;
+ }
+
+ release_app ();
+ results += null;
+ return results;
+ }
+
+ public async HashTable<string, Variant>[] GetResultMetas (string[] results) {
+ hold_app ();
+ VariantBuilder builder = new VariantBuilder(new VariantType("aa{sv}"));
+
+ foreach (string result in results) {
+ Seahorse.Object object = this.handles.lookup(result);
+ if (object == null || !(object in this.collection))
+ continue; // Bogus value
+
+ builder.open(new VariantType("a{sv}"));
+ builder.add("{sv}", "id", new Variant.string(result));
+ if (object.label != null)
+ builder.add("{sv}", "name", new Variant.string(object.label));
+
+ if (object.icon != null) {
+ Variant? icon_variant = object.icon.serialize();
+ if (icon_variant != null)
+ builder.add("{sv}", "icon", icon_variant);
+ }
+
+ string? description = get_description_if_available(object);
+ if (description != null) {
+ string escaped_description = Markup.escape_text(description);
+ builder.add("{sv}", "description", new Variant.string(description));
+ }
+
+ builder.close();
+ }
+ }
+
+ public void ActivateResult (string search_id, string[] terms, uint32 timestamp) {
+ hold_app ();
+ sscanf (identifier, "%p", &object);
+
+ Seahorse.Object object = this.handles.lookup(identifier);
+ if (object == null || !(object in this.collection) || !(object is Viewable))
+ return; // Bogus value
+
+ KeyManager key_manager = KeyManager.show(timestamp);
+ ((Viewable) object).view((Gtk.Window) key_manager);
+
+ release_app ();
+ }
+
+ public void LaunchSearch (string[] terms, uint32 timestamp) {
+ // TODO
+ }
+
+ /* Search through row for text */
+ private bool object_contains_filtered_text (Seahorse.Object object, string? text) {
+ string? name = object.label;
+ if (name != null && (text in name.down()))
+ return true;
+
+ string? description = get_description_if_available (object);
+ if (description != null && (text in description.down()))
+ return true;
+
+ return false;
+ }
+
+ private bool object_matches_search (Seahorse.Object? object, void* terms) {
+ foreach (string term in ((string[]) terms)) {
+ if (!object_contains_filtered_text (object, term))
+ return false;
+ }
+
+ return true;
+ }
+
+ private void on_object_gone(void* data, GLib.Object? where_the_object_was) {
+ HashTable handles = data;
+ handles.remove("%p".printf((void*)where_the_object_was));
+ }
+
+ // We called before loading, we queue GetInitialResultSet, but
+ // we drop all other calls, because we don't expect to see them
+ // before we reply to GetInitialResultSet
+
+ private void hold_app() {
+ ((Seahorse.Application) Application.get()).hold();
+ }
+
+ private void release_app() {
+ ((Seahorse.Application) Application.get()).release();
+ }
+
+ private bool queue_request_if_not_loaded (DBusMethodInvocation invocation, string[] terms) {
+ if (this.n_loading <= 0)
+ return false;
+
+ QueuedRequest req = QueuedRequest () {
+ invocation = invocation,
+ terms = terms
+ };
+
+ this.queued_requests.prepend (req);
+ return true;
+ }
+
+ private bool error_request_if_not_loaded (DBusMethodInvocation invocation) {
+ if (this.n_loading > 0) {
+ invocation.return_dbus_error("org.gnome.Seahore.Error.NotLoaded",
+ _("The search provider is not loaded yet"));
+ return true;
+ } else {
+ return false;
+ }
+ }
+ private void on_place_added (Gcr.Collection places, GLib.Object object) {
+ Place place = (Place) object;
+ if (!this.union_collection.have(place))
+ this.union_collection.add(place);
+ }
+
+ private void on_place_removed (Gcr.Collection places, GLib.Object object) {
+ Place place = (Place) object;
+ if (this.union_collection.have(place))
+ this.union_collection.remove(place);
+ }
+
+ private void on_backend_loaded (GLib.Object? object, ParamSpec pspec) {
+ this.n_loading--;
+ if (this.n_loading > 0)
+ return;
+
+ foreach (QueuedRequest? req in this.queued_requests) {
+ handle_get_initial_result_set (SEAHORSE_SHELL_SEARCH_PROVIDER2 (self),
+ req.invocation, req.terms);
+
+ /* In the previous get_initial_result_set() we had one unbalanced
+ hold, so we release it now. */
+ release_app ();
+ g_object_unref (req.invocation);
+ g_strfreev (req.terms);
+ g_slice_free (QueuedRequest, req);
+ }
+
+ this.queued_requests = null;
+ }
+
+ private static bool check_object_type (GLib.Object? object, void* custom_target) {
+ if (!(object is Viewable))
+ return false;
+
+ if (object is Secret.Item) {
+ string? schema_name = ((Secret.Item) object).get_schema_name ();
+ if (schema_name != "org.gnome.keyring.Note")
+ return false;
+ }
+
+ return true;
+ }
+
+ public bool seahorse_search_provider_dbus_register (DBusConnection connection, string? object_path)
throws GLib.Error {
+ return export (connection, object_path, error);
+ }
+
+ public void seahorse_search_provider_dbus_unregister (DBusConnection connection, string? object_path) {
+ if (has_connection(connection))
+ unexport_from_connection(connection);
+ }
+
+ private string? get_description_if_available (GLib.Object? obj) {
+ if (obj == null)
+ return null;
+
+ if (obj.get_class().find_property("description") == null)
+ return null;
+
+ string? description = null;
+ obj.get("description", out description);
+ return description;
+ }
+}
diff --git a/src/sidebar.vala b/src/sidebar.vala
new file mode 100644
index 0000000..032ef04
--- /dev/null
+++ b/src/sidebar.vala
@@ -0,0 +1,925 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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.Sidebar : Gtk.ScrolledWindow {
+
+ private const int ACTION_BUTTON_XPAD = 6;
+
+ private Gtk.TreeView? tree_view;
+
+ private Gtk.ListStore? store;
+ private List<Backend> backends;
+ private Gcr.UnionCollection? objects;
+
+ // The selection
+ private HashTable<Gcr.Collection, Gcr.Collection>? selection;
+ private bool updating;
+
+ // A set of chosen uris, used with settings
+ private GenericSet<string?> chosen;
+
+ // 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 Gtk.AccelGroup? accel_group;
+
+ private uint update_places_sig;
+
+ /**
+ * Collection of objects sidebar represents
+ */
+ public Gcr.Collection? collection {
+ get { return this.objects; }
+ }
+
+ /**
+ * The URIs selected by the user
+ */
+ public string[] selected_uris {
+ owned get { return chosen_uris_to_array(); }
+ set { replace_chosen_uris(value); }
+ }
+
+ /**
+ * Collection shows all objects combined
+ */
+ public bool combined {
+ get { return this._combined; }
+ set {
+ if (this._combined != value) {
+ this._combined = value;
+ update_objects_in_collection(false);
+ }
+ }
+ }
+ private bool _combined;
+
+ private enum RowType {
+ BACKEND,
+ PLACE,
+ }
+
+ private enum Columns {
+ ROW_TYPE,
+ ICON,
+ LABEL,
+ TOOLTIP,
+ CATEGORY,
+ COLLECTION,
+ URI,
+ N_COLUMNS;
+ }
+
+ private Type[] column_types = {
+ typeof(uint),
+ typeof(Icon),
+ typeof(string),
+ typeof(string),
+ typeof(bool),
+ typeof(string),
+ typeof(Gcr.Collection),
+ typeof(string)
+ };
+
+ public Sidebar() {
+ this.store = new Gtk.ListStore(Columns.N_COLUMNS, column_types);
+
+ this.backends = new List<Backend>();
+ this.selection = new HashTable<Gcr.Collection, Gcr.Collection>(direct_hash, direct_equal);
+ this.objects = new Gcr.UnionCollection();
+ this.chosen = new GenericSet<string?>(str_hash, str_equal);
+
+ this.accel_group = new Gtk.AccelGroup();
+
+ get_style_context().add_class("sidebar");
+
+ set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+ set_hadjustment(null);
+ set_vadjustment(null);
+ set_shadow_type(Gtk.ShadowType.OUT);
+ get_style_context().set_junction_sides(Gtk.JunctionSides.RIGHT | Gtk.JunctionSides.LEFT);
+
+ /* tree view */
+ this.tree_view = new Gtk.TreeView();
+ 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", Columns.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", Columns.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", Columns.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);
+ this.tree_view.append_column(col);
+
+ this.tree_view.set_headers_visible(false);
+ this.tree_view.set_tooltip_column(Columns.TOOLTIP);
+ this.tree_view.set_search_column(Columns.LABEL);
+ this.tree_view.set_model(this.store);
+ this.tree_view.popup_menu.connect(on_tree_view_popup_menu);
+ this.tree_view.button_press_event.connect(on_tree_view_button_press_event);
+ this.tree_view.motion_notify_event.connect(on_tree_view_motion_notify_event);
+ this.tree_view.button_release_event.connect(on_tree_view_button_release_event);
+ add(this.tree_view);
+ this.tree_view.show();
+
+ Gtk.TreeSelection selection = this.tree_view.get_selection();
+ selection.set_mode(Gtk.SelectionMode.MULTIPLE);
+ selection.set_select_function(on_tree_selection_validate);
+ selection.changed.connect(() => update_objects_for_selection(selection));
+
+ load_backends();
+ }
+
+ ~Sidebar() {
+ foreach (Backend backend in this.backends) {
+ SignalHandler.disconnect_by_func((void*) backend, (void*) on_place_added, this);
+ SignalHandler.disconnect_by_func((void*) backend, (void*) on_place_removed, this);
+ SignalHandler.disconnect_by_func((void*) backend, (void*) on_backend_changed, this);
+
+ 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() {
+ foreach (Backend backend in Backend.get_registered()) {
+ this.backends.append(backend);
+ backend.added.connect(on_place_added);
+ backend.removed.connect(on_place_removed);
+ backend.notify.connect(on_backend_changed);
+
+ foreach (weak GLib.Object obj in backend.get_objects())
+ on_place_added(backend, obj);
+ }
+
+ this.backends.sort((a, b) => {
+ int ordera = order_from_backend((Backend) a);
+ int orderb = order_from_backend((Backend) b);
+ return ordera - orderb;
+ });
+ }
+
+ 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_removed(Gcr.Collection? places, GLib.Object obj) {
+ SignalHandler.disconnect_by_func((void*) obj, (void*) update_places_later, this);
+ update_places_later();
+ }
+
+ private void on_backend_changed(GLib.Object obj, ParamSpec spec) {
+ update_places_later();
+ }
+
+ private static int order_from_backend (Backend backend) {
+ switch (backend.name) {
+ case "gkr":
+ return 0;
+ case "pgp":
+ return 1;
+ case "pkcs11":
+ return 2;
+ case "ssh":
+ return 3;
+ default:
+ return 10;
+ }
+ }
+
+ 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.GENERIC_FALLBACK);
+ 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.GENERIC_FALLBACK);
+ 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);
+ }
+ }
+
+ 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++;
+ }
+ }
+ }
+ 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, 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((void*) iter) && iter.user_data3 == ((void*) iter) &&
+ iter.user_data2 == ((void*) iter) && iter.user_data == ((void*) iter)) {
+ if (!store.get_iter_first(out iter))
+ store.append(out iter);
+ return;
+ }
+
+ if (!store.iter_next(ref iter)) {
+ store.append(out iter);
+ return;
+ }
+
+ for (;;) {
+ string? row_category;
+ Gcr.Collection? row_collection;
+ store.get(iter, Columns.CATEGORY, out row_category,
+ Columns.COLLECTION, out row_collection, -1);
+
+ if (row_category == category && row_collection == collection)
+ return;
+
+ if (!store.remove(ref iter)) {
+ store.append(out iter);
+ return;
+ }
+ }
+ }
+
+ private void update_backend(Backend? backend, Gtk.TreeIter? iter) {
+ if (backend.get_objects() == null) // Ignore categories that have nothing
+ return;
+
+ next_or_append_row(this.store, iter, backend.name, backend);
+ this.store.set(iter, Columns.ROW_TYPE, RowType.BACKEND,
+ Columns.CATEGORY, backend.name,
+ Columns.LABEL, backend.label,
+ Columns.TOOLTIP, backend.description,
+ Columns.COLLECTION, backend,
+ -1);
+
+ foreach (weak GLib.Object obj in backend.get_objects()) {
+ Place place = obj as Place;
+ if (place == null)
+ continue;
+
+ next_or_append_row(this.store, iter, backend.name, place);
+ this.store.set(iter, Columns.ROW_TYPE, RowType.PLACE,
+ Columns.CATEGORY, backend.name,
+ Columns.LABEL, place.label,
+ Columns.TOOLTIP, place.description,
+ Columns.ICON, place.icon,
+ Columns.COLLECTION, place,
+ Columns.URI, place.uri,
+ -1);
+ }
+ }
+
+ private void update_objects_in_collection(bool update_chosen) {
+ if (this.updating) // Updating collection is blocked
+ return;
+
+ 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;
+ }
+ }
+
+ // Combined overrides and shows all objects
+ if (this.combined)
+ include = true;
+
+ bool have = this.objects.have(place);
+ if (include && !have)
+ this.objects.add(place);
+ else if (!include && have)
+ this.objects.remove(place);
+ }
+ }
+ }
+
+ private void update_objects_for_selection(Gtk.TreeSelection selection) {
+ if (this.updating)
+ 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, Columns.COLLECTION, out collection, -1);
+ if (collection != null)
+ selected.insert(collection, collection);
+ });
+
+ this.selection = selected;
+
+ if (!this.combined)
+ update_objects_in_collection(true);
+ }
+
+ private void update_objects_for_chosen(GenericSet<string?> chosen) {
+ this.updating = true;
+
+ Gtk.TreeSelection selection = this.tree_view.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, Columns.COLLECTION, out collection,
+ Columns.URI, out uri, -1);
+
+ 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));
+ }
+
+ this.updating = false;
+ update_objects_for_selection(selection);
+ }
+
+ private void update_places() {
+ Gtk.TreeIter iter = Gtk.TreeIter();
+ iter.stamp = (int) &iter; // A marker that tells us the iter is not yet valid
+ iter.user_data3 = iter.user_data2 = iter.user_data = &iter;
+
+ foreach (Backend backend in this.backends)
+ update_backend(backend, iter);
+
+ // Update selection
+ update_objects_for_chosen(this.chosen);
+
+ if (this.combined)
+ update_objects_in_collection(false);
+ }
+
+ 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
+ });
+ }
+ }
+
+ private Lockable? lookup_lockable_for_iter(Gtk.TreeModel? model, Gtk.TreeIter? iter) {
+ Gcr.Collection? collection = null;
+ model.get(iter, Columns.COLLECTION, out collection, -1);
+
+ return collection as Lockable;
+ }
+
+ 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;
+
+ Lockable? lockable = lookup_lockable_for_iter(model, iter);
+ if (lockable != null) {
+ can_lock = lockable.lockable;
+ can_unlock = lockable.unlockable;
+ }
+
+ 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;
+ }
+
+ 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;
+
+ 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;
+ }
+ }
+
+ private void on_cell_renderer_heading_visible(Gtk.CellLayout layout, Gtk.CellRenderer? cell,
+ Gtk.TreeModel? model, Gtk.TreeIter? iter) {
+ RowType type;
+ model.get(iter, Columns.ROW_TYPE, out type, -1);
+ cell.visible = (type == RowType.BACKEND);
+ }
+
+ private void on_padding_cell_renderer(Gtk.CellLayout layout, Gtk.CellRenderer? cell,
+ Gtk.TreeModel? model, Gtk.TreeIter? iter) {
+ RowType type;
+ model.get(iter, Columns.ROW_TYPE, out type, -1);
+
+ if (type == RowType.BACKEND) {
+ cell.visible = false;
+ cell.xpad = 0;
+ cell.ypad = 0;
+ } else {
+ cell.visible = true;
+ cell.xpad = 3;
+ cell.ypad = 3;
+ }
+ }
+
+ 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, Columns.ROW_TYPE, out type, -1);
+ cell.visible = (type != RowType.BACKEND);
+ }
+
+ 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);
+
+ RowType row_type;
+ model.get(iter, Columns.ROW_TYPE, out row_type, -1);
+ if (row_type == RowType.BACKEND)
+ return false;
+
+ return true;
+ }
+
+ private void place_lock(Lockable lockable, Gtk.Window? window) {
+ Cancellable cancellable = new Cancellable();
+ TlsInteraction interaction = new Interaction(window);
+
+ lockable.lock.begin(interaction, cancellable, (obj, res) => {
+ try {
+ lockable.lock.end(res);
+ } catch (Error e) {
+ Util.show_error(window, _("Couldn’t lock"), e.message);
+ }
+ });
+ }
+
+ private void on_place_lock(Gtk.MenuItem item, Lockable lockable) {
+ place_lock(lockable, (Gtk.Window) item.get_toplevel());
+ }
+
+ private void place_unlock(Lockable lockable, Gtk.Window? window) {
+ Cancellable cancellable = new Cancellable();
+ TlsInteraction interaction = new Interaction(window);
+
+ lockable.unlock.begin(interaction, cancellable, (obj, res) => {
+ try {
+ lockable.unlock.end(res);
+ } catch (Error e) {
+ Util.show_error(window, _("Couldn’t unlock"), e.message);
+ }
+ });
+ }
+
+ private void on_place_unlock(Gtk.MenuItem item, Lockable lockable) {
+ place_unlock(lockable, (Gtk.Window) item.get_toplevel());
+ }
+
+ 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);
+ }
+ });
+ }
+ }
+
+ private void popup_menu_for_place(Place place, uint button, uint32 activate_time) {
+ Gtk.Menu menu = new Gtk.Menu();
+
+ // First add all the actions from the collection
+ Gtk.ActionGroup actions = place.actions;
+ foreach (weak Gtk.Action action in actions.list_actions()) {
+ action.set_accel_group(this.accel_group);
+ menu.append((Gtk.MenuItem) action.create_menu_item());
+ }
+
+ // Lock and unlock items
+ if (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);
+ 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);
+ menu.append(item);
+ }
+
+ // Delete item
+ if (place is Deletable) {
+ Gtk.ImageMenuItem item = new Gtk.ImageMenuItem.with_mnemonic(_("_Delete"));
+ item.set_image(new Gtk.Image.from_stock(Gtk.Stock.DELETE, Gtk.IconSize.MENU));
+ item.activate.connect(() => on_place_delete(item, (Deletable) place));
+ place.bind_property("deletable", item, "sensitive", BindingFlags.SYNC_CREATE);
+ menu.append(item);
+ item.show();
+ }
+
+ // Properties item
+ if (place is Viewable) {
+ Gtk.ImageMenuItem item = new Gtk.ImageMenuItem.with_mnemonic(_("_Properties"));
+ item.set_image(new Gtk.Image.from_stock(Gtk.Stock.PROPERTIES, Gtk.IconSize.MENU));
+ item.activate.connect(() => Viewable.view(place, (Gtk.Window) item.get_toplevel()));
+ menu.append(item);
+ item.show();
+ }
+
+ bool visible = false;
+ menu.foreach((widget) => {
+ if (widget.visible)
+ visible = true;
+ });
+
+ if (visible) {
+ menu.popup(null, null, null, button, activate_time);
+ menu.attach_to_widget(this, null);
+ menu.show();
+ } else {
+ menu.destroy();
+ }
+ }
+
+ private bool on_tree_view_popup_menu(Gtk.Widget? widget) {
+ Gtk.TreePath? path;
+ this.tree_view.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, Columns.COLLECTION, out collection, -1);
+
+ if (collection is Place) {
+ popup_menu_for_place((Place) collection, 0, Gtk.get_current_event_time());
+ 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 (this.tree_view.get_path_at_pos(x, y, out path, out column, null, null)) {
+ Gtk.TreeIter iter;
+ this.store.get_iter(out iter, path);
+
+ int hseparator;
+ this.tree_view.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_tree_view_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_tree_view_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 (!this.tree_view.get_path_at_pos((int) event.x, (int) event.y, out path, null, null, null))
+ return false;
+
+ this.tree_view.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, Columns.COLLECTION, out collection, -1);
+
+ if (collection is Place)
+ popup_menu_for_place((Place) collection, event.button, event.time);
+
+ return true;
+ }
+
+ private bool on_tree_view_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;
+
+ return results;
+ }
+
+ 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;
+ }
+
+ public List<weak Gcr.Collection>? get_selected_places() {
+ List<weak Gcr.Collection> places = this.objects.elements();
+
+ Gtk.TreePath? path = null;
+ this.tree_view.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, Columns.ROW_TYPE, out row_type,
+ Columns.COLLECTION, out collection, -1);
+
+ if (collection != null) {
+ if (row_type == RowType.PLACE) {
+ places.remove(collection);
+ places.prepend(collection);
+ }
+ }
+ }
+
+ return places;
+ }
+
+ public Place? get_focused_place() {
+ Gtk.TreeIter iter;
+
+ Gtk.TreePath? path = null;
+ this.tree_view.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, Columns.ROW_TYPE, out row_type,
+ Columns.COLLECTION, out collection, -1);
+
+ if (row_type == RowType.PLACE)
+ return (Place) collection;
+ }
+
+ return null;
+ }
+
+ public List<weak Backend>? get_backends() {
+ Gtk.TreeIter iter;
+
+ List<weak Backend> backends = this.backends.copy();
+ backends.reverse();
+
+ Gtk.TreePath? path = null;
+ this.tree_view.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, Columns.ROW_TYPE, out row_type,
+ Columns.COLLECTION, out collection, -1);
+
+ if (collection != null) {
+ if (row_type == RowType.BACKEND) {
+ backends.remove((Backend) collection);
+ backends.prepend((Backend) collection);
+ }
+ }
+ }
+
+ return backends;
+ }
+}
diff --git a/ssh/meson.build b/ssh/meson.build
index efb0719..f122d23 100644
--- a/ssh/meson.build
+++ b/ssh/meson.build
@@ -18,6 +18,7 @@ ssh_sources = [
ssh_dependencies = [
glib_deps,
gcr,
+ gcr_ui,
posix,
gtk,
common_dep,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]