[geary/wip/730682-refine-convo-list] Add an action bar to the conversation list.



commit 3c113bfcf89794413a0731e6ea79017c8330f65b
Author: Michael James Gratton <mike vee net>
Date:   Wed Dec 27 00:32:08 2017 +1030

    Add an action bar to the conversation list.
    
    Based on design at:
    https://wiki.gnome.org/Apps/Geary/Design/ConversationLifecycle
    
    * src/client/components/conversation-action-bar.vala: Action bar
      implementation to manage which buttons are visible based on the
      selected folder.
    
    * src/client/components/main-window.vala (MainWindow): Add action bar to
      the main window when constructed.

 src/CMakeLists.txt                                 |    1 +
 src/client/components/conversation-action-bar.vala |  191 +++++++++++++++
 src/client/components/main-window.vala             |   17 ++-
 ui/CMakeLists.txt                                  |    1 +
 ui/conversation-action-bar.ui                      |  248 ++++++++++++++++++++
 ui/main-window.ui                                  |   22 ++-
 6 files changed, 474 insertions(+), 6 deletions(-)
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 78a1ec2..fb88f9b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -340,6 +340,7 @@ client/accounts/add-edit-page.vala
 client/accounts/login-dialog.vala
 
 client/components/client-web-view.vala
+client/components/conversation-action-bar.vala
 client/components/count-badge.vala
 client/components/empty-placeholder.vala
 client/components/folder-popover.vala
