[geary/wip/713872-filtering] Squashed commit of the following:



commit 3ec1f6dfcc3169f960e97df60dbce01d59c6f2ba
Author: Private <geek vigilante gmail com>
Date:   Tue Oct 21 13:40:48 2014 -0700

    Squashed commit of the following:
    
    commit 2a17916d8c0afa06b8cac91c800c5f699fa540dd
    Author: Private <geek vigilante gmail com>
    Date:   Sun Oct 12 22:39:09 2014 +0200
    
        Completed message filter.
    
    commit 904f81b8873ab535711d001df30d2943d520d458
    Author: Private <geek vigilante gmail com>
    Date:   Sun Sep 28 10:02:05 2014 +0200
    
        Added option for folder filtering by unread and starred messages.
    
    From https://github.com/geekvigilante/geary

 po/POTFILES.in                                     |    1 +
 src/CMakeLists.txt                                 |    1 +
 src/client/application/geary-controller.vala       |   75 +++++++++++++++
 src/client/components/main-toolbar.vala            |   10 ++
 src/client/components/main-window.vala             |    5 +-
 .../conversation-list/conversation-filter.vala     |  100 ++++++++++++++++++++
 .../conversation-list/conversation-list-store.vala |   16 ---
 .../conversation-list/conversation-list-view.vala  |   29 +++---
 ui/CMakeLists.txt                                  |    1 +
 ui/toolbar_filter_menu.ui                          |    9 ++
 10 files changed, 216 insertions(+), 31 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 77f2fec..07a3825 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -365,5 +365,6 @@ src/engine/util/util-trillian.vala
 [type: gettext/glade]ui/preferences.glade
 [type: gettext/glade]ui/remove_confirm.glade
 [type: gettext/glade]ui/toolbar_mark_menu.ui
+[type: gettext/glade]ui/toolbar_filter_menu.ui
 [type: gettext/glade]ui/toolbar_menu.ui
 [type: gettext/glade]ui/upgrade_dialog.glade
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5579bcb..e8ce45b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -337,6 +337,7 @@ client/composer/email-entry.vala
 client/composer/scrollable-overlay.vala
 client/composer/webview-edit-fixer.vala
 
