[evolution] Show ongoing progress of calendar views in UI



commit 98a1b231124db277c70d4389e5cf0fd917a0f381
Author: Milan Crha <mcrha redhat com>
Date:   Wed Oct 15 14:13:05 2014 +0200

    Show ongoing progress of calendar views in UI
    
    This was a remaining thing from the 'Make calendar views non-UI-blocking'
    work, to show progress of views in UI. This is currently done by a spinner
    beside source's name in the ESourceSelector and a tooltip above that row.

 calendar/gui/e-cal-data-model.c             |   95 +++++++-
 calendar/gui/e-cal-data-model.h             |   15 +
 e-util/e-source-selector.c                  |  399 ++++++++++++++++++++++++++-
 e-util/e-source-selector.h                  |   14 +
 e-util/test-source-selector.c               |   73 +++++-
 modules/calendar/e-cal-base-shell-content.c |   54 ++++-
 po/POTFILES.in                              |    1 +
 7 files changed, 647 insertions(+), 4 deletions(-)
---
diff --git a/calendar/gui/e-cal-data-model.c b/calendar/gui/e-cal-data-model.c
index f010540..740a2ce 100644
--- a/calendar/gui/e-cal-data-model.c
+++ b/calendar/gui/e-cal-data-model.c
@@ -59,6 +59,13 @@ enum {
        PROP_TIMEZONE
 };
 
+enum {
+       VIEW_STATE_CHANGED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
 G_DEFINE_TYPE (ECalDataModel, e_cal_data_model, G_TYPE_OBJECT)
 
 typedef struct _ComponentData {
@@ -299,6 +306,73 @@ subscriber_data_free (gpointer ptr)
        }
 }
 
+typedef struct _ViewStateChangedData {
+       ECalDataModel *data_model;
+       ECalClientView *view;
+       ECalDataModelViewState state;
+       guint percent;
+       gchar *message;
+       GError *error;
+} ViewStateChangedData;
+
+static void
+view_state_changed_data_free (gpointer ptr)
+{
+       ViewStateChangedData *vscd = ptr;
+
+       if (vscd) {
+               g_clear_object (&vscd->data_model);
+               g_clear_object (&vscd->view);
+               g_clear_error (&vscd->error);
+               g_free (vscd->message);
+               g_free (vscd);
+       }
+}
+
+static gboolean
+cal_data_model_emit_view_state_changed_timeout_cb (gpointer user_data)
+{
+       ViewStateChangedData *vscd = user_data;
+
+       g_return_val_if_fail (vscd != NULL, FALSE);
+       g_return_val_if_fail (E_IS_CAL_DATA_MODEL (vscd->data_model), FALSE);
+       g_return_val_if_fail (E_IS_CAL_CLIENT_VIEW (vscd->view), FALSE);
+
+       g_signal_emit (vscd->data_model, signals[VIEW_STATE_CHANGED], 0,
+               vscd->view, vscd->state, vscd->percent, vscd->message, vscd->error);
+
+       return FALSE;
+}
+
+static void
+cal_data_model_emit_view_state_changed (ECalDataModel *data_model,
+                                       ECalClientView *view,
+                                       ECalDataModelViewState state,
+                                       guint percent,
+                                       const gchar *message,
+                                       const GError *error)
+{
+       ViewStateChangedData *vscd;
+
+       g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
+       g_return_if_fail (E_IS_CAL_CLIENT_VIEW (view));
+
+       if (e_cal_data_model_get_disposing (data_model))
+               return;
+
+       vscd = g_new0 (ViewStateChangedData, 1);
+       vscd->data_model = g_object_ref (data_model);
+       vscd->view = g_object_ref (view);
+       vscd->state = state;
+       vscd->percent = percent;
+       vscd->message = g_strdup (message);
+       vscd->error = error ? g_error_copy (error) : NULL;
+
+       g_timeout_add_full (G_PRIORITY_DEFAULT, 1,
+               cal_data_model_emit_view_state_changed_timeout_cb,
+               vscd, view_state_changed_data_free);
+}
+
 typedef void (* InternalThreadJobFunc) (ECalDataModel *data_model, gpointer user_data);
 
 typedef struct _InternalThreadJobData {
@@ -1261,6 +1335,8 @@ cal_data_model_view_progress (ECalClientView *view,
                              ECalDataModel *data_model)
 {
        g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
+
+       cal_data_model_emit_view_state_changed (data_model, view, E_CAL_DATA_MODEL_VIEW_STATE_PROGRESS, 
percent, message, NULL);
 }
 
 static void
@@ -1297,6 +1373,8 @@ cal_data_model_view_complete (ECalClientView *view,
                view_data->lost_components = NULL;
        }
 
+       cal_data_model_emit_view_state_changed (data_model, view, E_CAL_DATA_MODEL_VIEW_STATE_COMPLETE, 0, 
NULL, error);
+
        view_data_unlock (view_data);
        view_data_unref (view_data);
 }
