[evolution/wip/webkit2] Bug 442398 - Option to reorder and hide calendar groups



commit 4e279eec1f294e8f68b6c39bd8756511c593f877
Author: Milan Crha <mcrha redhat com>
Date:   Tue Sep 22 17:42:02 2015 +0200

    Bug 442398 - Option to reorder and hide calendar groups

 .../evolution-util/evolution-util-sections.txt     |    3 +
 e-util/e-source-selector.c                         |  735 ++++++++++++++++++++
 e-util/e-source-selector.h                         |    9 +
 modules/addressbook/e-book-shell-sidebar.c         |    3 +
 modules/addressbook/e-book-shell-view-actions.c    |   26 +
 modules/calendar/e-cal-base-shell-sidebar.c        |    3 +
 modules/calendar/e-cal-shell-view-actions.c        |   26 +
 modules/calendar/e-memo-shell-view-actions.c       |   26 +
 modules/calendar/e-task-shell-view-actions.c       |   27 +
 po/POTFILES.in                                     |    1 +
 ui/evolution-calendars.ui                          |    3 +
 ui/evolution-contacts.ui                           |    3 +
 ui/evolution-memos.ui                              |    3 +
 ui/evolution-tasks.ui                              |    3 +
 14 files changed, 871 insertions(+), 0 deletions(-)
---
diff --git a/doc/reference/evolution-util/evolution-util-sections.txt 
b/doc/reference/evolution-util/evolution-util-sections.txt
index 9f063de..d73f89d 100644
--- a/doc/reference/evolution-util/evolution-util-sections.txt
+++ b/doc/reference/evolution-util/evolution-util-sections.txt
@@ -4283,6 +4283,9 @@ e_source_selector_ref_source_by_path
 e_source_selector_queue_write
 e_source_selector_update_row
 e_source_selector_update_all_rows
+e_source_selector_manage_groups
+e_source_selector_save_groups_setup
+e_source_selector_load_groups_setup
 <SUBSECTION Standard>
 E_SOURCE_SELECTOR
 E_IS_SOURCE_SELECTOR
diff --git a/e-util/e-source-selector.c b/e-util/e-source-selector.c
index cb53789..43a732a 100644
--- a/e-util/e-source-selector.c
+++ b/e-util/e-source-selector.c
@@ -23,6 +23,7 @@
 #endif
 
 #include <string.h>
+#include <glib/gi18n-lib.h>
 
 #include <libedataserverui/libedataserverui.h>
 