+client/conversation-list/conversation-filter.vala
 client/conversation-list/conversation-list-cell-renderer.vala
 client/conversation-list/conversation-list-store.vala
 client/conversation-list/conversation-list-view.vala
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index c53fb67..73bb569 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -46,6 +46,10 @@ public class GearyController : Geary.BaseObject {
     public const string ACTION_MARK_AS_SPAM = "GearyMarkAsSpam";
     public const string ACTION_COPY_MENU = "GearyCopyMenuButton";
     public const string ACTION_MOVE_MENU = "GearyMoveMenuButton";
+    public const string ACTION_FILTER_MENU = "GearyFilterMenuButton";
+    public const string ACTION_FILTER_SHOW_ALL = "GearyFilterShowAll";
+    public const string ACTION_FILTER_SHOW_UNREAD = "GearyFilterShowUnread";
+    public const string ACTION_FILTER_SHOW_STARRED = "GearyFilterShowStarred";
     public const string ACTION_GEAR_MENU = "GearyGearMenuButton";
     public const string ACTION_SEARCH = "GearySearch";
     
@@ -334,6 +338,11 @@ public class GearyController : Geary.BaseObject {
         Gtk.ActionEntry move_menu = { ACTION_MOVE_MENU, null, TRANSLATABLE, "M", _("Move conversation"), 
null };
         move_menu.label = _("_Move");
         entries += move_menu;
+        
+        Gtk.ActionEntry filter_menu = { ACTION_FILTER_MENU, null, TRANSLATABLE, null, _("Filter messages"),
+            null };
+        filter_menu.label = _("_Filter");
+        entries += filter_menu;
 
         Gtk.ActionEntry new_message = { ACTION_NEW_MESSAGE, null, null, "<Ctrl>N", 
             _("Compose new message (Ctrl+N, N)"), on_new_message };
@@ -419,6 +428,21 @@ public class GearyController : Geary.BaseObject {
             null, null, false };
         entries += gear_menu;
         
+        Gtk.ToggleActionEntry filter_show_all = { ACTION_FILTER_SHOW_ALL, "filter-show-all", TRANSLATABLE, 
null,
+            null, on_filter_show_all, true };
+        filter_show_all.label = _("Show _All Messages");
+        entries += filter_show_all;
+        
+        Gtk.ToggleActionEntry filter_show_unread = { ACTION_FILTER_SHOW_UNREAD, "filter-show-unread",
+            TRANSLATABLE, null, null, on_filter_show_unread, false };
+        filter_show_unread.label = _("Only _Unread Messages");
+        entries += filter_show_unread;
+        
+        Gtk.ToggleActionEntry filter_show_starred = { ACTION_FILTER_SHOW_STARRED, "filter-show-starred",
+            TRANSLATABLE, null, null, on_filter_show_starred, false };
+        filter_show_starred.label = _("Only _Starred Messages");
+        entries += filter_show_starred;
+        
         return entries;
     }
     
@@ -1332,6 +1356,10 @@ public class GearyController : Geary.BaseObject {
         
         select_folder_mutex.release(ref mutex_token);
         
+        Gtk.ActionGroup actions = GearyApplication.instance.actions;
+        Gtk.ToggleAction show_all_action = actions.get_action(ACTION_FILTER_SHOW_ALL) as Gtk.ToggleAction;
+        show_all_action.set_active(true);
+        
         debug("Switched to %s", folder.to_string());
     }
     
@@ -1824,6 +1852,53 @@ public class GearyController : Geary.BaseObject {
         mark_as_spam_async.begin(null);
     }
     
+    private void on_filter_show_all() {
+        Gtk.ActionGroup actions = GearyApplication.instance.actions;
+        
+        Gtk.ToggleAction show_all_action = actions.get_action(ACTION_FILTER_SHOW_ALL) as Gtk.ToggleAction;
+        Gtk.ToggleAction show_unread_action = actions.get_action(ACTION_FILTER_SHOW_UNREAD) as 
Gtk.ToggleAction;
+        Gtk.ToggleAction show_starred_action = actions.get_action(ACTION_FILTER_SHOW_STARRED) as 
Gtk.ToggleAction;
+        
+        if (show_unread_action.active || show_starred_action.active) {
+            if (show_all_action.active) {
+                show_unread_action.active = false;
+                show_starred_action.active = false;
+            }
+        } else {
+            show_all_action.active = true;
+        }
+    }
+    
+    private void on_filter_show_unread() {
+        Gtk.ActionGroup actions = GearyApplication.instance.actions;
+        
+        Gtk.ToggleAction show_all_action = actions.get_action(ACTION_FILTER_SHOW_ALL) as Gtk.ToggleAction;
+        Gtk.ToggleAction show_unread_action = actions.get_action(ACTION_FILTER_SHOW_UNREAD) as 
Gtk.ToggleAction;
+        Gtk.ToggleAction show_starred_action = actions.get_action(ACTION_FILTER_SHOW_STARRED) as 
Gtk.ToggleAction;
+        
+        if (show_unread_action.active)
+            show_all_action.active = false;
+        else if (!show_starred_action.active)
+            show_all_action.active = true;
+        
+        main_window.conversation_filter.set_unread_filter(show_unread_action.active);
+    }
+    
+    private void on_filter_show_starred() {
+        Gtk.ActionGroup actions = GearyApplication.instance.actions;
+        
+        Gtk.ToggleAction show_all_action = actions.get_action(ACTION_FILTER_SHOW_ALL) as Gtk.ToggleAction;
+        Gtk.ToggleAction show_unread_action = actions.get_action(ACTION_FILTER_SHOW_UNREAD) as 
Gtk.ToggleAction;
+        Gtk.ToggleAction show_starred_action = actions.get_action(ACTION_FILTER_SHOW_STARRED) as 
Gtk.ToggleAction;
+        
+        if (show_starred_action.active)
+            show_all_action.active = false;
+        else if (!show_unread_action.active)
+            show_all_action.active = true;
+        
+        main_window.conversation_filter.set_starred_filter(show_starred_action.active);
+    }
+    
     private void copy_email(Gee.Collection<Geary.EmailIdentifier> ids,
         Geary.FolderPath destination) {
         if (ids.size > 0) {
diff --git a/src/client/components/main-toolbar.vala b/src/client/components/main-toolbar.vala
index 7e8d442..9969717 100644
--- a/src/client/components/main-toolbar.vala
+++ b/src/client/components/main-toolbar.vala
@@ -35,6 +35,11 @@ public class MainToolbar : PillHeaderbar {
         Gtk.Menu mark_menu = (Gtk.Menu) 
GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMarkMenu");
         mark_menu.foreach(GtkUtil.show_menuitem_accel_labels);
         
+        // Assemble filter menu.
+        GearyApplication.instance.load_ui_file("toolbar_filter_menu.ui");
+        Gtk.Menu filter_menu = (Gtk.Menu) 
GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarFilterMenu");
+        filter_menu.foreach(GtkUtil.show_menuitem_accel_labels);
+        
         // Setup the application menu.
         GearyApplication.instance.load_ui_file("toolbar_menu.ui");
         Gtk.Menu application_menu = (Gtk.Menu) 
GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMenu");
@@ -61,6 +66,11 @@ public class MainToolbar : PillHeaderbar {
         insert.add(create_menu_button("folder-symbolic", move_folder_menu, 
GearyController.ACTION_MOVE_MENU));
         add_start(create_pill_buttons(insert));
         
+        // Filter.
+        insert.clear();
+        insert.add(create_menu_button("folder-saved-search-symbolic", filter_menu, 
GearyController.ACTION_FILTER_MENU));
+        add_start(create_pill_buttons(insert));
+        
         // The toolbar looks bad when you hide one of a pair of pill buttons.
         // Unfortunately, this means we have to have one pair for archive/trash
         // and one single button for just trash, for when the archive button is
diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala
index 0736f80..4ad691f 100644
--- a/src/client/components/main-window.vala
+++ b/src/client/components/main-window.vala
@@ -14,6 +14,7 @@ public class MainWindow : Gtk.ApplicationWindow {
     
     public FolderList.Tree folder_list { get; private set; default = new FolderList.Tree(); }
     public ConversationListStore conversation_list_store { get; private set; default = new 
ConversationListStore(); }
+    public ConversationFilter conversation_filter { get; private set; }
     public MainToolbar main_toolbar { get; private set; }
     public ConversationListView conversation_list_view  { get; private set; }
     public ConversationViewer conversation_viewer { get; private set; default = new ConversationViewer(); }
@@ -38,7 +39,9 @@ public class MainWindow : Gtk.ApplicationWindow {
         
         title = GearyApplication.NAME;
         
-        conversation_list_view = new ConversationListView(conversation_list_store);
+        conversation_filter = new ConversationFilter(conversation_list_store);
+        
+        conversation_list_view = new ConversationListView(conversation_filter);
         
         add_events(Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK
             | Gdk.EventMask.FOCUS_CHANGE_MASK);
diff --git a/src/client/conversation-list/conversation-filter.vala 
b/src/client/conversation-list/conversation-filter.vala
new file mode 100644
index 0000000..9c50f1f
--- /dev/null
+++ b/src/client/conversation-list/conversation-filter.vala
@@ -0,0 +1,100 @@
+/* Copyright 2011-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.
+ */
+
+public class ConversationFilter : Gtk.TreeModelFilter {
+    private bool filter_unread = false;
+    private bool filter_starred = false;
+    private Gee.HashSet<Geary.App.Conversation> filtered_conversations;
+    
+    public signal void conversations_added_began();
+    
+    public signal void conversations_added_finished();
+    
+    public ConversationFilter(ConversationListStore conversation_list_store) {
+        Object(child_model: conversation_list_store, virtual_root: null);
+        
+        filtered_conversations = new Gee.HashSet<Geary.App.Conversation>();
+        
+        set_visible_func(filter_func);
+        
+        conversation_list_store.conversations_added_began.connect(on_conversations_added_began);
+        conversation_list_store.conversations_added_finished.connect(on_conversations_added_finished);
+    }
+    
+    public void set_starred_filter(bool enabled) {
+        filter_starred = enabled;
+        filtered_conversations.clear();
+        refilter();
+    }
+    
+    public void set_unread_filter(bool enabled) {
+        filter_unread = enabled;
+        filtered_conversations.clear();
+        refilter();
+    }
+    
+    private bool filter_func(Gtk.TreeModel model, Gtk.TreeIter iter) {
+        Geary.App.Conversation conversation;
+        model.get(iter, ConversationListStore.Column.CONVERSATION_OBJECT, out conversation);
+        
+        bool pass = ( (!filter_starred || conversation.is_flagged()) &&
+                      (!filter_unread || conversation.is_unread()) );
+        if (pass) {
+            // Remember this conversation until filtering conditions change
+            filtered_conversations.add(conversation);
+        } else {
+            // Keep the conversation visible until filtering conditions change, even if user alters
+            // conversation's attributes so that the conversation would be filtered out
+            pass = filtered_conversations.contains(conversation);
+        }
+        
+        return pass;
+    }
+    
+    private void on_conversations_added_began() {
+        conversations_added_began();
+    }
+    
+    private void on_conversations_added_finished() {
+        conversations_added_finished();
+    }
+    
+    public Geary.App.Conversation? get_conversation_at_path(Gtk.TreePath path) {
+        Gtk.TreeIter iter;
+        if (!get_iter(out iter, path))
+            return null;
+        
+        return get_conversation_at_iter(iter);
+    }
+    
+    private Geary.App.Conversation? get_conversation_at_iter(Gtk.TreeIter iter) {
+        Geary.App.Conversation? conversation;
+        get(iter, ConversationListStore.Column.CONVERSATION_OBJECT, out conversation);
+        
+        return conversation;
+    }
+    
+    public Gtk.TreePath? get_path_for_conversation(Geary.App.Conversation conversation) {
+        Gtk.TreeIter iter;
+        if (!get_iter_for_conversation(conversation, out iter))
+            return null;
+        
+        return get_path(iter);
+    }
+    
+    private bool get_iter_for_conversation(Geary.App.Conversation conversation, out Gtk.TreeIter iter) {
+        if (!get_iter_first(out iter))
+            return false;
+        
+        do {
+            if (get_conversation_at_iter(iter) == conversation)
+                return true;
+        } while (iter_next(ref iter));
+        
+        return false;
+    }
+}
+
diff --git a/src/client/conversation-list/conversation-list-store.vala 
b/src/client/conversation-list/conversation-list-store.vala
index 2444f7c..36a99ff 100644
--- a/src/client/conversation-list/conversation-list-store.vala
+++ b/src/client/conversation-list/conversation-list-store.vala
@@ -101,22 +101,6 @@ public class ConversationListStore : Gtk.ListStore {
         email_store = (current_folder == null ? null : new Geary.App.EmailStore(current_folder.account));
     }
     
-    public Geary.App.Conversation? get_conversation_at_path(Gtk.TreePath path) {
-        Gtk.TreeIter iter;
-        if (!get_iter(out iter, path))
-            return null;
-        
-        return get_conversation_at_iter(iter);
-    }
-    
-    public Gtk.TreePath? get_path_for_conversation(Geary.App.Conversation conversation) {
-        Gtk.TreeIter iter;
-        if (!get_iter_for_conversation(conversation, out iter))
-            return null;
-        
-        return get_path(iter);
-    }
-    
     private async void refresh_previews_async(Geary.App.ConversationMonitor conversation_monitor) {
         // Use a mutex because it's possible for the conversation monitor to fire multiple
         // "scan-started" signals as messages come in fast and furious, but only want to process
diff --git a/src/client/conversation-list/conversation-list-view.vala 
b/src/client/conversation-list/conversation-list-view.vala
index bdc1370..c72ec7e 100644
--- a/src/client/conversation-list/conversation-list-view.vala
+++ b/src/client/conversation-list/conversation-list-view.vala
@@ -14,7 +14,7 @@ public class ConversationListView : Gtk.TreeView {
     private double last_upper = -1.0;
     private bool reset_adjustment = false;
     private Gee.Set<Geary.App.Conversation> selected = new Gee.HashSet<Geary.App.Conversation>();
-    private ConversationListStore conversation_list_store;
+    private ConversationFilter conversation_filter;
     private Geary.App.ConversationMonitor? conversation_monitor;
     private Gee.Set<Geary.App.Conversation>? current_visible_conversations = null;
     private Geary.Scheduler.Scheduled? scheduled_update_visible_conversations = null;
@@ -34,9 +34,10 @@ public class ConversationListView : Gtk.TreeView {
     
     public signal void visible_conversations_changed(Gee.Set<Geary.App.Conversation> visible);
     
-    public ConversationListView(ConversationListStore conversation_list_store) {
-        this.conversation_list_store = conversation_list_store;
-        set_model(conversation_list_store);
+    public ConversationListView(ConversationFilter conversation_filter) {
+        
+        this.conversation_filter = conversation_filter;
+        set_model(conversation_filter);
         
         set_show_expanders(false);
         set_headers_visible(false);
@@ -58,8 +59,8 @@ public class ConversationListView : Gtk.TreeView {
         get_model().row_deleted.connect(on_rows_changed);
         get_model().row_deleted.connect(on_row_deleted);
         
-        conversation_list_store.conversations_added_began.connect(on_conversations_added_began);
-        conversation_list_store.conversations_added_finished.connect(on_conversations_added_finished);
+        conversation_filter.conversations_added_began.connect(on_conversations_added_began);
+        conversation_filter.conversations_added_finished.connect(on_conversations_added_finished);
         button_press_event.connect(on_button_press);
 
         // Set up drag and drop.
@@ -177,7 +178,7 @@ public class ConversationListView : Gtk.TreeView {
             
             // Get the current conversation.  If it's selected, we'll apply the mark operation to
             // all selected conversations; otherwise, it just applies to this one.
-            Geary.App.Conversation conversation = conversation_list_store.get_conversation_at_path(path);
+            Geary.App.Conversation conversation = conversation_filter.get_conversation_at_path(path);
             Gee.Collection<Geary.App.Conversation> to_mark;
             if (GearyApplication.instance.controller.get_selected_conversations().contains(conversation))
                 to_mark = GearyApplication.instance.controller.get_selected_conversations();
@@ -214,7 +215,7 @@ public class ConversationListView : Gtk.TreeView {
             return true;
         
         if (event.button == 3 && event.type == Gdk.EventType.BUTTON_PRESS) {
-            Geary.App.Conversation conversation = conversation_list_store.get_conversation_at_path(path);
+            Geary.App.Conversation conversation = conversation_filter.get_conversation_at_path(path);
             
             string?[] action_names = {};
             action_names += GearyController.ACTION_DELETE_MESSAGE;
@@ -328,7 +329,7 @@ public class ConversationListView : Gtk.TreeView {
         // Conversations are selected, so collect them and signal if different
         Gee.HashSet<Geary.App.Conversation> new_selected = new Gee.HashSet<Geary.App.Conversation>();
         foreach (Gtk.TreePath path in paths) {
-            Geary.App.Conversation? conversation = conversation_list_store.get_conversation_at_path(path);
+            Geary.App.Conversation? conversation = conversation_filter.get_conversation_at_path(path);
             if (conversation != null)
                 new_selected.add(conversation);
         }
@@ -349,7 +350,7 @@ public class ConversationListView : Gtk.TreeView {
             return visible_conversations;
         
         while (start_path.compare(end_path) <= 0) {
-            Geary.App.Conversation? conversation = 
conversation_list_store.get_conversation_at_path(start_path);
+            Geary.App.Conversation? conversation = conversation_filter.get_conversation_at_path(start_path);
             if (conversation != null)
                 visible_conversations.add(conversation);
             
@@ -363,7 +364,7 @@ public class ConversationListView : Gtk.TreeView {
         Gee.HashSet<Geary.App.Conversation> selected_conversations = new 
Gee.HashSet<Geary.App.Conversation>();
         
         foreach (Gtk.TreePath path in get_all_selected_paths()) {
-            Geary.App.Conversation? conversation = conversation_list_store.get_conversation_at_path(path);
+            Geary.App.Conversation? conversation = conversation_filter.get_conversation_at_path(path);
             if (path != null)
                 selected_conversations.add(conversation);
         }
@@ -400,7 +401,7 @@ public class ConversationListView : Gtk.TreeView {
     }
 
     public void select_conversation(Geary.App.Conversation conversation) {
-        Gtk.TreePath path = conversation_list_store.get_path_for_conversation(conversation);
+        Gtk.TreePath path = conversation_filter.get_path_for_conversation(conversation);
         if (path != null)
             set_cursor(path, null, false);
     }
@@ -408,7 +409,7 @@ public class ConversationListView : Gtk.TreeView {
     public void select_conversations(Gee.Set<Geary.App.Conversation> conversations) {
         Gtk.TreeSelection selection = get_selection();
         foreach (Geary.App.Conversation conversation in conversations) {
-            Gtk.TreePath path = conversation_list_store.get_path_for_conversation(conversation);
+            Gtk.TreePath path = conversation_filter.get_path_for_conversation(conversation);
             if (path != null)
                 selection.select_path(path);
         }
@@ -438,7 +439,7 @@ public class ConversationListView : Gtk.TreeView {
     }
     
     private void on_row_activated(Gtk.TreePath path) {
-        Geary.App.Conversation? c = conversation_list_store.get_conversation_at_path(path);
+        Geary.App.Conversation? c = conversation_filter.get_conversation_at_path(path);
         if (c != null)
             conversation_activated(c);
     }
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index cdf0f7b..fc39051 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -15,5 +15,6 @@ install(FILES password-dialog.glade DESTINATION ${UI_DEST})
 install(FILES preferences.glade DESTINATION ${UI_DEST})
 install(FILES remove_confirm.glade DESTINATION ${UI_DEST})
 install(FILES toolbar_mark_menu.ui DESTINATION ${UI_DEST})
+install(FILES toolbar_filter_menu.ui DESTINATION ${UI_DEST})
 install(FILES toolbar_menu.ui DESTINATION ${UI_DEST})
 install(FILES upgrade_dialog.glade DESTINATION ${UI_DEST})
diff --git a/ui/toolbar_filter_menu.ui b/ui/toolbar_filter_menu.ui
new file mode 100644
index 0000000..e7f50c6
--- /dev/null
+++ b/ui/toolbar_filter_menu.ui
@@ -0,0 +1,9 @@
+<ui>
+    <popup name="ToolbarFilterMenu">
+        <menuitem name="ShowAll" action="GearyFilterShowAll" />
+        <separator />
+        <menuitem name="ShowUnread" action="GearyFilterShowUnread" />
+        <menuitem name="ShowStarred" action="GearyFilterShowStarred" />
+    </popup>
+</ui>
+


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