@@ -1387,8 +1465,10 @@ cal_data_model_create_view_thread (EAlertSinkThreadJobData *job_data,
 
        g_free (filter);
 
-       if (!g_cancellable_is_cancelled (cancellable))
+       if (!g_cancellable_is_cancelled (cancellable)) {
+               cal_data_model_emit_view_state_changed (data_model, view, E_CAL_DATA_MODEL_VIEW_STATE_START, 
0, NULL, NULL);
                e_cal_client_view_start (view, error);
+       }
 
        g_clear_object (&view);
 }
@@ -1442,6 +1522,7 @@ cal_data_model_update_client_view (ECalDataModel *data_model,
 
        if (view_data->view) {
                view_data_disconnect_view (view_data);
+               cal_data_model_emit_view_state_changed (data_model, view_data->view, 
E_CAL_DATA_MODEL_VIEW_STATE_STOP, 0, NULL, NULL);
                g_clear_object (&view_data->view);
        }
 
@@ -1556,6 +1637,9 @@ cal_data_model_remove_client_view (ECalDataModel *data_model,
 
                cal_data_model_thaw_all_subscribers (data_model);
 
+               if (view_data->view)
+                       cal_data_model_emit_view_state_changed (data_model, view_data->view, 
E_CAL_DATA_MODEL_VIEW_STATE_STOP, 0, NULL, NULL);
+
                view_data->is_used = FALSE;
                view_data_unlock (view_data);
 
@@ -1818,6 +1902,15 @@ e_cal_data_model_class_init (ECalDataModelClass *class)
                        "Time Zone",
                        NULL,
                        G_PARAM_READWRITE));
+
+       signals[VIEW_STATE_CHANGED] = g_signal_new (
+               "view-state-changed",
+               G_TYPE_FROM_CLASS (class),
+               G_SIGNAL_RUN_LAST,
+               G_STRUCT_OFFSET (ECalDataModelClass, view_state_changed),
+               NULL, NULL,
+               NULL,
+               G_TYPE_NONE, 5, E_TYPE_CAL_CLIENT_VIEW, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING, 
G_TYPE_ERROR);
 }
 
 static void
diff --git a/calendar/gui/e-cal-data-model.h b/calendar/gui/e-cal-data-model.h
index 8047644..c0696a3 100644
--- a/calendar/gui/e-cal-data-model.h
+++ b/calendar/gui/e-cal-data-model.h
@@ -45,6 +45,13 @@
 
 G_BEGIN_DECLS
 
+typedef enum {
+       E_CAL_DATA_MODEL_VIEW_STATE_START,
+       E_CAL_DATA_MODEL_VIEW_STATE_PROGRESS,
+       E_CAL_DATA_MODEL_VIEW_STATE_COMPLETE,
+       E_CAL_DATA_MODEL_VIEW_STATE_STOP
+} ECalDataModelViewState;
+
 typedef struct _ECalDataModel ECalDataModel;
 typedef struct _ECalDataModelClass ECalDataModelClass;
 typedef struct _ECalDataModelPrivate ECalDataModelPrivate;
