diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2013-11-21 01:07:43 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2013-11-21 01:33:30 +0800 |
commit | 9bb9e9d47844ff9102bd114c6636783a5d41d625 (patch) | |
tree | fb34c06d2ac781b40761c42081a62f1600a1667a | |
parent | c980a3dc64247126a8ce1a0e9bf4bc932b7304f6 (diff) | |
download | gsoc2013-evolution-9bb9e9d47844ff9102bd114c6636783a5d41d625.tar gsoc2013-evolution-9bb9e9d47844ff9102bd114c6636783a5d41d625.tar.gz gsoc2013-evolution-9bb9e9d47844ff9102bd114c6636783a5d41d625.tar.bz2 gsoc2013-evolution-9bb9e9d47844ff9102bd114c6636783a5d41d625.tar.lz gsoc2013-evolution-9bb9e9d47844ff9102bd114c6636783a5d41d625.tar.xz gsoc2013-evolution-9bb9e9d47844ff9102bd114c6636783a5d41d625.tar.zst gsoc2013-evolution-9bb9e9d47844ff9102bd114c6636783a5d41d625.zip |
EMFolderTree: Show connection status icons.
Each network service row in the folder tree now shows an icon which
follows the state of the service's connection status and remote host
reachability.
-rw-r--r-- | mail/em-folder-tree-model.c | 201 | ||||
-rw-r--r-- | mail/em-folder-tree-model.h | 7 | ||||
-rw-r--r-- | mail/em-folder-tree.c | 23 |
3 files changed, 228 insertions, 3 deletions
diff --git a/mail/em-folder-tree-model.c b/mail/em-folder-tree-model.c index 3f7c08796a..c8ab161769 100644 --- a/mail/em-folder-tree-model.c +++ b/mail/em-folder-tree-model.c @@ -46,6 +46,10 @@ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), EM_TYPE_FOLDER_TREE_MODEL, EMFolderTreeModelPrivate)) +/* See GtkCellRendererSpinner:pulse property. + * Animation cycles over 12 frames in 750 ms. */ +#define SPINNER_PULSE_INTERVAL (750 / 12) + typedef struct _StoreInfo StoreInfo; struct _EMFolderTreeModelPrivate { @@ -78,6 +82,15 @@ struct _StoreInfo { gulong folder_info_stale_handler_id; gulong folder_subscribed_handler_id; gulong folder_unsubscribed_handler_id; + gulong connection_status_handler_id; + gulong host_reachable_handler_id; + + /* For comparison with the current status. */ + CamelServiceConnectionStatus last_status; + + /* Spinner renderers have to be animated manually. */ + guint spinner_pulse_value; + guint spinner_pulse_timeout_id; }; enum { @@ -117,6 +130,10 @@ static void folder_tree_model_folder_unsubscribed_cb (CamelStore *store, CamelFolderInfo *fi, GWeakRef *model_weak_ref); +static void folder_tree_model_status_notify_cb + (CamelStore *store, + GParamSpec *pspec, + GWeakRef *model_weak_ref); static guint signals[LAST_SIGNAL]; @@ -126,6 +143,7 @@ static StoreInfo * store_info_new (EMFolderTreeModel *model, CamelStore *store) { + CamelService *service; StoreInfo *si; gulong handler_id; @@ -183,6 +201,25 @@ store_info_new (EMFolderTreeModel *model, si->folder_unsubscribed_handler_id = handler_id; } + if (CAMEL_IS_NETWORK_SERVICE (store)) { + handler_id = g_signal_connect_data ( + store, "notify::connection-status", + G_CALLBACK (folder_tree_model_status_notify_cb), + e_weak_ref_new (model), + (GClosureNotify) e_weak_ref_free, 0); + si->connection_status_handler_id = handler_id; + + handler_id = g_signal_connect_data ( + store, "notify::host-reachable", + G_CALLBACK (folder_tree_model_status_notify_cb), + e_weak_ref_new (model), + (GClosureNotify) e_weak_ref_free, 0); + si->host_reachable_handler_id = handler_id; + } + + service = CAMEL_SERVICE (store); + si->last_status = camel_service_get_connection_status (service); + return si; } @@ -234,6 +271,19 @@ store_info_unref (StoreInfo *si) si->store, si->folder_unsubscribed_handler_id); + if (si->connection_status_handler_id > 0) + g_signal_handler_disconnect ( + si->store, + si->connection_status_handler_id); + + if (si->host_reachable_handler_id > 0) + g_signal_handler_disconnect ( + si->store, + si->host_reachable_handler_id); + + if (si->spinner_pulse_timeout_id > 0) + g_source_remove (si->spinner_pulse_timeout_id); + g_object_unref (si->store); gtk_tree_row_reference_free (si->row); g_hash_table_destroy (si->full_hash); @@ -449,6 +499,36 @@ folder_tree_model_services_reordered (EMailAccountStore *account_store, folder_tree_model_sort, NULL, NULL); } +static gboolean +folder_tree_model_spinner_pulse_cb (gpointer user_data) +{ + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + StoreInfo *si; + + si = (StoreInfo *) user_data; + + if (!gtk_tree_row_reference_valid (si->row)) + return G_SOURCE_REMOVE; + + path = gtk_tree_row_reference_get_path (si->row); + model = gtk_tree_row_reference_get_model (si->row); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + gtk_tree_store_set ( + GTK_TREE_STORE (model), &iter, + COL_STATUS_SPINNER_PULSE, + si->spinner_pulse_value++, + -1); + + if (si->spinner_pulse_value == G_MAXUINT) + si->spinner_pulse_value = 0; + + return G_SOURCE_CONTINUE; +} + static void folder_tree_model_selection_finalized_cb (EMFolderTreeModel *model) { @@ -565,7 +645,10 @@ folder_tree_model_constructed (GObject *object) G_TYPE_BOOLEAN, /* has not-yet-loaded subfolders */ G_TYPE_UINT, /* last known unread count */ G_TYPE_BOOLEAN, /* folder is a draft folder */ - G_TYPE_UINT /* user's sortorder */ + G_TYPE_ICON, /* status GIcon */ + G_TYPE_BOOLEAN, /* status icon visible */ + G_TYPE_UINT, /* status spinner pulse */ + G_TYPE_BOOLEAN, /* status spinner visible */ }; gtk_tree_store_set_column_types ( @@ -1360,6 +1443,115 @@ exit: g_object_unref (model); } +static void +folder_tree_model_update_status_icon (StoreInfo *si) +{ + CamelService *service; + CamelServiceConnectionStatus status; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + GIcon *icon = NULL; + const gchar *icon_name; + gboolean was_connecting; + gboolean host_reachable; + + g_return_if_fail (si != NULL); + + if (!gtk_tree_row_reference_valid (si->row)) + return; + + service = CAMEL_SERVICE (si->store); + status = camel_service_get_connection_status (service); + was_connecting = (si->last_status == CAMEL_SERVICE_CONNECTING); + si->last_status = status; + + host_reachable = camel_network_service_get_host_reachable ( + CAMEL_NETWORK_SERVICE (service)); + + switch (status) { + case CAMEL_SERVICE_DISCONNECTED: + if (!host_reachable) + icon_name = "network-no-route-symbolic"; + else if (was_connecting) + icon_name = "network-error-symbolic"; + else + icon_name = "network-offline-symbolic"; + break; + + case CAMEL_SERVICE_CONNECTING: + icon_name = NULL; + break; + + case CAMEL_SERVICE_CONNECTED: + icon_name = "network-idle-symbolic"; + break; + + case CAMEL_SERVICE_DISCONNECTING: + icon_name = NULL; + break; + } + + if (icon_name == NULL && si->spinner_pulse_timeout_id == 0) { + si->spinner_pulse_timeout_id = g_timeout_add_full ( + G_PRIORITY_DEFAULT, + SPINNER_PULSE_INTERVAL, + folder_tree_model_spinner_pulse_cb, + store_info_ref (si), + (GDestroyNotify) store_info_unref); + } + + if (icon_name != NULL && si->spinner_pulse_timeout_id > 0) { + g_source_remove (si->spinner_pulse_timeout_id); + si->spinner_pulse_timeout_id = 0; + } + + path = gtk_tree_row_reference_get_path (si->row); + model = gtk_tree_row_reference_get_model (si->row); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + if (icon_name != NULL) { + /* Use fallbacks if symbolic icons are not available. */ + icon = g_themed_icon_new_with_default_fallbacks (icon_name); + } + + gtk_tree_store_set ( + GTK_TREE_STORE (model), &iter, + COL_STATUS_ICON, icon, + COL_STATUS_ICON_VISIBLE, (icon_name != NULL), + COL_STATUS_SPINNER_VISIBLE, (icon_name == NULL), + -1); + + g_clear_object (&icon); + +} + +static void +folder_tree_model_status_notify_cb (CamelStore *store, + GParamSpec *pspec, + GWeakRef *model_weak_ref) +{ + EMFolderTreeModel *model; + StoreInfo *si; + + /* Even though this is a GObject::notify signal, CamelService + * always emits it from its GMainContext on the "main" thread, + * so it's safe to modify the GtkTreeStore from here. */ + + model = g_weak_ref_get (model_weak_ref); + g_return_if_fail (model != NULL); + + si = folder_tree_model_store_index_lookup (model, store); + + if (si != NULL) { + folder_tree_model_update_status_icon (si); + store_info_unref (si); + } + + g_object_unref (model); +} + void em_folder_tree_model_add_store (EMFolderTreeModel *model, CamelStore *store) @@ -1427,8 +1619,6 @@ em_folder_tree_model_add_store (EMFolderTreeModel *model, folder_tree_model_store_index_insert (model, si); - store_info_unref (si); - /* Each store has folders, but we don't load them until * the user demands them. */ root = iter; @@ -1446,8 +1636,13 @@ em_folder_tree_model_add_store (EMFolderTreeModel *model, COL_BOOL_IS_DRAFT, FALSE, -1); + if (CAMEL_IS_NETWORK_SERVICE (store)) + folder_tree_model_update_status_icon (si); + g_signal_emit (model, signals[LOADED_ROW], 0, path, &root); gtk_tree_path_free (path); + + store_info_unref (si); } void diff --git a/mail/em-folder-tree-model.h b/mail/em-folder-tree-model.h index 85fe1e714f..5ec4a3bf29 100644 --- a/mail/em-folder-tree-model.h +++ b/mail/em-folder-tree-model.h @@ -69,6 +69,13 @@ enum { * been added to the tree */ COL_UINT_UNREAD_LAST_SEL, /* last known unread count */ COL_BOOL_IS_DRAFT, /* %TRUE for a draft folder */ + + /* Status icon/spinner, only for top-level store rows. */ + COL_STATUS_ICON, + COL_STATUS_ICON_VISIBLE, + COL_STATUS_SPINNER_PULSE, + COL_STATUS_SPINNER_VISIBLE, + NUM_COLUMNS }; diff --git a/mail/em-folder-tree.c b/mail/em-folder-tree.c index f7e7afd197..57f9ef61f5 100644 --- a/mail/em-folder-tree.c +++ b/mail/em-folder-tree.c @@ -1195,6 +1195,8 @@ folder_tree_constructed (GObject *object) priv->selection_changed_handler_id = handler_id; column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_sizing ( + column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); gtk_tree_view_append_column (tree_view, column); renderer = gtk_cell_renderer_pixbuf_new (); @@ -1221,6 +1223,27 @@ folder_tree_constructed (GObject *object) renderer, "edited", G_CALLBACK (folder_tree_cell_edited_cb), object); + column = gtk_tree_view_column_new (); + gtk_tree_view_append_column (tree_view, column); + + renderer = gtk_cell_renderer_pixbuf_new (); + g_object_set (renderer, "xalign", 1.0, NULL); + gtk_tree_view_column_pack_end (column, renderer, FALSE); + gtk_tree_view_column_add_attribute ( + column, renderer, "gicon", COL_STATUS_ICON); + gtk_tree_view_column_add_attribute ( + column, renderer, "visible", COL_STATUS_ICON_VISIBLE); + + renderer = gtk_cell_renderer_spinner_new (); + g_object_set (renderer, "xalign", 1.0, NULL); + gtk_tree_view_column_pack_end (column, renderer, FALSE); + gtk_tree_view_column_add_attribute ( + column, renderer, "active", COL_BOOL_IS_STORE); + gtk_tree_view_column_add_attribute ( + column, renderer, "pulse", COL_STATUS_SPINNER_PULSE); + gtk_tree_view_column_add_attribute ( + column, renderer, "visible", COL_STATUS_SPINNER_VISIBLE); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); gtk_tree_selection_set_select_function ( selection, (GtkTreeSelectionFunc) |