[geary/wip/721828-undo-2: 1/2] UI plumbing



commit b652eb69c77c9c9d7bd6cc1b071a9bf8966fa30c
Author: Jim Nelson <jim yorba org>
Date:   Tue Jan 13 15:07:01 2015 -0800

    UI plumbing

 src/CMakeLists.txt                           |    1 +
 src/client/application/geary-controller.vala |   50 ++++++++++++++++++++++++++
 src/client/components/main-toolbar.vala      |    6 +++
 src/engine/api/geary-revokable.vala          |   49 +++++++++++++++++++++++++
 4 files changed, 106 insertions(+), 0 deletions(-)
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0d04959..b8375dd 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -37,6 +37,7 @@ engine/api/geary-logging.vala
 engine/api/geary-named-flag.vala
 engine/api/geary-named-flags.vala
 engine/api/geary-progress-monitor.vala
+engine/api/geary-revokable.vala
 engine/api/geary-search-folder.vala
 engine/api/geary-search-query.vala
 engine/api/geary-service.vala
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 998fc72..9622aea 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -33,6 +33,7 @@ public class GearyController : Geary.BaseObject {
     public const string ACTION_EMPTY_MENU = "GearyEmptyMenu";
     public const string ACTION_EMPTY_SPAM = "GearyEmptySpam";
     public const string ACTION_EMPTY_TRASH = "GearyEmptyTrash";
+    public const string ACTION_UNDO = "GearyUndo";
     public const string ACTION_FIND_IN_CONVERSATION = "GearyFindInConversation";
     public const string ACTION_FIND_NEXT_IN_CONVERSATION = "GearyFindNextInConversation";
     public const string ACTION_FIND_PREVIOUS_IN_CONVERSATION = "GearyFindPreviousInConversation";
@@ -125,6 +126,7 @@ public class GearyController : Geary.BaseObject {
     private Gee.List<string> pending_mailtos = new Gee.ArrayList<string>();
     private Geary.Nonblocking.Mutex untrusted_host_prompt_mutex = new Geary.Nonblocking.Mutex();
     private Gee.HashSet<Geary.Endpoint> validating_endpoints = new Gee.HashSet<Geary.Endpoint>();
+    private Geary.Revokable? revokable = null;
     
     // List of windows we're waiting to close before Geary closes.
     private Gee.List<ComposerWidget> waiting_to_close = new Gee.ArrayList<ComposerWidget>();
@@ -238,6 +240,9 @@ public class GearyController : Geary.BaseObject {
         // instantiate here to ensure that Config is initialized and ready
         autostart_manager = new AutostartManager();
         
+        // initialize revokable
+        save_revokable(null, null);
+        
         // Start Geary.
         try {
             yield Geary.Engine.instance.open_async(GearyApplication.instance.get_user_data_directory(), 
@@ -404,6 +409,9 @@ public class GearyController : Geary.BaseObject {
         empty_trash.label = _("Empty _Trash…");
         entries += empty_trash;
         
+        Gtk.ActionEntry undo = { ACTION_UNDO, "edit-undo-symbolic", null, "<Ctrl>Z", null, on_revoke };
+        entries += undo;
+        
         Gtk.ActionEntry zoom_in = { ACTION_ZOOM_IN, null, null, "<Ctrl>equal",
             null, on_zoom_in };
         entries += zoom_in;
@@ -2433,6 +2441,48 @@ public class GearyController : Geary.BaseObject {
         }
     }
     
+    private void save_revokable(Geary.Revokable? new_revokable, string? description) {
+        // disconnect old revokable
+        if (revokable != null)
+            revokable.notify[Geary.Revokable.PROP_CAN_REVOKE].disconnect(on_can_revoke_changed);
+        
+        // store new revokable
+        revokable = new_revokable;
+        
+        // connect to new revokable
+        if (revokable != null)
+            revokable.notify[Geary.Revokable.PROP_CAN_REVOKE].connect(on_can_revoke_changed);
+        
+        Gtk.Action undo_action = GearyApplication.instance.get_action(ACTION_UNDO);
+        undo_action.sensitive = revokable != null && revokable.can_revoke;
+        undo_action.tooltip = (revokable != null && description != null) ? description : _("Undo");
+    }
+    
+    private void on_can_revoke_changed() {
+        // remove revokable if it goes invalid
+        if (revokable != null && !revokable.can_revoke)
+            save_revokable(null, null);
+    }
+    
+    private void on_revoke() {
+        if (revokable != null && revokable.can_revoke)
+            revokable.revoke_async.begin(null, on_revoke_completed);
+    }
+    
+    private void on_revoke_completed(Object? object, AsyncResult result) {
+        // Don't use the "revokable" instance because it might have gone null before this callback
+        // was reached
+        Geary.Revokable? origin = object as Geary.Revokable;
+        if (origin == null)
+            return;
+        
+        try {
+            origin.revoke_async.end(result);
+        } catch (Error err) {
+            debug("Unable to revoke operation: %s", err.message);
+        }
+    }
+    
     private void on_zoom_in() {
         main_window.conversation_viewer.web_view.zoom_in();
     }
diff --git a/src/client/components/main-toolbar.vala b/src/client/components/main-toolbar.vala
index 6b787e8..39c52a8 100644
--- a/src/client/components/main-toolbar.vala
+++ b/src/client/components/main-toolbar.vala
@@ -72,6 +72,10 @@ public class MainToolbar : PillHeaderbar {
         insert.add(create_menu_button(null, empty_menu, GearyController.ACTION_EMPTY_MENU));
         Gtk.Box archive_trash_delete_empty = create_pill_buttons(insert);
         
+        insert.clear();
+        insert.add(create_toolbar_button(null, GearyController.ACTION_UNDO, false));
+        Gtk.Box undo = create_pill_buttons(insert);
+        
         // Search bar.
         search_entry.width_chars = 28;
         search_entry.tooltip_text = _("Search all mail in account for keywords (Ctrl+S)");
@@ -88,6 +92,7 @@ public class MainToolbar : PillHeaderbar {
         // pack_end() ordering is reversed in GtkHeaderBar in 3.12 and above
 #if !GTK_3_12
         add_end(archive_trash_delete_empty);
+        add_end(undo);
         add_end(search_upgrade_progress_bar);
         add_end(search_entry);
 #endif
@@ -103,6 +108,7 @@ public class MainToolbar : PillHeaderbar {
 #if GTK_3_12
         add_end(search_entry);
         add_end(search_upgrade_progress_bar);
+        add_end(undo);
         add_end(archive_trash_delete_empty);
 #endif
         
diff --git a/src/engine/api/geary-revokable.vala b/src/engine/api/geary-revokable.vala
new file mode 100644
index 0000000..51b6f95
--- /dev/null
+++ b/src/engine/api/geary-revokable.vala
@@ -0,0 +1,49 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * A representation of an operation with the Geary Engine that make be revoked (undone) at a later
+ * time.
+ */
+
+public abstract class Geary.Revokable : BaseObject {
+    public const string PROP_CAN_REVOKE = "can-revoke";
+    public const string PROP_IS_REVOKING = "is-revoking";
+    
+    /**
+     * Indicates if { link revoke_async} is a valid operation for this { link Revokable}.
+     *
+     * Due to later operations or notifications, it's possible for the Revokable to go invalid.
+     * In some circumstances, this may be that it cannot fully revoke the original operation, in
+     * others it may be that it can't revoke any part of the original operation, depending on the
+     * nature of the operation.
+     */
+    public bool can_revoke { get; protected set; default = true; }
+    
+    /**
+     * Indicates a { link revoke_async} operation is underway.
+     *
+     * Only one revoke operation can occur at a time.  If this is true when revoke_async() is
+     * called, it will throw an Error.
+     */
+    public bool is_revoking { get; protected set; default = false; }
+    
+    protected Revokable() {
+    }
+    
+    /**
+     * Revoke (undo) the operation.
+     *
+     * Returns false if the operation failed and is no longer revokable.
+     *
+     * If the call throws an Error that does not necessarily mean the { link Revokable} is
+     * invalid.  Check the return value or { link can_revoke}.
+     *
+     * @throws EngineError.ALREADY_OPEN if { link is_revoking} is true when called.
+     */
+    public abstract async bool revoke_async(Cancellable? cancellable = null) throws Error;
+}
+


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