@@ -56,6 +63,14 @@ struct _ECalDataModel {
 
 struct _ECalDataModelClass {
        GObjectClass parent_class;
+
+       /* Signals */
+       void (* view_state_changed)     (ECalDataModel *data_model,
+                                        ECalClientView *view,
+                                        ECalDataModelViewState state,
+                                        guint percent,
+                                        const gchar *message,
+                                        const GError *error);
 };
 
 typedef GCancellable * (* ECalDataModelSubmitThreadJobFunc)
diff --git a/e-util/e-source-selector.c b/e-util/e-source-selector.c
index 527b576..c9bdabd 100644
--- a/e-util/e-source-selector.c
+++ b/e-util/e-source-selector.c
@@ -54,6 +54,10 @@ struct _ESourceSelectorPrivate {
        gboolean show_colors;
        gboolean show_icons;
        gboolean show_toggles;
+
+       GtkCellRenderer *busy_renderer;
+       guint n_busy_sources;
+       gulong update_busy_renderer_id;
 };
 
 struct _AsyncContext {
@@ -91,6 +95,8 @@ enum {
        COLUMN_SHOW_TOGGLE,
        COLUMN_WEIGHT,
        COLUMN_SOURCE,
+       COLUMN_TOOLTIP,
+       COLUMN_IS_BUSY,
        NUM_COLUMNS
 };
 
@@ -163,6 +169,80 @@ e_cell_renderer_safe_toggle_new (void)
        return g_object_new (e_cell_renderer_safe_toggle_get_type (), NULL);
 }
 
+static gboolean
+source_selector_pulse_busy_renderer_cb (gpointer user_data)
+{
+       ESourceSelector *selector = user_data;
+       GObject *busy_renderer;
+       guint pulse;
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
+
+       if (!selector->priv->busy_renderer)
+               return FALSE;
+
+       busy_renderer = G_OBJECT (selector->priv->busy_renderer);
+
+       g_object_get (busy_renderer, "pulse", &pulse, NULL);
+
+       pulse++;
+
+       g_object_set (busy_renderer, "pulse", pulse, NULL);
+
+       g_hash_table_iter_init (&iter, selector->priv->source_index);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               GtkTreeRowReference *reference = value;
+               GtkTreeModel *model;
+               GtkTreePath *path;
+               GtkTreeIter tree_iter;
+
+               if (reference && gtk_tree_row_reference_valid (reference)) {
+                       gboolean is_busy = FALSE;
+
+                       model = gtk_tree_row_reference_get_model (reference);
+                       path = gtk_tree_row_reference_get_path (reference);
+                       gtk_tree_model_get_iter (model, &tree_iter, path);
+
+                       gtk_tree_model_get (
+                               model, &tree_iter,
+                               COLUMN_IS_BUSY, &is_busy,
+                               -1);
+
+                       if (is_busy)
+                               gtk_tree_model_row_changed (model, path, &tree_iter);
+
+                       gtk_tree_path_free (path);
+               }
+       }
+
+       return TRUE;
+}
+
+static void
+source_selector_inc_busy_sources (ESourceSelector *selector)
+{
+       selector->priv->n_busy_sources++;
+
+       if (selector->priv->busy_renderer && !selector->priv->update_busy_renderer_id)
+               selector->priv->update_busy_renderer_id =
+                       e_named_timeout_add (123, source_selector_pulse_busy_renderer_cb, selector);
+}
+
+static void
+source_selector_dec_busy_sources (ESourceSelector *selector)
+{
+       g_return_if_fail (selector->priv->n_busy_sources > 0);
+
+       selector->priv->n_busy_sources--;
+
+       if (selector->priv->n_busy_sources == 0 && selector->priv->update_busy_renderer_id) {
+               g_source_remove (selector->priv->update_busy_renderer_id);
+               selector->priv->update_busy_renderer_id = 0;
+       }
+}
+
 static void
 clear_saved_primary_selection (ESourceSelector *selector)
 {
@@ -340,6 +420,107 @@ source_selector_save_expanded (GtkTreeView *tree_view,
        g_queue_push_tail (queue, source);
 }
 
+typedef struct _SavedStatus
+{
+       gboolean is_busy;
+       gchar *tooltip;
+} SavedStatusData;
+
+static void
+saved_status_data_free (gpointer ptr)
+{
+       SavedStatusData *data = ptr;
+
+       if (data) {
+               g_free (data->tooltip);
+               g_free (data);
+       }
+}
+
+static GHashTable *
+source_selector_save_sources_status (ESourceSelector *selector)
+{
+       GHashTable *status;
+       GHashTableIter iter;
+       gpointer key, value;
+
+       status = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, saved_status_data_free);
+
+       g_hash_table_iter_init (&iter, selector->priv->source_index);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               ESource *source = key;
+               GtkTreeRowReference *reference = value;
+               GtkTreeModel *model;
+               GtkTreePath *path;
+               GtkTreeIter tree_iter;
+
+               if (reference && gtk_tree_row_reference_valid (reference)) {
+                       SavedStatusData *data;
+
+                       model = gtk_tree_row_reference_get_model (reference);
+                       path = gtk_tree_row_reference_get_path (reference);
+                       gtk_tree_model_get_iter (model, &tree_iter, path);
+
+                       data = g_new0 (SavedStatusData, 1);
+
+                       gtk_tree_model_get (
+                               model, &tree_iter,
+                               COLUMN_IS_BUSY, &data->is_busy,
+                               COLUMN_TOOLTIP, &data->tooltip,
+                               -1);
+
+                       if (data->is_busy)
+                               source_selector_dec_busy_sources (selector);
+
+                       gtk_tree_path_free (path);
+
+                       g_hash_table_insert (status, g_strdup (e_source_get_uid (source)), data);
+               }
+       }
+
+       return status;
+}
+
+static void
+source_selector_load_sources_status (ESourceSelector *selector,
+                                    GHashTable *status)
+{
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_hash_table_iter_init (&iter, selector->priv->source_index);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               ESource *source = key;
+               GtkTreeRowReference *reference = value;
+               GtkTreeModel *model;
+               GtkTreePath *path;
+               GtkTreeIter tree_iter;
+
+               if (reference && gtk_tree_row_reference_valid (reference)) {
+                       SavedStatusData *data;
+
+                       model = gtk_tree_row_reference_get_model (reference);
+                       path = gtk_tree_row_reference_get_path (reference);
+                       gtk_tree_model_get_iter (model, &tree_iter, path);
+
+                       gtk_tree_path_free (path);
+
+                       data = g_hash_table_lookup (status, e_source_get_uid (source));
+                       if (!data)
+                               continue;
+
+                       gtk_tree_store_set (
+                               GTK_TREE_STORE (model), &tree_iter,
+                               COLUMN_IS_BUSY, data->is_busy,
+                               COLUMN_TOOLTIP, data->tooltip,
+                               -1);
+
+                       if (data->is_busy)
+                               source_selector_inc_busy_sources (selector);
+               }
+       }
+}
+
 static void
 source_selector_build_model (ESourceSelector *selector)
 {
@@ -349,6 +530,7 @@ source_selector_build_model (ESourceSelector *selector)
        GtkTreeView *tree_view;
        GtkTreeSelection *selection;
        GtkTreeModel *model;
+       GHashTable *saved_status;
        ESource *selected;
        const gchar *extension_name;
        GNode *root;
@@ -366,6 +548,7 @@ source_selector_build_model (ESourceSelector *selector)
        source_index = selector->priv->source_index;
        selected = e_source_selector_ref_primary_selection (selector);
        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
+       saved_status = source_selector_save_sources_status (selector);
 
        /* Signal is blocked to avoid "primary-selection-changed" signal
         * on model clear. */
@@ -434,6 +617,9 @@ source_selector_build_model (ESourceSelector *selector)
                e_source_selector_set_primary_selection (selector, selected);
                g_object_unref (selected);
        }