@@ -59,6 +60,9 @@ struct _ESourceSelectorPrivate {
        GtkCellRenderer *busy_renderer;
        guint n_busy_sources;
        gulong update_busy_renderer_id;
+
+       GHashTable *hidden_groups;
+       GSList *groups_order;
 };
 
 struct _AsyncContext {
@@ -361,6 +365,108 @@ source_selector_get_icon_name (ESourceSelector *selector,
 }
 
 static gboolean
+source_selector_source_is_enabled_and_selected (ESource *source,
+                                               const gchar *extension_name)
+{
+       gpointer extension;
+
+       g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+
+       if (!extension_name ||
+           !e_source_get_enabled (source))
+               return e_source_get_enabled (source);
+
+       if (!e_source_has_extension (source, extension_name))
+               return FALSE;
+
+       extension = e_source_get_extension (source, extension_name);
+       if (!E_IS_SOURCE_SELECTABLE (extension))
+               return TRUE;
+
+       return e_source_selectable_get_selected (extension);
+}
+
+typedef struct {
+       const gchar *extension_name;
+       gboolean any_selected;
+} LookupSelectedData;
+
+static gboolean
+source_selector_lookup_selected_cb (GNode *node,
+                                   gpointer user_data)
+{
+       LookupSelectedData *data = user_data;
+       ESource *source;
+
+       g_return_val_if_fail (data != NULL, TRUE);
+       g_return_val_if_fail (data->extension_name != NULL, TRUE);
+
+       source = node->data;
+       if (!E_IS_SOURCE (source))
+               return TRUE;
+
+       data->any_selected = source_selector_source_is_enabled_and_selected (source, data->extension_name);
+
+       return data->any_selected;
+}
+
+static gboolean
+source_selector_node_is_hidden (ESourceSelector *selector,
+                               GNode *main_node)
+{
+       GNode *node;
+       ESource *source;
+       const gchar *extension_name;
+       LookupSelectedData data;
+       gboolean hidden;
+
+       g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
+       g_return_val_if_fail (main_node != NULL, FALSE);
+
+       if (G_NODE_IS_ROOT (main_node))
+               return FALSE;
+
+       extension_name = e_source_selector_get_extension_name (selector);
+       hidden = FALSE;
+
+       /* Check the path to the root, any is hidden, this one can be also hidden */
+       node = main_node;
+       while (node) {
+               source = node->data;
+
+               if (!source || G_NODE_IS_ROOT (node))
+                       break;
+
+               if (source_selector_source_is_enabled_and_selected (source, extension_name)) {
+                       hidden = FALSE;
+                       break;
+               }
+
+               hidden = hidden || g_hash_table_contains (selector->priv->hidden_groups, e_source_get_uid 
(source));
+
+               node = node->parent;
+       }
+
+       if (!hidden)
+               return FALSE;
+
+       /* If any source in this subtree/group is enabled and selected,
+          then the group cannot be hidden */
+
+       node = main_node;
+       if (node->parent && !G_NODE_IS_ROOT (node->parent)) {
+               node = node->parent;
+       }
+
+       data.extension_name = extension_name;
+       data.any_selected = FALSE;
+
+       g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_ALL, -1, source_selector_lookup_selected_cb, &data);
+
+       return !data.any_selected;
+}
+
+static gboolean
 source_selector_traverse (GNode *node,
                           ESourceSelector *selector)
 {
@@ -375,6 +481,9 @@ source_selector_traverse (GNode *node,
        if (G_NODE_IS_ROOT (node))
                return FALSE;
 
+       if (source_selector_node_is_hidden (selector, node))
+               return FALSE;
+
        source_index = selector->priv->source_index;
 
        model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
@@ -523,6 +632,73 @@ source_selector_load_sources_status (ESourceSelector *selector,
 }
 
 static void
+source_selector_sort_groups (ESourceSelector *selector,
+                            GNode *root)
+{
+       GHashTable *groups; /* gchar *uid, GUINT index into node_sources */
+       GPtrArray *node_sources; /* GNode * as stored in the root first sub-level */
+       ESource *source;
+       GNode *node;
+       GSList *link;
+       guint ii;
+
+       g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+       g_return_if_fail (G_NODE_IS_ROOT (root));
+
+       if (!selector->priv->groups_order ||
+           !g_node_n_children (root))
+               return;
+
+       groups = g_hash_table_new (g_str_hash, g_str_equal);
+       node_sources = g_ptr_array_sized_new (g_node_n_children (root));
+
+       node = g_node_first_child (root);
+       while (node) {
+               GNode *next_node = g_node_next_sibling (node);
+
+               source = node->data;
+
+               if (source) {
+                       g_node_unlink (node);
+
+                       g_hash_table_insert (groups, (gpointer) e_source_get_uid (source), GUINT_TO_POINTER 
(node_sources->len));
+                       g_ptr_array_add (node_sources, node);
+               }
+
+               node = next_node;
+       }
+
+       /* First add known nodes as defined by the user... */
+       for (link = selector->priv->groups_order; link; link = g_slist_next (link)) {
+               const gchar *uid = link->data;
+
+               if (!uid || !g_hash_table_contains (groups, uid))
+                       continue;
+
+               ii = GPOINTER_TO_UINT (g_hash_table_lookup (groups, uid));
+               g_warn_if_fail (ii < node_sources->len);
+
+               node = node_sources->pdata[ii];
+               node_sources->pdata[ii] = NULL;
+
+               if (node)
+                       g_node_append (root, node);
+       }
+
+       /* ... then add all unknown (new) sources in the order
+          as they were in the passed-in tree */
+       for (ii = 0; ii < node_sources->len; ii++) {
+               node = node_sources->pdata[ii];
+
+               if (node)
+                       g_node_append (root, node);
+       }
+
+       g_ptr_array_unref (node_sources);
+       g_hash_table_destroy (groups);
+}
+
+static void
 source_selector_build_model (ESourceSelector *selector)
 {
        ESourceRegistry *registry;
@@ -569,6 +745,8 @@ source_selector_build_model (ESourceSelector *selector)
 
        root = e_source_registry_build_display_tree (registry, extension_name);
 
+       source_selector_sort_groups (selector, root);
+
        g_node_traverse (
                root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
                (GNodeTraverseFunc) source_selector_traverse,
@@ -1065,6 +1243,10 @@ source_selector_dispose (GObject *object)
 
        g_hash_table_remove_all (priv->source_index);
        g_hash_table_remove_all (priv->pending_writes);
+       g_hash_table_remove_all (priv->hidden_groups);
+
+       g_slist_free_full (priv->groups_order, g_free);
+       priv->groups_order = NULL;
 
        clear_saved_primary_selection (E_SOURCE_SELECTOR (object));
 
@@ -1081,6 +1263,7 @@ source_selector_finalize (GObject *object)
 
        g_hash_table_destroy (priv->source_index);
        g_hash_table_destroy (priv->pending_writes);
+       g_hash_table_destroy (priv->hidden_groups);
 
        g_free (priv->extension_name);
 
@@ -1685,6 +1868,7 @@ e_source_selector_init (ESourceSelector *selector)
        selector->priv = E_SOURCE_SELECTOR_GET_PRIVATE (selector);
 
        selector->priv->pending_writes = pending_writes;
+       selector->priv->hidden_groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 
        selector->priv->main_context = g_main_context_get_thread_default ();
        if (selector->priv->main_context != NULL)
@@ -2840,3 +3024,554 @@ e_source_selector_get_source_is_busy (ESourceSelector *selector,
 
        return is_busy;
 }
+
+static gboolean
+source_selector_get_source_hidden (ESourceSelector *selector,
+                                  ESource *source)
+{
+       g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
+       g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+       g_return_val_if_fail (e_source_get_uid (source) != NULL, FALSE);
+
+       return g_hash_table_contains (selector->priv->hidden_groups, e_source_get_uid (source));
+}
+
+static void
+tree_show_toggled (GtkCellRendererToggle *renderer,
+                  gchar *path_str,
+                  gpointer user_data)
+{
+       GtkWidget *table = user_data;
+       GtkTreeModel *model;
+       GtkTreePath *path;
+       GtkTreeIter iter;
+
+       path = gtk_tree_path_new_from_string (path_str);
+       model = gtk_tree_view_get_model (GTK_TREE_VIEW (table));
+
+       if (gtk_tree_model_get_iter (model, &iter, path)) {
+               gboolean shown = TRUE;
+
+               gtk_tree_model_get (model, &iter, 2, &shown, -1);
+               shown = !shown;
+               gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, shown, -1);
+
+               /* to have buttons synced with the change */
+               g_signal_emit_by_name (table, "cursor-changed");
+       }
+
+       gtk_tree_path_free (path);
+}
+
+static GtkWidget *
+create_tree (ESourceSelector *selector,
+            GtkWidget **tree)
+{
+       ESourceRegistry *registry;
+       GtkWidget *table, *scrolled;
+       GtkTreeSelection *selection;
+       GtkCellRenderer *renderer;
+       GtkListStore *model;
+       GNode *root;
+
+       scrolled = gtk_scrolled_window_new (NULL, NULL);
+       gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_IN);
+       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
+                                       GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+       model = gtk_list_store_new (3, G_TYPE_STRING, E_TYPE_SOURCE, G_TYPE_BOOLEAN);
+       table = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
+       gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (table), FALSE);
+
+       /* Cannot select/unselect sources, thus also cannot hide them */
+       if (e_source_selector_get_show_toggles (selector)) {
+               renderer = gtk_cell_renderer_toggle_new ();
+               g_object_set (G_OBJECT (renderer), "activatable", TRUE, NULL);
+               gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (table), -1,
+                                                            _("Show"), renderer,
+                                                            "active", 2, NULL);
+               g_signal_connect (renderer, "toggled", G_CALLBACK (tree_show_toggled), table);
+       }
+
+       renderer = gtk_cell_renderer_text_new ();
+       gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (table), -1,
+                                                    _("Group name"), renderer,
+                                                    "text", 0, NULL);
+
+       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (table));
+       gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+       gtk_container_add (GTK_CONTAINER (scrolled), table);
+
+       *tree = table;
+
+       registry = e_source_selector_get_registry (selector);
+       root = e_source_registry_build_display_tree (registry, e_source_selector_get_extension_name 
(selector));
+
+       source_selector_sort_groups (selector, root);
+
+       if (root) {
+               GNode *node;
+
+               for (node = g_node_first_child (root); node; node = g_node_next_sibling (node)) {
+                       GtkTreeIter iter;
+                       ESource *source;
+
+                       source = node->data;
+
+                       if (source) {
+                               gtk_list_store_append (model, &iter);
+                               gtk_list_store_set (model, &iter,
+                                               0, e_source_get_display_name (source),
+                                               1, source,
+                                               2, !source_selector_get_source_hidden (selector, source),
+                                               -1);
+                       }
+               }
+       }
+
+       e_source_registry_free_display_tree (root);
+
+       g_object_unref (model);
+
+       return scrolled;
+}
+
+static void
+process_move_button (GtkButton *button,
+                    GtkTreeView *tree,
+                    gboolean is_up,
+                    gboolean do_move)
+{
+       GtkTreeModel *model;
+       GtkTreeSelection *selection;
+       GtkTreeIter iter;
+       gboolean enable = FALSE;
+
+       g_return_if_fail (button != NULL);
+       g_return_if_fail (tree != NULL);
+
+       selection = gtk_tree_view_get_selection (tree);
+
+       if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+               gpointer ptr = NULL, ptr2;
+               GtkTreeIter iter2;
+               int i, cnt = gtk_tree_model_iter_n_children (model, NULL);
+               gboolean can_move = FALSE;
+
+               gtk_tree_model_get (model, &iter, 1, &ptr, -1);
+
+               for (i = 0; i < cnt; i++) {
+                       if (!gtk_tree_model_iter_nth_child (model, &iter2, NULL, i))
+                               break;
+
+                       ptr2 = NULL;
+                       gtk_tree_model_get (model, &iter2, 1, &ptr2, -1);
+
+                       if (ptr == ptr2 || (is_up && !do_move && i > 0)) {
+                               can_move = TRUE;
+                               break;
+                       }
+               }
+
+               if (can_move)
+                       can_move = ((is_up && i > 0) || (!is_up && i + 1 < cnt)) && i < cnt;
+
+               if (can_move && do_move) {
+                       i = i + (is_up ? -1 : 1);
+                       if (gtk_tree_model_iter_nth_child (model, &iter2, NULL, i)) {
+                               GtkTreePath *path;
+
+                               gtk_list_store_swap (GTK_LIST_STORE (model), &iter, &iter2);
+                               gtk_tree_selection_select_iter (selection, &iter);
+
+                               /* scroll to the selected cell */
+                               path = gtk_tree_model_get_path (model, &iter);
+                               gtk_tree_view_scroll_to_cell (tree, path, NULL, FALSE, 0.0, 0.0);
+                               gtk_tree_path_free (path);
+
+                               /* cursor has been moved to the other row */
+                               can_move = (is_up && i > 0) || (!is_up && i + 1 < cnt);
+
+                               g_signal_emit_by_name (tree, "cursor-changed");
+                       }
+               }
+
+               enable = can_move;
+       }
+
+       if (!do_move)
+               gtk_widget_set_sensitive (GTK_WIDGET (button), enable);
+}
+
+static void
+up_clicked (GtkButton *button,
+           GtkTreeView *tree)
+{
+       process_move_button (button, tree, TRUE, TRUE);
+}
+
+static void
+up_cursor_changed (GtkTreeView *tree,
+                  GtkButton *button)
+{
+       process_move_button (button, tree, TRUE, FALSE);
+}
+
+static void
+down_clicked (GtkButton *button,
+             GtkTreeView *tree)
+{
+       process_move_button (button, tree, FALSE, TRUE);
+}
+
+static void
+down_cursor_changed (GtkTreeView *tree,
+                    GtkButton *button)
+{
+       process_move_button (button, tree, FALSE, FALSE);
+}
+
+static void
+show_hide_cursor_changed (GtkTreeView *tree,
+                         GtkButton *button)
+{
+       GtkTreeModel *model;
+       GtkTreeSelection *selection;
+       GtkTreeIter iter;
+
+       g_return_if_fail (button != NULL);
+       g_return_if_fail (tree != NULL);
+
+       selection = gtk_tree_view_get_selection (tree);
+
+       if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+               gboolean shown = FALSE;
+
+               gtk_tree_model_get (model, &iter, 2, &shown, -1);
+
+               gtk_button_set_label (button, shown ? _("_Hide") : _("_Show"));
+       }
+}
+
+static void
+show_hide_clicked (GtkButton *button,
+                  GtkTreeView *tree)
+{
+       GtkTreeModel *model;
+       GtkTreeSelection *selection;
+       GtkTreeIter iter;
+
+       g_return_if_fail (button != NULL);
+       g_return_if_fail (tree != NULL);
+
+       selection = gtk_tree_view_get_selection (tree);
+
+       if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+               gboolean shown = TRUE;
+
+               gtk_tree_model_get (model, &iter, 2, &shown, -1);
+               shown = !shown;
+               gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, shown, -1);
+
+               show_hide_cursor_changed (tree, button);
+       }
+}
+
+/**
+ * e_source_selector_manage_groups:
+ * @selector: an #ESourceSelector
+ *
+ * Manages list of groups, like their order in the source selector,
+ * and a hidden property of the group.
+ *
+ * Returns: Whether user confirmed changes in the dialog.
+ *
+ * Since: 3.20
+ **/
+gboolean
+e_source_selector_manage_groups (ESourceSelector *selector)
+{
+       GtkWidget *dlg, *box, *pbox, *tree, *w, *w2;
+       gchar *txt;
+       gboolean confirmed = FALSE;
+
+       g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
+
+       w = gtk_widget_get_toplevel (GTK_WIDGET (selector));
+       if (!w || !gtk_widget_is_toplevel (w))
+               w = NULL;
+
+       dlg = gtk_dialog_new_with_buttons (_("Manage Groups"),
+                       w ? GTK_WINDOW (w) : NULL,
+                       GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+                       GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+                       GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+                       NULL);
+
+       w = gtk_dialog_get_content_area (GTK_DIALOG (dlg));
+       box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+       gtk_container_set_border_width (GTK_CONTAINER (box), 12);
+       gtk_box_pack_start (GTK_BOX (w), box, TRUE, TRUE, 0);
+
+       txt = g_strconcat ("<b>", _("Available Groups:"), "</b>", NULL);
+       w = gtk_label_new ("");
+       gtk_label_set_markup (GTK_LABEL (w), txt);
+       g_free (txt);
+       gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
+       gtk_box_pack_start (GTK_BOX (box), w, FALSE, FALSE, 2);
+
+       pbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+       gtk_box_pack_start (GTK_BOX (box), pbox, TRUE, TRUE, 2);
+
+       /* space on the left */
+       w = gtk_label_new ("");
+       gtk_box_pack_start (GTK_BOX (pbox), w, FALSE, FALSE, 6);
+
+       w = create_tree (selector, &tree);
+       gtk_widget_set_size_request (w, 200, 240);
+       gtk_box_pack_start (GTK_BOX (pbox), w, TRUE, TRUE, 2);
+
+       /* box of buttons */
+       w2 = gtk_button_box_new (GTK_ORIENTATION_VERTICAL);
+       gtk_button_box_set_layout (GTK_BUTTON_BOX (w2), GTK_BUTTONBOX_START);
+       gtk_box_pack_start (GTK_BOX (pbox), w2, FALSE, FALSE, 2);
+
+       #define add_button(_x,_y,_cb,_cb2) \
+               w = (_x) ? gtk_button_new_from_icon_name (_x, GTK_ICON_SIZE_BUTTON) : gtk_button_new (); \
+               gtk_button_set_label (GTK_BUTTON (w), _y); \
+               gtk_button_set_use_underline (GTK_BUTTON (w), TRUE); \
+               gtk_box_pack_start (GTK_BOX (w2), w, FALSE, FALSE, 2); \
+               g_signal_connect (w, "clicked", (GCallback)_cb, tree); \
+               g_signal_connect (tree, "cursor-changed", (GCallback)_cb2, w);
+
+       add_button ("go-up", _("_Up"), up_clicked, up_cursor_changed);
+       add_button ("go-down", _("_Down"), down_clicked, down_cursor_changed);
+
+       if (e_source_selector_get_show_toggles (selector)) {
+               add_button (NULL, _("_Show"), show_hide_clicked, show_hide_cursor_changed);
+               gtk_button_set_use_underline (GTK_BUTTON (w), TRUE);
+       }
+
+       #undef add_button
+
+       gtk_widget_show_all (box);
+
+       if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_ACCEPT) {
+               GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree));
+               GtkTreeIter iter;
+               gint ii, cnt = gtk_tree_model_iter_n_children (model, NULL);
+
+               g_hash_table_remove_all (selector->priv->hidden_groups);
+               g_slist_free_full (selector->priv->groups_order, g_free);
+               selector->priv->groups_order = NULL;
+
+               for (ii = 0; ii < cnt; ii++) {
+                       gpointer group = NULL;
+                       gboolean shown = TRUE;
+
+                       if (!gtk_tree_model_iter_nth_child (model, &iter, NULL, ii))
+                               break;
+
+                       gtk_tree_model_get (model, &iter, 1, &group, 2, &shown, -1);
+
+                       if (group) {
+                               const gchar *uid = e_source_get_uid (group);
+
+                               selector->priv->groups_order = g_slist_prepend (selector->priv->groups_order, 
g_strdup (uid));
+
+                               if (!shown)
+                                       g_hash_table_insert (selector->priv->hidden_groups, g_strdup (uid), 
GINT_TO_POINTER (1));
+                       }
+               }
+
+               selector->priv->groups_order = g_slist_reverse (selector->priv->groups_order);
+
+               source_selector_build_model (selector);
+
+               confirmed = TRUE;
+       }
+
+       gtk_widget_destroy (dlg);
+
+       return confirmed;
+}
+
+static gboolean
+source_selector_store_value (GKeyFile *key_file,
+                            const gchar *group_key,
+                            const gchar * const *value,
+                            gsize value_length)
+{
+       gchar **stored;
+       gsize length = 0, ii;
+       gboolean changed = FALSE;
+
+       g_return_val_if_fail (key_file != NULL, FALSE);
+       g_return_val_if_fail (group_key != NULL, FALSE);
+
+       stored = g_key_file_get_string_list (key_file, E_SOURCE_SELECTOR_GROUPS_SETUP_NAME, group_key, 
&length, NULL);
+       if (stored) {
+               changed = value_length != length;
+               if (!changed) {
+                       for (ii = 0; ii < length && !changed; ii++) {
+                               changed = g_strcmp0 (value[ii], stored[ii]) != 0;
+                       }
+               }
+
+               g_strfreev (stored);
+       } else {
+               changed = value != NULL;
+       }
+
+       if (changed) {
+               if (value)
+                       g_key_file_set_string_list (key_file, E_SOURCE_SELECTOR_GROUPS_SETUP_NAME, group_key, 
value, value_length);
+               else
+                       changed = g_key_file_remove_key (key_file, E_SOURCE_SELECTOR_GROUPS_SETUP_NAME, 
group_key, NULL);
+       }
+
+       return changed;
+}
+
+/**
+ * e_source_selector_save_groups_setup:
+ * @selector: an #ESourceSelector
+ * @key_file: a #GKeyFile to store the sgroups setup to
+ *
+ * Stores current setup of the groups in the @key_file.
+ *
+ * Use e_source_selector_load_groups_setup() to pass the settings
+ * back to the @selector.
+ *
+ * Returns: Whether the saved values are different, aka whether it's
+ *    required to store the changes.
+ *
+ * Since: 3.20
+ **/
+gboolean
+e_source_selector_save_groups_setup (ESourceSelector *selector,
+                                    GKeyFile *key_file)
+{
+       GPtrArray *value;
+       const gchar *extension_name;
+       gchar *group_key;
+       gboolean changed;
+
+       g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
+       g_return_val_if_fail (key_file != NULL, FALSE);
+
+       extension_name = e_source_selector_get_extension_name (selector);
+       g_return_val_if_fail (extension_name != NULL, FALSE);
+
+       group_key = g_strconcat (extension_name, "-hidden-groups", NULL);
+
+       if (g_hash_table_size (selector->priv->hidden_groups) > 0) {
+               GHashTableIter iter;
+               gpointer key, unused;
+
+               value = g_ptr_array_sized_new (g_hash_table_size (selector->priv->hidden_groups));
+
+               g_hash_table_iter_init (&iter, selector->priv->hidden_groups);
+               while (g_hash_table_iter_next (&iter, &key, &unused)) {
+                       if (key)
+                               g_ptr_array_add (value, key);
+               }
+
+               /* expects NULL-terminated array of strings, thus terminate it */
+               g_ptr_array_add (value, NULL);
+
+               changed = source_selector_store_value (key_file, group_key, (const gchar * const *) 
value->pdata, value->len - 1);
+
+               g_ptr_array_unref (value);
+       } else {
+               changed = source_selector_store_value (key_file, group_key, NULL, 0);
+       }
+
+       g_free (group_key);
+       group_key = g_strconcat (extension_name, "-groups-order", NULL);
+
+       if (selector->priv->groups_order) {
+               GSList *link;
+
+               value = g_ptr_array_sized_new (g_slist_length (selector->priv->groups_order));
+
+               for (link = selector->priv->groups_order; link; link = g_slist_next (link)) {
+                       if (link->data)
+                               g_ptr_array_add (value, link->data);
+               }
+
+               /* expects NULL-terminated array of strings, thus terminate it */
+               g_ptr_array_add (value, NULL);
+
+               changed = source_selector_store_value (key_file, group_key, (const gchar * const *) 
value->pdata, value->len - 1) || changed;
+
+               g_ptr_array_unref (value);
+       } else {
+               changed = source_selector_store_value (key_file, group_key, NULL, 0) || changed;
+       }
+
+       g_free (group_key);
+
+       return changed;
+}
+
+/**
+ * e_source_selector_load_groups_setup:
+ * @selector: an #ESourceSelector
+ * @key_file: a #GKeyFile to load the groups setup from
+ *
+ * Loads setup of the groups from the @key_file.
+ *
+ * Use e_source_selector_save_groups_setup() to store
+ * the settings of the @selector.
+ *
+ * Since: 3.20
+ **/
+void
+e_source_selector_load_groups_setup (ESourceSelector *selector,
+                                    GKeyFile *key_file)
+{
+       const gchar *extension_name;
+       gchar **stored;
+       gchar *group_key;
+       gsize ii;
+
+       g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+
+       extension_name = e_source_selector_get_extension_name (selector);
+       g_return_if_fail (extension_name != NULL);
+
+       g_hash_table_remove_all (selector->priv->hidden_groups);
+       g_slist_free_full (selector->priv->groups_order, g_free);
+       selector->priv->groups_order = NULL;
+
+       group_key = g_strconcat (extension_name, "-hidden-groups", NULL);
+
+       stored = g_key_file_get_string_list (key_file, E_SOURCE_SELECTOR_GROUPS_SETUP_NAME, group_key, NULL, 
NULL);
+       if (stored) {
+               for (ii = 0; stored[ii]; ii++) {
+                       g_hash_table_insert (selector->priv->hidden_groups, g_strdup (stored[ii]), 
GINT_TO_POINTER (1));
+               }
+
+               g_strfreev (stored);
+       }
+
+       g_free (group_key);
+       group_key = g_strconcat (extension_name, "-groups-order", NULL);
+
+       stored = g_key_file_get_string_list (key_file, E_SOURCE_SELECTOR_GROUPS_SETUP_NAME, group_key, NULL, 
NULL);
+       if (stored) {
+               for (ii = 0; stored[ii]; ii++) {
+                       selector->priv->groups_order = g_slist_prepend (selector->priv->groups_order, 
g_strdup (stored[ii]));
+               }
+
+               g_strfreev (stored);
+       }
+
+       g_free (group_key);
+
+       selector->priv->groups_order = g_slist_reverse (selector->priv->groups_order);
+
+       source_selector_build_model (selector);
+}
diff --git a/e-util/e-source-selector.h b/e-util/e-source-selector.h
index e186601..afccfa6 100644
--- a/e-util/e-source-selector.h
+++ b/e-util/e-source-selector.h
@@ -47,6 +47,8 @@
        (G_TYPE_INSTANCE_GET_CLASS \
        ((obj), E_TYPE_SOURCE_SELECTOR, ESourceSelectorClass))
 