diff --git a/src/client/components/conversation-action-bar.vala 
b/src/client/components/conversation-action-bar.vala
new file mode 100644
index 0000000..9ceaba6
--- /dev/null
+++ b/src/client/components/conversation-action-bar.vala
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2017 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * A context-sensitive set of action buttons for a conversation.
+ */
+[GtkTemplate (ui = "/org/gnome/Geary/conversation-action-bar.ui")]
+public class ConversationActionBar : Gtk.ActionBar {
+
+    private Geary.Account? account = null;
+
+    private bool has_archive = false;
+    private bool has_trash = false;
+
+    private FolderPopover copy_folder_menu = new FolderPopover();
+    private FolderPopover move_folder_menu = new FolderPopover();
+
+    [GtkChild]
+    private Gtk.Grid flag_actions;
+    [GtkChild]
+    private Gtk.Button mark_read_action;
+    [GtkChild]
+    private Gtk.Button mark_unread_action;
+    [GtkChild]
+    private Gtk.Button mark_starred_action;
+    [GtkChild]
+    private Gtk.Button mark_unstarred_action;
+
+    [GtkChild]
+    private Gtk.Grid folder_actions;
+    [GtkChild]
+    private Gtk.Button archive_action;
+    [GtkChild]
+    private Gtk.Button restore_action;
+    [GtkChild]
+    private Gtk.MenuButton copy_action;
+    [GtkChild]
+    private Gtk.MenuButton move_action;
+
+    [GtkChild]
+    private Gtk.Grid destructive_actions;
+    [GtkChild]
+    private Gtk.Button junk_action;
+    [GtkChild]
+    private Gtk.Button trash_action;
+    [GtkChild]
+    private Gtk.Button delete_action;
+
+
+    public ConversationActionBar() {
+        this.copy_action.popover = copy_folder_menu;
+        this.move_action.popover = move_folder_menu;
+    }
+
+    public void set_account(Geary.Account account) {
+        if (this.account != null) {
+            this.account.folders_special_type.disconnect(on_special_folder_changed);
+        }
+
+        this.account = account;
+        this.account.folders_special_type.connect(on_special_folder_changed);
+        update_account();
+    }
+
+    public void update_location(Geary.Folder location) {
+        Gtk.Button? primary_action = null;
+        bool show_flag_actions = false;
+        bool show_folder_actions = false;
+        bool show_junk = false;
+        bool show_trash = false;
+        bool show_delete = false;
+
+        switch (location.special_folder_type) {
+        case Geary.SpecialFolderType.INBOX:
+            if (this.has_archive) {
+                primary_action = archive_action;
+            }
+            show_flag_actions = true;
+            show_folder_actions = true;
+            show_junk = true;
+            show_trash = true;
+            break;
+
+        case Geary.SpecialFolderType.ARCHIVE:
+        case Geary.SpecialFolderType.NONE:
+            primary_action = restore_action;
+            show_flag_actions = true;
+            show_folder_actions = true;
+            show_junk = true;
+            show_trash = true;
+            break;
+
+        case Geary.SpecialFolderType.SENT:
+            show_flag_actions = true;
+            show_trash = true;
+            break;
+
+        case Geary.SpecialFolderType.DRAFTS:
+            show_trash = true;
+            break;
+
+        case Geary.SpecialFolderType.TRASH:
+            primary_action = restore_action;
+            show_junk = true;
+            show_delete = true;
+            break;
+
+        case Geary.SpecialFolderType.SPAM:
+            primary_action = restore_action;
+            show_delete = true;
+            break;
+
+        case Geary.SpecialFolderType.OUTBOX:
+            show_delete = true;
+            break;
+
+        default:
+            // XXX remainder (Search, All, Flagged, etc) are all
+            // conversation specific, so examine it/them and work out
+            // what to do here
+            show_flag_actions = true;
+            break;
+        }
+
+        // XXX just always hide these for now while the UX is sorted out
+        //this.flag_actions.set_visible(show_flag_actions);
+        this.flag_actions.set_visible(false);
+        update_action_pair(this.mark_read_action, this.mark_unread_action);
+        update_action_pair(this.mark_starred_action, this.mark_unstarred_action);
+
+        this.folder_actions.set_visible(primary_action != null || show_folder_actions);
+        this.archive_action.set_visible(primary_action == this.archive_action);
+        this.restore_action.set_visible(primary_action == this.restore_action);
+        this.copy_action.set_visible(show_folder_actions);
+        this.move_action.set_visible(show_folder_actions);
+
+        if (show_trash && !this.has_trash) {
+            show_trash = false;
+            show_delete = true;
+        }
+        this.destructive_actions.set_visible(
+            show_junk || show_trash || show_delete
+        );
+        this.junk_action.set_visible(show_junk);
+        this.trash_action.set_visible(show_trash);
+        this.delete_action.set_visible(show_delete && !show_trash);
+    }
+
+    private void update_account() {
+        try {
+            this.has_archive = (
+                this.account.get_special_folder(Geary.SpecialFolderType.ARCHIVE) != null
+            );
+        } catch (Error err) {
+            debug("Could not get Archive for account: %s", this.account.to_string());
+        }
+        try {
+            this.has_trash = (
+                this.account.get_special_folder(Geary.SpecialFolderType.TRASH) != null
+            );
+        } catch (Error err) {
+            debug("Could not get Trash for account: %s", this.account.to_string());
+        }
+    }
+
+    private inline void update_action_pair(Gtk.Button primary, Gtk.Button secondary) {
+        bool show_primary = true;
+        string? secondary_action_name = secondary.get_action_name();
+        MainWindow? window = get_toplevel() as MainWindow;
+        if (window != null && secondary_action_name != null) {
+            Action? secondary_action = window.lookup_action(
+                secondary_action_name.substring(4) // chop off the "win."
+            );
+            if (secondary_action != null) {
+                show_primary = !secondary_action.get_enabled();
+            }
+        }
+
+        primary.set_visible(show_primary);
+        secondary.set_visible(!show_primary);
+    }
+
+    private void on_special_folder_changed() {
+        update_account();
+    }
+
+}
diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala
index 55ede0c..8d8c318 100644
--- a/src/client/components/main-window.vala
+++ b/src/client/components/main-window.vala
@@ -42,7 +42,10 @@ public class MainWindow : Gtk.ApplicationWindow {
 
     public Geary.Folder? current_folder { get; private set; default = null; }
 
-    private Geary.AggregateProgressMonitor progress_monitor = new Geary.AggregateProgressMonitor();
+    private ConversationActionBar conversation_list_actions =
+        new ConversationActionBar();
+    private Geary.AggregateProgressMonitor progress_monitor =
+        new Geary.AggregateProgressMonitor();
     private Geary.ProgressMonitor? folder_progress = null;
 
     private MonitoredSpinner spinner = new MonitoredSpinner();
@@ -59,9 +62,12 @@ public class MainWindow : Gtk.ApplicationWindow {
     private Gtk.Box folder_box;
     [GtkChild]
     private Gtk.ScrolledWindow folder_list_scrolled;
+
     [GtkChild]
     private Gtk.Box conversation_box;
     [GtkChild]
+    private Gtk.Grid conversation_list_grid;
+    [GtkChild]
     private Gtk.ScrolledWindow conversation_list_scrolled;
 
     // This is a frame so users can use F6/Shift-F6 to get to it
@@ -85,8 +91,10 @@ public class MainWindow : Gtk.ApplicationWindow {
         this.conversation_list.marked_conversations_evaporated.connect(on_selection_mode_disabled);
         this.conversation_list.selection_mode_enabled.connect(on_selection_mode_enabled);
         this.conversation_list.visible_conversations_changed.connect(on_visible_conversations_changed);
-
         this.conversation_list.load_more.connect(on_load_more);
+
+        this.conversation_list_grid.add(this.conversation_list_actions);
+
         load_config(application.config);
         restore_saved_window_state();
 
@@ -442,8 +450,11 @@ public class MainWindow : Gtk.ApplicationWindow {
             this.current_folder.properties.notify.disconnect(update_headerbar);
 
         // connect to new folder
-        if (folder != null)
+        if (folder != null) {
             folder.properties.notify.connect(update_headerbar);
+            this.conversation_list_actions.set_account(folder.account);
+            this.conversation_list_actions.update_location(this.current_folder);
+        }
 
         // swap it in
         this.current_folder = folder;
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index e8aaa76..5164978 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -12,6 +12,7 @@ set(RESOURCE_LIST
   STRIPBLANKS "composer-widget.ui"
               "composer-web-view.css"
               "composer-web-view.js"
+  STRIPBLANKS "conversation-action-bar.ui"
   STRIPBLANKS "conversation-email.ui"
   STRIPBLANKS "conversation-email-attachment-view.ui"
   STRIPBLANKS "conversation-email-menus.ui"
diff --git a/ui/conversation-action-bar.ui b/ui/conversation-action-bar.ui
new file mode 100644
index 0000000..c84cdc6
--- /dev/null
+++ b/ui/conversation-action-bar.ui
@@ -0,0 +1,248 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.2 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <template class="ConversationActionBar" parent="GtkActionBar">
+    <property name="can_focus">False</property>
+    <property name="can_focus">False</property>
+    <property name="no_show_all">True</property>
+    <property name="visible">True</property>
+    <child>
+      <object class="GtkGrid" id="flag_actions">
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkButton" id="mark_read_action">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="action_name">win.conversation-mark-read</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="icon_name">mail-unread-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="mark_unread_action">
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="action_name">win.conversation-mark-unread</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="icon_name">mail-read-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="mark_starred_action">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="action_name">win.conversation-mark-starred</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="icon_name">starred-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">2</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="mark_unstarred_action">
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="action_name">win.conversation-mark-unstarred</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="icon_name">starred-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">3</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <style>
+          <class name="linked"/>
+        </style>
+      </object>
+      <packing>
+        <property name="pack_type">start</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkGrid" id="folder_actions">
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkButton" id="archive_action">
+            <property name="label" translatable="yes">Archive</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="action_name">win.conversation-archive</property>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="restore_action">
+            <property name="label" translatable="yes">Restore</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="action_name">win.conversation-restore</property>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkMenuButton" id="copy_action">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="icon_name">tag-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">2</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkMenuButton" id="move_action">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="icon_name">folder-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">3</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <style>
+          <class name="linked"/>
+        </style>
+      </object>
+      <packing>
+        <property name="pack_type">start</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkGrid" id="destructive_actions">
+        <property name="can_focus">False</property>
+        <property name="halign">end</property>
+        <property name="hexpand">True</property>
+        <child>
+          <object class="GtkButton" id="junk_action">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="action_name">win.conversation-junk</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="icon_name">dialog-warning-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="trash_action">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="action_name">win.conversation-trash</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="icon_name">user-trash-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="delete_action">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="halign">end</property>
+            <property name="hexpand">True</property>
+            <property name="action_name">win.conversation-delete</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="icon_name">edit-delete-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">2</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <style>
+          <class name="linked"/>
+        </style>
+      </object>
+      <packing>
+        <property name="pack_type">end</property>
+        <property name="position">2</property>
+      </packing>
+    </child>
+    <style>
+      <class name="background"/>
+      <class name="geary-conversation-action-bar"/>
+    </style>
+  </template>
+</interface>
diff --git a/ui/main-window.ui b/ui/main-window.ui
index a7ecc85..d903fd6 100644
--- a/ui/main-window.ui
+++ b/ui/main-window.ui
@@ -75,11 +75,27 @@
                             <property name="label_xalign">0</property>
                             <property name="shadow_type">in</property>
                             <child>
-                              <object class="GtkScrolledWindow" id="conversation_list_scrolled">
-                                <property name="width_request">250</property>
+                              <object class="GtkGrid" id="conversation_list_grid">
                                 <property name="visible">True</property>
                                 <property name="can_focus">False</property>
-                                <property name="hscrollbar_policy">never</property>
+                                <property name="orientation">vertical</property>
+                                <child>
+                                  <object class="GtkScrolledWindow" id="conversation_list_scrolled">
+                                    <property name="width_request">250</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="hexpand">True</property>
+                                    <property name="vexpand">True</property>
+                                    <property name="hscrollbar_policy">never</property>
+                                  </object>
+                                  <packing>
+                                    <property name="left_attach">0</property>
+                                    <property name="top_attach">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <placeholder/>
+                                </child>
                               </object>
                             </child>
                             <style>


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