+
+       source_selector_load_sources_status (selector, saved_status);
+       g_hash_table_destroy (saved_status);
 }
 
 static void
@@ -518,6 +704,9 @@ source_selector_source_removed_cb (ESourceRegistry *registry,
        if (!e_source_has_extension (source, extension_name))
                return;
 
+       if (e_source_selector_get_source_is_busy (selector, source))
+               source_selector_dec_busy_sources (selector);
+
        if (e_source_selector_source_is_selected (selector, source))
                g_signal_emit (selector, signals[SOURCE_UNSELECTED], 0, source);
 
@@ -562,6 +751,9 @@ source_selector_source_disabled_cb (ESourceRegistry *registry,
        if (!e_source_has_extension (source, extension_name))
                return;
 
+       if (e_source_selector_get_source_is_busy (selector, source))
+               source_selector_dec_busy_sources (selector);
+
        if (e_source_selector_source_is_selected (selector, source))
                g_signal_emit (selector, signals[SOURCE_UNSELECTED], 0, source);
 
@@ -827,6 +1019,11 @@ source_selector_dispose (GObject *object)
 
        priv = E_SOURCE_SELECTOR_GET_PRIVATE (object);
 
+       if (priv->update_busy_renderer_id) {
+               g_source_remove (priv->update_busy_renderer_id);
+               priv->update_busy_renderer_id = 0;
+       }
+
        if (priv->source_added_handler_id > 0) {
                g_signal_handler_disconnect (
                        priv->registry,
@@ -863,6 +1060,7 @@ source_selector_dispose (GObject *object)
        }
 
        g_clear_object (&priv->registry);
+       g_clear_object (&priv->busy_renderer);
 
        g_hash_table_remove_all (priv->source_index);
        g_hash_table_remove_all (priv->pending_writes);
@@ -1516,7 +1714,9 @@ e_source_selector_init (ESourceSelector *selector)
                G_TYPE_BOOLEAN,         /* COLUMN_SHOW_ICON */
                G_TYPE_BOOLEAN,         /* COLUMN_SHOW_TOGGLE */
                G_TYPE_INT,             /* COLUMN_WEIGHT */
-               E_TYPE_SOURCE);         /* COLUMN_SOURCE */
+               E_TYPE_SOURCE,          /* COLUMN_SOURCE */
+               G_TYPE_STRING,          /* COLUMN_TOOLTIP */
+               G_TYPE_BOOLEAN);        /* COLUMN_IS_BUSY */
 
        gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (tree_store));
 