+#define E_SOURCE_SELECTOR_GROUPS_SETUP_NAME "SourceSelector"
+
 G_BEGIN_DECLS
 
 typedef struct _ESourceSelector ESourceSelector;
@@ -157,6 +159,13 @@ void               e_source_selector_set_source_is_busy
 gboolean       e_source_selector_get_source_is_busy
                                                (ESourceSelector *selector,
                                                 ESource *source);
+gboolean       e_source_selector_manage_groups (ESourceSelector *selector);
+gboolean       e_source_selector_save_groups_setup
+                                               (ESourceSelector *selector,
+                                                GKeyFile *key_file);
+void           e_source_selector_load_groups_setup
+                                               (ESourceSelector *selector,
+                                                GKeyFile *key_file);
 
 G_END_DECLS
 
diff --git a/modules/addressbook/e-book-shell-sidebar.c b/modules/addressbook/e-book-shell-sidebar.c
index 7a40caf..97e31d0 100644
--- a/modules/addressbook/e-book-shell-sidebar.c
+++ b/modules/addressbook/e-book-shell-sidebar.c
@@ -167,6 +167,9 @@ book_shell_sidebar_constructed (GObject *object)
        priv->selector = g_object_ref (widget);
        gtk_widget_show (widget);
 
+       e_source_selector_load_groups_setup (E_SOURCE_SELECTOR (priv->selector),
+               e_shell_view_get_state_key_file (shell_view));
+
        settings = e_util_ref_settings ("org.gnome.evolution.addressbook");
 
        g_settings_bind_with_mapping (
diff --git a/modules/addressbook/e-book-shell-view-actions.c b/modules/addressbook/e-book-shell-view-actions.c
index a2ae672..06c9c5d 100644
--- a/modules/addressbook/e-book-shell-view-actions.c
+++ b/modules/addressbook/e-book-shell-view-actions.c
@@ -83,6 +83,21 @@ action_address_book_delete_cb (GtkAction *action,
 }
 
 static void
+action_address_book_manage_groups_cb (GtkAction *action,
+                                     EBookShellView *book_shell_view)
+{
+       EShellView *shell_view;
+       ESourceSelector *selector;
+
+       shell_view = E_SHELL_VIEW (book_shell_view);
+       selector = e_book_shell_sidebar_get_selector (book_shell_view->priv->book_shell_sidebar);
+
+       if (e_source_selector_manage_groups (selector) &&
+           e_source_selector_save_groups_setup (selector, e_shell_view_get_state_key_file (shell_view)))
+               e_shell_view_set_state_dirty (shell_view);
+}
+
+static void
 action_address_book_move_cb (GtkAction *action,
                              EBookShellView *book_shell_view)
 {
@@ -932,6 +947,13 @@ static GtkActionEntry contact_entries[] = {
          N_("Delete the selected address book"),
          G_CALLBACK (action_address_book_delete_cb) },
 
+       { "address-book-manage-groups",
+         NULL,
+         N_("_Manage Address Book groups..."),
+         NULL,
+         N_("Manage task list groups order and visibility"),
+         G_CALLBACK (action_address_book_manage_groups_cb) },
+
        { "address-book-move",
          "folder-move",
          N_("Mo_ve All Contacts To..."),
@@ -1067,6 +1089,10 @@ static EPopupActionEntry contact_popup_entries[] = {
          N_("_Delete"),
          "address-book-delete" },
 
+       { "address-book-popup-manage-groups",
+         N_("_Manage groups..."),
+         "address-book-manage-groups" },
+
        { "address-book-popup-properties",
          N_("_Properties"),
          "address-book-properties" },
diff --git a/modules/calendar/e-cal-base-shell-sidebar.c b/modules/calendar/e-cal-base-shell-sidebar.c
index 73c9d96..d3ce3e1 100644
--- a/modules/calendar/e-cal-base-shell-sidebar.c
+++ b/modules/calendar/e-cal-base-shell-sidebar.c
@@ -732,6 +732,9 @@ cal_base_shell_sidebar_constructed (GObject *object)
        cal_base_shell_sidebar->priv->selector = E_SOURCE_SELECTOR (widget);
        gtk_container_add (GTK_CONTAINER (container), widget);
 
+       e_source_selector_load_groups_setup (cal_base_shell_sidebar->priv->selector,
+               e_shell_view_get_state_key_file (shell_view));
+
        if (add_navigator) {
                ECalendarItem *calitem;
 
diff --git a/modules/calendar/e-cal-shell-view-actions.c b/modules/calendar/e-cal-shell-view-actions.c
index af7855f..acd0216 100644
--- a/modules/calendar/e-cal-shell-view-actions.c
+++ b/modules/calendar/e-cal-shell-view-actions.c
@@ -131,6 +131,21 @@ action_calendar_jump_to_cb (GtkAction *action,
 }
 
 static void
+action_calendar_manage_groups_cb (GtkAction *action,
+                                 ECalShellView *cal_shell_view)
+{
+       EShellView *shell_view;
+       ESourceSelector *selector;
+
+       shell_view = E_SHELL_VIEW (cal_shell_view);
+       selector = e_cal_base_shell_sidebar_get_selector (cal_shell_view->priv->cal_shell_sidebar);
+
+       if (e_source_selector_manage_groups (selector) &&
+           e_source_selector_save_groups_setup (selector, e_shell_view_get_state_key_file (shell_view)))
+               e_shell_view_set_state_dirty (shell_view);
+}
+
+static void
 action_calendar_new_cb (GtkAction *action,
                         ECalShellView *cal_shell_view)
 {
@@ -1228,6 +1243,13 @@ static GtkActionEntry calendar_entries[] = {
          N_("Select a specific date"),
          G_CALLBACK (action_calendar_jump_to_cb) },
 
+       { "calendar-manage-groups",
+         NULL,
+         N_("_Manage Calendar groups..."),
+         NULL,
+         N_("Manage Calendar groups order and visibility"),
+         G_CALLBACK (action_calendar_manage_groups_cb) },
+
        { "calendar-new",
          "x-office-calendar",
          N_("_New Calendar"),
@@ -1442,6 +1464,10 @@ static EPopupActionEntry calendar_popup_entries[] = {
          NULL,
          "calendar-jump-to" },
 
+       { "calendar-popup-manage-groups",
+         N_("_Manage groups..."),
+         "calendar-manage-groups" },
+
        { "calendar-popup-properties",
          NULL,
          "calendar-properties" },
diff --git a/modules/calendar/e-memo-shell-view-actions.c b/modules/calendar/e-memo-shell-view-actions.c
index 51e71f0..56e2353 100644
--- a/modules/calendar/e-memo-shell-view-actions.c
+++ b/modules/calendar/e-memo-shell-view-actions.c
@@ -133,6 +133,21 @@ action_memo_list_delete_cb (GtkAction *action,
 }
 
 static void
+action_memo_list_manage_groups_cb (GtkAction *action,
+                                  EMemoShellView *memo_shell_view)
+{
+       EShellView *shell_view;
+       ESourceSelector *selector;
+
+       shell_view = E_SHELL_VIEW (memo_shell_view);
+       selector = e_cal_base_shell_sidebar_get_selector (memo_shell_view->priv->memo_shell_sidebar);
+
+       if (e_source_selector_manage_groups (selector) &&
+           e_source_selector_save_groups_setup (selector, e_shell_view_get_state_key_file (shell_view)))
+               e_shell_view_set_state_dirty (shell_view);
+}
+
+static void
 action_memo_list_new_cb (GtkAction *action,
                          EMemoShellView *memo_shell_view)
 {
@@ -559,6 +574,13 @@ static GtkActionEntry memo_entries[] = {
          N_("Delete the selected memo list"),
          G_CALLBACK (action_memo_list_delete_cb) },
 
+       { "memo-list-manage-groups",
+         NULL,
+         N_("_Manage Memo List groups..."),
+         NULL,
+         N_("Manage Memo List groups order and visibility"),
+         G_CALLBACK (action_memo_list_manage_groups_cb) },
+
        { "memo-list-new",
          "stock_notes",
          N_("_New Memo List"),
@@ -635,6 +657,10 @@ static EPopupActionEntry memo_popup_entries[] = {
          N_("_Delete"),
          "memo-list-delete" },
 
+       { "memo-list-popup-manage-groups",
+         N_("_Manage groups..."),
+         "memo-list-manage-groups" },
+
        { "memo-list-popup-properties",
          NULL,
          "memo-list-properties" },
diff --git a/modules/calendar/e-task-shell-view-actions.c b/modules/calendar/e-task-shell-view-actions.c
index 9e58b75..46373f3 100644
--- a/modules/calendar/e-task-shell-view-actions.c
+++ b/modules/calendar/e-task-shell-view-actions.c
@@ -156,6 +156,22 @@ action_task_list_delete_cb (GtkAction *action,
 }
 
 static void
+action_task_list_manage_groups_cb (GtkAction *action,
+                                  ETaskShellView *task_shell_view)
+{
+       EShellView *shell_view;
+       ESourceSelector *selector;
+
+       shell_view = E_SHELL_VIEW (task_shell_view);
+       selector = e_cal_base_shell_sidebar_get_selector (task_shell_view->priv->task_shell_sidebar);
+
+       if (e_source_selector_manage_groups (selector) &&
+           e_source_selector_save_groups_setup (selector, e_shell_view_get_state_key_file (shell_view)))
+               e_shell_view_set_state_dirty (shell_view);
+}
+
+
+static void
 action_task_list_new_cb (GtkAction *action,
                          ETaskShellView *task_shell_view)
 {
@@ -684,6 +700,13 @@ static GtkActionEntry task_entries[] = {
          N_("Delete the selected task list"),
          G_CALLBACK (action_task_list_delete_cb) },
 
+       { "task-list-manage-groups",
+         NULL,
+         N_("_Manage Task List groups..."),
+         NULL,
+         N_("Manage task list groups order and visibility"),
+         G_CALLBACK (action_task_list_manage_groups_cb) },
+
        { "task-list-new",
          "stock_todo",
          N_("_New Task List"),
@@ -788,6 +811,10 @@ static EPopupActionEntry task_popup_entries[] = {
          N_("_Delete"),
          "task-list-delete" },
 
+       { "task-list-popup-manage-groups",
+         N_("_Manage groups..."),
+         "task-list-manage-groups" },
+
        { "task-list-popup-properties",
          NULL,
          "task-list-properties" },
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 01d845e..1e0eeb4 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -270,6 +270,7 @@ e-util/e-send-options.c
 [type: gettext/glade]e-util/e-send-options.ui
 e-util/e-source-config.c
 e-util/e-source-config-dialog.c
+e-util/e-source-selector.c
 e-util/e-source-selector-dialog.c
 e-util/e-spell-dictionary.c
 e-util/e-spell-entry.c
diff --git a/ui/evolution-calendars.ui b/ui/evolution-calendars.ui
index 4aba1ea..f9978d1 100644
--- a/ui/evolution-calendars.ui
+++ b/ui/evolution-calendars.ui
@@ -24,6 +24,7 @@
     <menu action='view-menu'>
       <menuitem action='calendar-go-today'/>
       <menuitem action='calendar-jump-to'/>
+      <menuitem action='calendar-manage-groups'/>
     </menu>
     <placeholder name='custom-menus'>
       <menu action='calendar-actions-menu'>
@@ -69,6 +70,8 @@
     <menuitem action='calendar-popup-select-one'/>
     <placeholder name='calendar-popup-actions'/>
     <separator/>
+    <menuitem action='calendar-popup-manage-groups'/>
+    <separator/>
     <menuitem action='calendar-popup-properties'/>
   </popup>
   <popup name='calendar-empty-popup'>
diff --git a/ui/evolution-contacts.ui b/ui/evolution-contacts.ui
index 36d640f..a153998 100644
--- a/ui/evolution-contacts.ui
+++ b/ui/evolution-contacts.ui
@@ -23,6 +23,7 @@
       </placeholder>
     </menu>
     <menu action='view-menu'>
+      <menuitem action='address-book-manage-groups'/>
       <placeholder name='view-custom-menus'>
         <menu action='contact-preview-menu'>
           <menuitem action='contact-preview'/>
@@ -63,8 +64,10 @@
     <menuitem action='address-book-popup-save-as'/>
     <separator/>
     <menuitem action='address-book-popup-delete'/>
+    <separator/>
     <placeholder name='address-book-popup-actions'/>
     <separator/>
+    <menuitem action='address-book-popup-manage-groups'/>
     <separator/>
     <menuitem action='address-book-popup-map'/>
     <separator/>
diff --git a/ui/evolution-memos.ui b/ui/evolution-memos.ui
index 81bad8a..2f34206 100644
--- a/ui/evolution-memos.ui
+++ b/ui/evolution-memos.ui
@@ -22,6 +22,7 @@
       </placeholder>
     </menu>
     <menu action='view-menu'>
+      <menuitem action='memo-list-manage-groups'/>
       <placeholder name='view-custom-menus'>
         <menu action='memo-preview-menu'>
           <menuitem action='memo-preview'/>
@@ -66,6 +67,8 @@
     <menuitem action='memo-list-popup-select-one'/>
     <placeholder name='memo-list-popup-actions'/>
     <separator/>
+    <menuitem action='memo-list-popup-manage-groups'/>
+    <separator/>
     <menuitem action='memo-list-popup-properties'/>
   </popup>
   <popup name='memo-search-options'>
diff --git a/ui/evolution-tasks.ui b/ui/evolution-tasks.ui
index 90617e9..2fc2d01 100644
--- a/ui/evolution-tasks.ui
+++ b/ui/evolution-tasks.ui
@@ -25,6 +25,7 @@
       </placeholder>
     </menu>
     <menu action='view-menu'>
+      <menuitem action='task-list-manage-groups'/>
       <placeholder name='view-custom-menus'>
         <menu action='task-preview-menu'>
           <menuitem action='task-preview'/>
@@ -79,6 +80,8 @@
     <menuitem action='task-list-popup-select-one'/>
     <placeholder name='task-list-popup-actions'/>
     <separator/>
+    <menuitem action='task-list-popup-manage-groups'/>
+    <separator/>
     <menuitem action='task-list-popup-properties'/>
   </popup>
   <popup name='task-search-options'>


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