[geary/wip/714104-refine-account-dialog: 5/5] Implement initial drag and drop for accounts, sender mailbox ordering



commit 6c22f76e5349b1ec81f27e46901167c0d6186c47
Author: Michael Gratton <mike vee net>
Date:   Sun Dec 2 13:51:15 2018 +1100

    Implement initial drag and drop for accounts, sender mailbox ordering

 src/client/accounts/accounts-editor-edit-pane.vala |  72 +++++++++-
 src/client/accounts/accounts-editor-list-pane.vala | 147 +++++++++++++++------
 src/client/accounts/accounts-editor-row.vala       |  75 +++++++++++
 ui/geary.css                                       |  21 ++-
 4 files changed, 263 insertions(+), 52 deletions(-)
---
diff --git a/src/client/accounts/accounts-editor-edit-pane.vala 
b/src/client/accounts/accounts-editor-edit-pane.vala
index 4e8c1c30..bb793d74 100644
--- a/src/client/accounts/accounts-editor-edit-pane.vala
+++ b/src/client/accounts/accounts-editor-edit-pane.vala
@@ -62,9 +62,9 @@ internal class Accounts.EditorEditPane : Gtk.Grid, EditorPane, AccountPane {
         this.details_list.add(new NicknameRow(account));
 
         this.senders_list.set_header_func(Editor.seperator_headers);
-        foreach (Geary.RFC822.MailboxAddress sender
-                 in account.get_sender_mailboxes()) {
-            this.senders_list.add(new MailboxRow(account, sender));
+        foreach (Geary.RFC822.MailboxAddress sender in
+                 account.sender_mailboxes) {
+            this.senders_list.add(new_mailbox_row(sender));
         }
         this.senders_list.add(new AddMailboxRow());
 
@@ -173,6 +173,12 @@ internal class Accounts.EditorEditPane : Gtk.Grid, EditorPane, AccountPane {
         );
     }
 
+    internal MailboxRow new_mailbox_row(Geary.RFC822.MailboxAddress sender) {
+        MailboxRow row = new MailboxRow(this.account, sender);
+        row.dropped.connect(on_sender_row_dropped);
+        return row;
+    }
+
     private void on_account_changed() {
         update_header();
     }
@@ -181,6 +187,18 @@ internal class Accounts.EditorEditPane : Gtk.Grid, EditorPane, AccountPane {
         update_actions();
     }
 
+    private void on_sender_row_dropped(EditorRow source, EditorRow target) {
+        this.commands.execute.begin(
+            new ReorderMailboxCommand(
+                (MailboxRow) source,
+                (MailboxRow) target,
+                this.account,
+                this.senders_list
+            ),
+            null
+        );
+    }
+
     [GtkCallback]
     private void on_setting_activated(Gtk.ListBoxRow row) {
         EditorRow<EditorEditPane>? setting = row as EditorRow<EditorEditPane>;
@@ -309,8 +327,7 @@ private class Accounts.AddMailboxRow : AddRow<EditorEditPane> {
                 pane.commands.execute.begin(
                     new AppendMailboxCommand(
                         (Gtk.ListBox) get_parent(),
-                        new MailboxRow(
-                            pane.account,
+                        pane.new_mailbox_row(
                             new Geary.RFC822.MailboxAddress(
                                 popover.display_name,
                                 popover.address
@@ -338,6 +355,7 @@ private class Accounts.MailboxRow : AccountRow<EditorEditPane,Gtk.Label> {
                       Geary.RFC822.MailboxAddress mailbox) {
         base(account, "", new Gtk.Label(""));
         this.mailbox = mailbox;
+        enable_drag();
 
         update();
     }
@@ -584,6 +602,50 @@ internal class Accounts.UpdateMailboxCommand : Application.Command {
 }
 
 
+internal class Accounts.ReorderMailboxCommand : Application.Command {
+
+
+    private MailboxRow source;
+    private int source_index;
+    private int target_index;
+
+    private Geary.AccountInformation account;
+    private Gtk.ListBox list;
+
+
+    public ReorderMailboxCommand(MailboxRow source,
+                                 MailboxRow target,
+                                 Geary.AccountInformation account,
+                                 Gtk.ListBox list) {
+        this.source = source;
+        this.source_index = source.get_index();
+        this.target_index = target.get_index();
+
+        this.account = account;
+        this.list = list;
+    }
+
+    public async override void execute(GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        move_source(this.target_index);
+    }
+
+    public async override void undo(GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        move_source(this.source_index);
+    }
+
+    private void move_source(int destination) {
+        this.account.remove_sender(this.source.mailbox);
+        this.account.insert_sender(destination, this.source.mailbox);
+
+        this.list.remove(this.source);
+        this.list.insert(this.source, destination);
+    }
+
+}
+
+
 internal class Accounts.RemoveMailboxCommand : Application.Command {
 
 
diff --git a/src/client/accounts/accounts-editor-list-pane.vala 
b/src/client/accounts/accounts-editor-list-pane.vala
index 3165bb83..bae6576a 100644
--- a/src/client/accounts/accounts-editor-list-pane.vala
+++ b/src/client/accounts/accounts-editor-list-pane.vala
@@ -158,7 +158,9 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
 
     private void add_account(Geary.AccountInformation account,
                              Manager.Status status) {
-        this.accounts_list.add(new AccountListRow(account, status));
+        AccountListRow row = new AccountListRow(account, status);
+        row.dropped.connect(on_editor_row_dropped);
+        this.accounts_list.add(row);
     }
 
     private void add_notification(InAppNotification notification) {
@@ -216,6 +218,15 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
         }
     }
 
+    private void on_editor_row_dropped(EditorRow source, EditorRow target) {
+        this.commands.execute.begin(
+            new ReorderAccountCommand(
+                (AccountListRow) source, (AccountListRow) target, this.accounts
+            ),
+            null
+        );
+    }
+
     private void on_account_removed(Geary.AccountInformation account) {
         AccountListRow? row = get_account_row(account);
         if (row != null) {
@@ -225,17 +236,21 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
     }
 
     private void on_execute(Application.Command command) {
-        InAppNotification ian = new InAppNotification(command.executed_label);
-        ian.set_button(_("Undo"), "win." + GearyController.ACTION_UNDO);
-        add_notification(ian);
+        if (command.executed_label != null) {
+            InAppNotification ian = new InAppNotification(command.executed_label);
+            ian.set_button(_("Undo"), "win." + GearyController.ACTION_UNDO);
+            add_notification(ian);
+        }
 
         update_actions();
     }
 
     private void on_undo(Application.Command command) {
-        InAppNotification ian = new InAppNotification(command.undone_label);
-        ian.set_button(_("Redo"), "win." + GearyController.ACTION_REDO);
-        add_notification(ian);
+        if (command.undone_label != null) {
+            InAppNotification ian = new InAppNotification(command.undone_label);
+            ian.set_button(_("Redo"), "win." + GearyController.ACTION_REDO);
+            add_notification(ian);
+        }
 
         update_actions();
     }
@@ -267,34 +282,26 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
 }
 
 
-private class Accounts.AccountListRow : EditorRow<EditorListPane> {
-
+private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
 
-    internal Geary.AccountInformation account;
 
+    private Gtk.Label service_label = new Gtk.Label("");
     private Gtk.Image unavailable_icon = new Gtk.Image.from_icon_name(
         "dialog-warning-symbolic", Gtk.IconSize.BUTTON
     );
-    private Gtk.Label account_name = new Gtk.Label("");
-    private Gtk.Label account_details = new Gtk.Label("");
-
 
     public AccountListRow(Geary.AccountInformation account,
                           Manager.Status status) {
-        this.account = account;
-
-        this.account_name.show();
-        this.account_name.set_hexpand(true);
-        this.account_name.halign = Gtk.Align.START;
+        base(account, "", new Gtk.Grid());
+        enable_drag();
 
-        this.account_details.show();
+        this.value.add(this.unavailable_icon);
+        this.value.add(this.service_label);
 
-        this.layout.add(this.unavailable_icon);
-        this.layout.add(this.account_name);
-        this.layout.add(this.account_details);
+        this.service_label.show();
 
         this.account.information_changed.connect(on_account_changed);
-        update_nickname();
+        update();
         update_status(status);
     }
 
@@ -327,24 +334,12 @@ private class Accounts.AccountListRow : EditorRow<EditorListPane> {
         }
     }
 
-    public void update_nickname() {
+    public override void update() {
         string name = this.account.nickname;
         if (Geary.String.is_empty(name)) {
             name = account.primary_mailbox.to_address_display("", "");
         }
-        this.account_name.set_text(name);
-    }
-
-    public void update_status(Manager.Status status) {
-        if (status != Manager.Status.UNAVAILABLE) {
-            this.unavailable_icon.hide();
-            this.set_tooltip_text("");
-        } else {
-            this.unavailable_icon.show();
-            this.set_tooltip_text(
-                _("This account has encountered a problem and is unavailable")
-            );
-        }
+        this.label.set_text(name);
 
         string? details = this.account.service_label;
         switch (account.service_provider) {
@@ -360,27 +355,43 @@ private class Accounts.AccountListRow : EditorRow<EditorListPane> {
             details = _("Yahoo");
             break;
         }
-        this.account_details.set_text(details);
+        this.service_label.set_text(details);
+    }
+
+    public void update_status(Manager.Status status) {
+        if (status != Manager.Status.UNAVAILABLE) {
+            this.unavailable_icon.hide();
+            this.set_tooltip_text("");
+        } else {
+            this.unavailable_icon.show();
+            this.set_tooltip_text(
+                _("This account has encountered a problem and is unavailable")
+            );
+        }
 
         if (status == Manager.Status.ENABLED) {
-            this.account_name.get_style_context().remove_class(
+            this.label.get_style_context().remove_class(
                 Gtk.STYLE_CLASS_DIM_LABEL
             );
-            this.account_details.get_style_context().remove_class(
+            this.service_label.get_style_context().remove_class(
                 Gtk.STYLE_CLASS_DIM_LABEL
             );
         } else {
-            this.account_name.get_style_context().add_class(
+            this.label.get_style_context().add_class(
                 Gtk.STYLE_CLASS_DIM_LABEL
             );
-            this.account_details.get_style_context().add_class(
+            this.service_label.get_style_context().add_class(
                 Gtk.STYLE_CLASS_DIM_LABEL
             );
         }
     }
 
     private void on_account_changed() {
-        update_nickname();
+        update();
+        Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
+        if (parent != null) {
+            parent.invalidate_sort();
+        }
     }
 
 }
@@ -451,6 +462,56 @@ private class Accounts.AddServiceProviderRow : EditorRow<EditorListPane> {
 }
 
 
+internal class Accounts.ReorderAccountCommand : Application.Command {
+
+
+    private AccountListRow source;
+    private int source_index;
+    private int target_index;
+
+    private Manager manager;
+
+
+    public ReorderAccountCommand(AccountListRow source,
+                                 AccountListRow target,
+                                 Manager manager) {
+        this.source = source;
+        this.source_index = source.get_index();
+        this.target_index = target.get_index();
+
+        this.manager = manager;
+    }
+
+    public async override void execute(GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        move_source(this.target_index);
+    }
+
+    public async override void undo(GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        move_source(this.source_index);
+    }
+
+    private void move_source(int destination) {
+        Gee.List<Geary.AccountInformation> accounts =
+            this.manager.iterable().to_linked_list();
+        accounts.sort(Geary.AccountInformation.compare_ascending);
+        accounts.remove(this.source.account);
+        accounts.insert(destination, this.source.account);
+
+        int ord = 0;
+        foreach (Geary.AccountInformation account in accounts) {
+            if (account.ordinal != ord) {
+                account.ordinal = ord;
+                account.information_changed();
+            }
+            ord++;
+        }
+    }
+
+}
+
+
 internal class Accounts.RemoveAccountCommand : Application.Command {
 
 
diff --git a/src/client/accounts/accounts-editor-row.vala b/src/client/accounts/accounts-editor-row.vala
index 44336775..681faa48 100644
--- a/src/client/accounts/accounts-editor-row.vala
+++ b/src/client/accounts/accounts-editor-row.vala
@@ -8,9 +8,19 @@
 
 internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
 
+    private const string DND_ATOM = "geary-editor-row";
+    private const Gtk.TargetEntry[] DRAG_ENTRIES = {
+        { DND_ATOM, Gtk.TargetFlags.SAME_APP, 0 }
+    };
+
 
     protected Gtk.Grid layout { get; private set; default = new Gtk.Grid(); }
 
+    private Gtk.Container drag_handle;
+
+
+    public signal void dropped(EditorRow target);
+
 
     public EditorRow() {
         get_style_context().add_class("geary-settings");
@@ -19,6 +29,23 @@ internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
         this.layout.show();
         add(this.layout);
 
+        // We'd like to add the drag handle only when needed, but
+        // GNOME/gtk#1495 prevents us from doing so.
+        Gtk.EventBox drag_box = new Gtk.EventBox();
+        drag_box.add(
+            new Gtk.Image.from_icon_name(
+                "open-menu-symbolic", Gtk.IconSize.BUTTON
+            )
+        );
+        this.drag_handle = new Gtk.Grid();
+        this.drag_handle.valign = Gtk.Align.CENTER;
+        this.drag_handle.add(drag_box);
+        this.drag_handle.show_all();
+        this.drag_handle.hide();
+        // Translators: Tooltip for dragging list items
+        this.drag_handle.set_tooltip_text(_("Drag to move this item"));
+        this.layout.add(drag_handle);
+
         this.show();
     }
 
@@ -26,6 +53,54 @@ internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
         // No-op by default
     }
 
+    /** Adds a drag handle to the row and enables drag signals. */
+    protected void enable_drag() {
+        Gtk.drag_source_set(
+            this.drag_handle,
+            Gdk.ModifierType.BUTTON1_MASK,
+            DRAG_ENTRIES,
+            Gdk.DragAction.MOVE
+        );
+
+        Gtk.drag_dest_set(
+            this,
+            Gtk.DestDefaults.ALL,
+            DRAG_ENTRIES,
+            Gdk.DragAction.MOVE
+        );
+
+        this.drag_handle.drag_data_get.connect(on_drag_data_get);
+        this.drag_data_received.connect(on_drag_data_received);
+        this.drag_handle.get_style_context().add_class("geary-drag-handle");
+        this.drag_handle.show();
+
+        get_style_context().add_class("geary-draggable");
+    }
+
+
+    private void on_drag_data_get(Gdk.DragContext context,
+                                  Gtk.SelectionData selection_data,
+                                  uint info, uint time_) {
+        selection_data.set(
+            Gdk.Atom.intern_static_string(DND_ATOM), 8,
+            get_index().to_string().data
+        );
+    }
+
+    private void on_drag_data_received(Gdk.DragContext context,
+                                       int x, int y,
+                                       Gtk.SelectionData selection_data,
+                                       uint info, uint time_) {
+        int drag_index = int.parse((string) selection_data.get_data());
+        Gtk.ListBox? parent = this.get_parent() as Gtk.ListBox;
+        if (parent != null) {
+            EditorRow? drag_row = parent.get_row_at_index(drag_index) as EditorRow;
+            if (drag_row != null && drag_row != this) {
+                drag_row.dropped(this);
+            }
+        }
+    }
+
 }
 
 
diff --git a/ui/geary.css b/ui/geary.css
index a9530a26..3cbc9afc 100644
--- a/ui/geary.css
+++ b/ui/geary.css
@@ -208,6 +208,10 @@ row.geary-settings {
   padding: 0px;
 }
 
+row.geary-settings image {
+  padding: 0px 6px;
+}
+
 row.geary-settings > grid > * {
   margin: 18px 6px;
 }
@@ -222,11 +226,20 @@ row.geary-settings > grid > *:first-child:dir(rtl) {
   margin-right: 18px;
 }
 
-row.geary-settings > grid > image:dir(ltr) {
-  margin-right: 6px;
+/* dir pseudo-class used here for required additional specificity */
+row.geary-settings > grid > grid.geary-drag-handle:dir(ltr),
+row.geary-settings > grid > grid.geary-drag-handle:dir(rtl) {
+  margin: 0;
 }
-row.geary-settings > grid > image:dir(rtl) {
-  margin-left: 6px;
+
+row.geary-settings > grid > grid.geary-drag-handle image:dir(ltr) {
+  padding: 12px;
+  padding-right: 6px;
+}
+
+row.geary-settings > grid > grid.geary-drag-handle image:dir(rtl) {
+  padding: 12px;
+  padding-left: 6px;
 }
 
 frame.geary-settings.geary-signature {


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