@@ -1568,6 +1768,15 @@ e_source_selector_init (ESourceSelector *selector)
                "weight", COLUMN_WEIGHT,
                NULL);
 
+       renderer = gtk_cell_renderer_spinner_new ();
+       selector->priv->busy_renderer = g_object_ref (renderer);
+       gtk_tree_view_column_pack_end (column, renderer, FALSE);
+       gtk_tree_view_column_set_attributes (
+               column, renderer,
+               "visible", COLUMN_IS_BUSY,
+               "active", COLUMN_IS_BUSY,
+               NULL);
+
        selection = gtk_tree_view_get_selection (tree_view);
        gtk_tree_selection_set_select_function (
                selection, (GtkTreeSelectionFunc)
@@ -1578,6 +1787,8 @@ e_source_selector_init (ESourceSelector *selector)
                G_OBJECT (selector), 0);
 
        gtk_tree_view_set_headers_visible (tree_view, FALSE);
+       gtk_tree_view_set_tooltip_column (tree_view, COLUMN_TOOLTIP);
+       gtk_widget_set_has_tooltip (GTK_WIDGET (tree_view), TRUE);
 }
 
 /**
@@ -2437,3 +2648,189 @@ e_source_selector_update_all_rows (ESourceSelector *selector)
        g_list_free_full (list, (GDestroyNotify) g_object_unref);
 }
 
+/**
+ * e_source_selector_set_source_tooltip:
+ * @selector: an #ESourceSelector
+ * @source: an #ESource for which to set the tooltip
+ *
+ * Updates tooltip for the given @source.
+ *
+ * Since: 3.14
+ **/
+void
+e_source_selector_set_source_tooltip (ESourceSelector *selector,
+                                     ESource *source,
+                                     const gchar *tooltip)
+{
+       GtkTreeRowReference *reference;
+       GtkTreeModel *model;
+       GtkTreePath *path;
+       GtkTreeIter iter;
+
+       g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+       g_return_if_fail (E_IS_SOURCE (source));
+
+       reference = g_hash_table_lookup (selector->priv->source_index, source);
+
+       /* If the ESource is not in our tree model then return silently. */
+       if (reference == NULL)
+               return;
+
+       /* If we do have a row reference, it should be valid. */
+       g_return_if_fail (gtk_tree_row_reference_valid (reference));
+
+       model = gtk_tree_row_reference_get_model (reference);
+       path = gtk_tree_row_reference_get_path (reference);
+       gtk_tree_model_get_iter (model, &iter, path);
+       gtk_tree_path_free (path);
+
+       gtk_tree_store_set (
+               GTK_TREE_STORE (model), &iter,
+               COLUMN_TOOLTIP, tooltip && *tooltip ? tooltip : NULL,
+               -1);
+}
+
+/**
+ * e_source_selector_dup_source_tooltip:
+ * @selector: an #ESourceSelector
+ * @source: an #ESource for which to read the tooltip
+ *
+ * Returns: Current tooltip for the given @source. Free the returned
+ *    string with g_free() when done with it.
+ *
+ * Since: 3.14
+ **/
+gchar *
+e_source_selector_dup_source_tooltip (ESourceSelector *selector,
+                                     ESource *source)
+{
+       GtkTreeRowReference *reference;
+       GtkTreeModel *model;
+       GtkTreePath *path;
+       GtkTreeIter iter;
+       gchar *tooltip = NULL;
+
+       g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
+       g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+       reference = g_hash_table_lookup (selector->priv->source_index, source);
+
+       /* If the ESource is not in our tree model then return silently. */
+       if (reference == NULL)
+               return NULL;
+
+       /* If we do have a row reference, it should be valid. */
+       g_return_val_if_fail (gtk_tree_row_reference_valid (reference), FALSE);
+
+       model = gtk_tree_row_reference_get_model (reference);
+       path = gtk_tree_row_reference_get_path (reference);
+       gtk_tree_model_get_iter (model, &iter, path);
+       gtk_tree_path_free (path);
+
+       gtk_tree_model_get (
+               model, &iter,
+               COLUMN_TOOLTIP, &tooltip,
+               -1);
+
+       return tooltip;
+}
+
+/**
+ * e_source_selector_set_source_is_busy:
+ * @selector: an #ESourceSelector
+ * @source: an #ESource for which to set the is-busy status
+ *
+ * Updates the is-busy flag status for the given @source.
+ *
+ * Since: 3.14
+ **/
+void
+e_source_selector_set_source_is_busy (ESourceSelector *selector,
+                                     ESource *source,
+                                     gboolean is_busy)
+{
+       GtkTreeRowReference *reference;
+       GtkTreeModel *model;
+       GtkTreePath *path;
+       GtkTreeIter iter;
+       gboolean old_is_busy = FALSE;
+
+       g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+       g_return_if_fail (E_IS_SOURCE (source));
+
+       reference = g_hash_table_lookup (selector->priv->source_index, source);
+
+       /* If the ESource is not in our tree model then return silently. */
+       if (reference == NULL)
+               return;
+
+       /* If we do have a row reference, it should be valid. */
+       g_return_if_fail (gtk_tree_row_reference_valid (reference));
+
+       model = gtk_tree_row_reference_get_model (reference);
+       path = gtk_tree_row_reference_get_path (reference);
+       gtk_tree_model_get_iter (model, &iter, path);
+       gtk_tree_path_free (path);
+
+       gtk_tree_model_get (
+               GTK_TREE_MODEL (model), &iter,
+               COLUMN_IS_BUSY, &old_is_busy,
+               -1);
+
+       if ((old_is_busy ? 1 : 0) == (is_busy ? 1 : 0))
+               return;
+
+       gtk_tree_store_set (
+               GTK_TREE_STORE (model), &iter,
+               COLUMN_IS_BUSY, is_busy,
+               -1);
+
+       if (is_busy)
+               source_selector_inc_busy_sources (selector);
+       else
+               source_selector_dec_busy_sources (selector);
+}
+
+/**
+ * e_source_selector_get_source_is_busy:
+ * @selector: an #ESourceSelector
+ * @source: an #ESource for which to read the is-busy status
+ *
+ * Returns: Current is-busy flag status for the given @source.
+ *
+ * Since: 3.14
+ **/
+gboolean
+e_source_selector_get_source_is_busy (ESourceSelector *selector,
+                                     ESource *source)
+{
+       GtkTreeRowReference *reference;
+       GtkTreeModel *model;
+       GtkTreePath *path;
+       GtkTreeIter iter;
+       gboolean is_busy = FALSE;
+
+       g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
+       g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+
+       reference = g_hash_table_lookup (selector->priv->source_index, source);
+
+       /* If the ESource is not in our tree model then return silently. */
+       if (reference == NULL)
+               return FALSE;
+
+       /* If we do have a row reference, it should be valid. */
+       g_return_val_if_fail (gtk_tree_row_reference_valid (reference), FALSE);
+
+       model = gtk_tree_row_reference_get_model (reference);
+       path = gtk_tree_row_reference_get_path (reference);
+       gtk_tree_model_get_iter (model, &iter, path);
+       gtk_tree_path_free (path);
+
+       gtk_tree_model_get (
+               model, &iter,
+               COLUMN_IS_BUSY, &is_busy,
+               -1);
+
+       return is_busy;
+}
diff --git a/e-util/e-source-selector.h b/e-util/e-source-selector.h
index f3e9e56..e186601 100644
--- a/e-util/e-source-selector.h
+++ b/e-util/e-source-selector.h
@@ -143,6 +143,20 @@ void               e_source_selector_update_row    (ESourceSelector *selector,
                                                 ESource *source);
 void           e_source_selector_update_all_rows
                                                (ESourceSelector *selector);
+void           e_source_selector_set_source_tooltip
+                                               (ESourceSelector *selector,
+                                                ESource *source,
+                                                const gchar *tooltip);
+gchar *                e_source_selector_dup_source_tooltip
+                                               (ESourceSelector *selector,
+                                                ESource *source);
+void           e_source_selector_set_source_is_busy
+                                               (ESourceSelector *selector,
+                                                ESource *source,
+                                                gboolean is_busy);
+gboolean       e_source_selector_get_source_is_busy
+                                               (ESourceSelector *selector,
+                                                ESource *source);
 
 G_END_DECLS
 
diff --git a/e-util/test-source-selector.c b/e-util/test-source-selector.c
index 87aac7b..6751d5a 100644
--- a/e-util/test-source-selector.c
+++ b/e-util/test-source-selector.c
@@ -21,6 +21,7 @@
 #define OPENED_KEY "sources-opened-key"
 #define SOURCE_TYPE_KEY "sources-source-type-key"
 #define EXTENSION_NAME_KEY "sources-extension-name-key"
+#define TOOLTIP_ENTRY_KEY "sources-tooltip-entry-key"
 
 static void
 dump_selection (ESourceSelector *selector,
@@ -99,6 +100,19 @@ disable_widget_if_opened_cb (ESourceSelector *selector,
 }
 
 static void
+enable_widget_if_any_selected (ESourceSelector *selector,
+                              GtkWidget *widget)
+{
+       ESource *source;
+       gboolean sensitive;
+
+       source = e_source_selector_ref_primary_selection (selector);
+       sensitive = (source != NULL);
+       gtk_widget_set_sensitive (widget, sensitive);
+       g_clear_object (&source);
+}
+
+static void
 open_selected_clicked_cb (GtkWidget *button,
                           ESourceSelector *selector)
 {
@@ -171,12 +185,44 @@ close_selected_clicked_cb (GtkWidget *button,
        g_object_unref (source);
 }
 
+static void
+flip_busy_clicked_cb (GtkWidget *button,
+                     ESourceSelector *selector)
+{
+       ESource *source;
+
+       source = e_source_selector_ref_primary_selection (selector);
+       if (source)
+               e_source_selector_set_source_is_busy (selector, source,
+                       !e_source_selector_get_source_is_busy (selector, source));
+
+       g_clear_object (&source);
+}
+
+static void
+set_tooltip_clicked_cb (GtkWidget *button,
+                       ESourceSelector *selector)
+{
+       ESource *source;
+       GtkEntry *entry;
+
+       entry = g_object_get_data (G_OBJECT (button), TOOLTIP_ENTRY_KEY);
+       g_return_if_fail (entry != NULL);
+
+       source = e_source_selector_ref_primary_selection (selector);
+       if (source)
+               e_source_selector_set_source_tooltip (selector, source,
+                       gtk_entry_get_text (entry));
+
+       g_clear_object (&source);
+}
+
 static GtkWidget *
 create_page (ESourceRegistry *registry,
              const gchar *extension_name,
              ECalClientSourceType source_type)
 {
-       GtkWidget *widget, *subwindow, *selector, *button_box;
+       GtkWidget *widget, *subwindow, *selector, *button_box, *entry;
        GtkGrid *grid;
        GHashTable *opened_sources;
 
@@ -235,6 +281,31 @@ create_page (ESourceRegistry *registry,
                selector, "primary-selection-changed",
                G_CALLBACK (enable_widget_if_opened_cb), widget);
 
+       widget = gtk_button_new_with_label ("Flip busy status");
+       gtk_widget_set_margin_top (widget, 10);
+       gtk_container_add (GTK_CONTAINER (button_box), widget);
+
+       g_signal_connect (
+               widget, "clicked",
+               G_CALLBACK (flip_busy_clicked_cb), selector);
+       g_signal_connect (
+               selector, "primary-selection-changed",
+               G_CALLBACK (enable_widget_if_any_selected), widget);
+
+       entry = gtk_entry_new ();
+       gtk_container_add (GTK_CONTAINER (button_box), entry);
+
+       widget = gtk_button_new_with_label ("Set Tooltip");
+       g_object_set_data (G_OBJECT (widget), TOOLTIP_ENTRY_KEY, entry);
+       gtk_container_add (GTK_CONTAINER (button_box), widget);
+
+       g_signal_connect (
+               widget, "clicked",
+               G_CALLBACK (set_tooltip_clicked_cb), selector);
+       g_signal_connect (
+               selector, "primary-selection-changed",
+               G_CALLBACK (enable_widget_if_any_selected), widget);
+
        widget = gtk_label_new ("");
        g_object_set (
                G_OBJECT (widget),
diff --git a/modules/calendar/e-cal-base-shell-content.c b/modules/calendar/e-cal-base-shell-content.c
index e1d0129..f826a7d 100644
--- a/modules/calendar/e-cal-base-shell-content.c
+++ b/modules/calendar/e-cal-base-shell-content.c
@@ -21,7 +21,7 @@
 #endif
 
 #include <string.h>
-#include <glib/gi18n.h>
+#include <glib/gi18n-lib.h>
 
 #include "e-cal-base-shell-sidebar.h"
 #include "e-cal-base-shell-view.h"
@@ -31,6 +31,7 @@ struct _ECalBaseShellContentPrivate {
        ECalDataModel *data_model;
        ECalModel *model;
        gulong object_created_id;
+       gulong view_state_changed_id;
 };
 
 enum {
@@ -103,6 +104,47 @@ cal_base_shell_content_object_created_cb (ECalBaseShellContent *cal_base_shell_c
 }
 
 static void
+cal_base_sahell_content_view_state_changed_cb (ECalDataModel *data_model,
+                                              ECalClientView *view,
+                                              ECalDataModelViewState state,
+                                              guint percent,
+                                              const gchar *message,
+                                              const GError *error,
+                                              ECalBaseShellContent *cal_base_shell_content)
+{
+       EShellView *shell_view;
+       EShellSidebar *shell_sidebar;
+       ESourceSelector *selector;
+       ESource *source;
+
+       shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (cal_base_shell_content));
+       g_return_if_fail (E_IS_SHELL_VIEW (shell_view));
+
+       shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+       g_return_if_fail (E_IS_SHELL_SIDEBAR (shell_sidebar));
+
+       selector = e_cal_base_shell_sidebar_get_selector (E_CAL_BASE_SHELL_SIDEBAR (shell_sidebar));
+       source = e_client_get_source (E_CLIENT (e_cal_client_view_get_client (view)));
+
+       if (state == E_CAL_DATA_MODEL_VIEW_STATE_START ||
+           state == E_CAL_DATA_MODEL_VIEW_STATE_PROGRESS) {
+               e_source_selector_set_source_is_busy (selector, source, TRUE);
+
+               if (message) {
+                       gchar *tooltip;
+
+                       /* Translators: This is a running activity whose percent complete is known. */
+                       tooltip = g_strdup_printf (_("%s (%d%% complete)"), message, percent);
+                       e_source_selector_set_source_tooltip (selector, source, tooltip);
+                       g_free (tooltip);
+               }
+       } else {
+               e_source_selector_set_source_is_busy (selector, source, FALSE);
+               e_source_selector_set_source_tooltip (selector, source, NULL);
+       }
+}
+
+static void
 cal_base_shell_content_view_created_cb (EShellWindow *shell_window,
                                        EShellView *shell_view,
                                        ECalBaseShellContent *cal_base_shell_content)
@@ -136,6 +178,10 @@ cal_base_shell_content_view_created_cb (EShellWindow *shell_window,
        g_signal_connect (selector, "notify::primary-selection",
                G_CALLBACK (cal_base_shell_content_primary_selection_changed_cb), cal_base_shell_content);
 
+       cal_base_shell_content->priv->view_state_changed_id = g_signal_connect (
+               cal_base_shell_content->priv->data_model, "view-state-changed",
+               G_CALLBACK (cal_base_sahell_content_view_state_changed_cb), cal_base_shell_content);
+
        klass = E_CAL_BASE_SHELL_CONTENT_GET_CLASS (cal_base_shell_content);
        g_return_if_fail (klass != NULL);
 
@@ -204,6 +250,12 @@ cal_base_shell_content_dispose (GObject *object)
 
        e_cal_data_model_set_disposing (cal_base_shell_content->priv->data_model, TRUE);
 
+       if (cal_base_shell_content->priv->view_state_changed_id != 0) {
+               g_signal_handler_disconnect (cal_base_shell_content->priv->data_model,
+                       cal_base_shell_content->priv->view_state_changed_id);
+               cal_base_shell_content->priv->view_state_changed_id = 0;
+       }
+
        if (cal_base_shell_content->priv->object_created_id != 0) {
                g_signal_handler_disconnect (cal_base_shell_content->priv->model,
                        cal_base_shell_content->priv->object_created_id);
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6939e10..195c269 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -419,6 +419,7 @@ modules/cal-config-weather/evolution-cal-config-weather.c
 modules/cal-config-webcal/evolution-cal-config-webcal.c
 modules/calendar/e-cal-attachment-handler.c
 modules/calendar/e-cal-base-shell-backend.c
+modules/calendar/e-cal-base-shell-content.c
 modules/calendar/e-cal-base-shell-sidebar.c
 modules/calendar/e-calendar-preferences.c
 [type: gettext/glade]modules/calendar/e-calendar-preferences.ui


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