diff options
Diffstat (limited to 'libempathy-gtk/empathy-individual-view.c')
-rw-r--r-- | libempathy-gtk/empathy-individual-view.c | 481 |
1 files changed, 301 insertions, 180 deletions
diff --git a/libempathy-gtk/empathy-individual-view.c b/libempathy-gtk/empathy-individual-view.c index 4e6db6c6d..9a7e5215e 100644 --- a/libempathy-gtk/empathy-individual-view.c +++ b/libempathy-gtk/empathy-individual-view.c @@ -32,10 +32,12 @@ #include <gdk/gdkkeysyms.h> #include <gtk/gtk.h> -#include <folks/folks.h> #include <telepathy-glib/account-manager.h> #include <telepathy-glib/util.h> +#include <folks/folks.h> +#include <folks/folks-telepathy.h> + #include <libempathy/empathy-call-factory.h> #include <libempathy/empathy-individual-manager.h> #include <libempathy/empathy-contact-groups.h> @@ -74,6 +76,10 @@ typedef struct GtkTreeModelFilter *filter; GtkWidget *search_widget; + + guint expand_groups_idle_handler; + /* owned string (group name) -> bool (whether to expand/contract) */ + GHashTable *expand_groups; } EmpathyIndividualViewPriv; typedef struct @@ -144,115 +150,6 @@ static guint signals[LAST_SIGNAL]; G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view, GTK_TYPE_TREE_VIEW); -static gboolean -individual_view_is_visible_individual (EmpathyIndividualView *self, - FolksIndividual *individual) -{ - EmpathyIndividualViewPriv *priv = GET_PRIV (self); - EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget); - const gchar *str; - GList *personas, *l; - - /* We're only giving the visibility wrt filtering here, not things like - * presence. */ - if (live == NULL || gtk_widget_get_visible (GTK_WIDGET (live)) == FALSE) - return TRUE; - - /* check alias name */ - str = folks_individual_get_alias (individual); - if (empathy_live_search_match (live, str)) - return TRUE; - - /* check contact id, remove the @server.com part */ - personas = folks_individual_get_personas (individual); - for (l = personas; l; l = l->next) - { - const gchar *p; - gchar *dup_str = NULL; - gboolean visible; - - str = folks_persona_get_uid (l->data); - p = strstr (str, "@"); - if (p != NULL) - str = dup_str = g_strndup (str, p - str); - - visible = empathy_live_search_match (live, str); - g_free (dup_str); - if (visible) - return TRUE; - } - - /* FIXME: Add more rules here, we could check phone numbers in - * contact's vCard for example. */ - - return FALSE; -} - -static gboolean -individual_view_filter_visible_func (GtkTreeModel *model, - GtkTreeIter *iter, - gpointer user_data) -{ - EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data); - EmpathyIndividualViewPriv *priv = GET_PRIV (self); - FolksIndividual *individual = NULL; - gboolean is_group, is_separator, valid; - GtkTreeIter child_iter; - gboolean visible, is_online; - gboolean is_searching = TRUE; - - if (priv->search_widget == NULL || - !gtk_widget_get_visible (priv->search_widget)) - is_searching = FALSE; - - gtk_tree_model_get (model, iter, - EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, - EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator, - EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online, - EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, - -1); - - if (individual != NULL) - { - visible = individual_view_is_visible_individual (self, individual); - g_object_unref (individual); - - if (is_searching) - return visible; - else - return (priv->show_offline || is_online); - } - - if (is_separator) - return TRUE; - - /* Not a contact, not a separator, must be a group */ - g_return_val_if_fail (is_group, FALSE); - - /* only show groups which are not empty */ - for (valid = gtk_tree_model_iter_children (model, &child_iter, iter); - valid; valid = gtk_tree_model_iter_next (model, &child_iter)) - { - gtk_tree_model_get (model, &child_iter, - EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, - EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online, - -1); - - if (individual == NULL) - continue; - - visible = individual_view_is_visible_individual (self, individual); - g_object_unref (individual); - - /* show group if it has at least one visible contact in it */ - if ((is_searching && visible) || - (!is_searching && (priv->show_offline || is_online))) - return TRUE; - } - - return FALSE; -} - static void individual_view_tooltip_destroy_cb (GtkWidget *widget, EmpathyIndividualView *view) @@ -281,7 +178,6 @@ individual_view_query_tooltip_cb (EmpathyIndividualView *view, GtkTreePath *path; static gint running = 0; gboolean ret = FALSE; - EmpathyContact *contact; priv = GET_PRIV (view); @@ -308,17 +204,11 @@ individual_view_query_tooltip_cb (EmpathyIndividualView *view, if (individual == NULL) goto OUT; - contact = empathy_contact_dup_from_folks_individual (individual); - g_object_unref (individual); - - if (contact == NULL) - goto OUT; - if (priv->tooltip_widget == NULL) { - priv->tooltip_widget = empathy_contact_widget_new (contact, - EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP | - EMPATHY_CONTACT_WIDGET_SHOW_LOCATION); + priv->tooltip_widget = empathy_individual_widget_new (individual, + EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP | + EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION); gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8); g_object_ref (priv->tooltip_widget); g_signal_connect (priv->tooltip_widget, "destroy", @@ -326,12 +216,15 @@ individual_view_query_tooltip_cb (EmpathyIndividualView *view, gtk_widget_show (priv->tooltip_widget); } else - empathy_contact_widget_set_contact (priv->tooltip_widget, contact); + { + empathy_individual_widget_set_individual ( + EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual); + } gtk_tooltip_set_custom (tooltip, priv->tooltip_widget); ret = TRUE; - g_object_unref (contact); + g_object_unref (individual); OUT: running--; @@ -960,12 +853,12 @@ individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell, shell = GTK_MENU_SHELL (menu); /* audio */ - item = empathy_individual_audio_call_menu_item_new (individual); + item = empathy_individual_audio_call_menu_item_new (individual, NULL); gtk_menu_shell_append (shell, item); gtk_widget_show (item); /* video */ - item = empathy_individual_video_call_menu_item_new (individual); + item = empathy_individual_video_call_menu_item_new (individual, NULL); gtk_menu_shell_append (shell, item); gtk_widget_show (item); @@ -1407,41 +1300,76 @@ individual_view_search_show_cb (EmpathyLiveSearch *search, individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE)); } -typedef struct { - EmpathyIndividualView *view; - GtkTreeRowReference *row_ref; - gboolean expand; -} ExpandData; - static gboolean -individual_view_expand_idle_cb (gpointer user_data) +expand_idle_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + EmpathyIndividualView *self) { - ExpandData *data = user_data; - GtkTreePath *path; + EmpathyIndividualViewPriv *priv; + gboolean is_group; + gpointer should_expand; + gchar *name; - path = gtk_tree_row_reference_get_path (data->row_ref); - if (path == NULL) - goto done; + /* We only want groups */ + if (gtk_tree_path_get_depth (path) > 1) + return FALSE; - g_signal_handlers_block_by_func (data->view, - individual_view_row_expand_or_collapse_cb, - GINT_TO_POINTER (data->expand)); + gtk_tree_model_get (model, iter, + EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, + EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, + -1); - if (data->expand) - gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE); - else - gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path); + if (is_group == FALSE) + { + g_free (name); + return FALSE; + } - gtk_tree_path_free (path); + priv = GET_PRIV (self); + + if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL, + &should_expand) == TRUE) + { + if (GPOINTER_TO_INT (should_expand) == TRUE) + gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE); + else + gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path); + + g_hash_table_remove (priv->expand_groups, name); + } + + g_free (name); + + return FALSE; +} + +static gboolean +individual_view_expand_idle_cb (EmpathyIndividualView *self) +{ + EmpathyIndividualViewPriv *priv = GET_PRIV (self); + + DEBUG ("individual_view_expand_idle_cb"); + + g_signal_handlers_block_by_func (self, + individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE)); + g_signal_handlers_block_by_func (self, + individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE)); + + /* The store/filter could've been removed while we were in the idle queue */ + if (priv->filter != NULL) + { + gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter), + (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self); + } - g_signal_handlers_unblock_by_func (data->view, - individual_view_row_expand_or_collapse_cb, - GINT_TO_POINTER (data->expand)); + g_signal_handlers_unblock_by_func (self, + individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE)); + g_signal_handlers_unblock_by_func (self, + individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE)); -done: - g_object_unref (data->view); - gtk_tree_row_reference_free (data->row_ref); - g_slice_free (ExpandData, data); + g_object_unref (self); + priv->expand_groups_idle_handler = 0; return FALSE; } @@ -1453,9 +1381,9 @@ individual_view_row_has_child_toggled_cb (GtkTreeModel *model, EmpathyIndividualView *view) { EmpathyIndividualViewPriv *priv = GET_PRIV (view); - gboolean is_group = FALSE; + gboolean should_expand, is_group = FALSE; gchar *name = NULL; - ExpandData *data; + gpointer will_expand; gtk_tree_model_get (model, iter, EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, @@ -1468,23 +1396,35 @@ individual_view_row_has_child_toggled_cb (GtkTreeModel *model, return; } - data = g_slice_new0 (ExpandData); - data->view = g_object_ref (view); - data->row_ref = gtk_tree_row_reference_new (model, path); - data->expand = - (priv->view_features & + should_expand = (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 || (priv->search_widget != NULL && gtk_widget_get_visible (priv->search_widget)) || empathy_contact_group_get_expanded (name); /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within - * gtk_tree_model_filter_refilter () */ - g_idle_add (individual_view_expand_idle_cb, data); + * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to + * a hash table, and expand or contract them as appropriate all at once in + * an idle handler which iterates over all the group rows. */ + if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL, + &will_expand) == FALSE && + GPOINTER_TO_INT (will_expand) != should_expand) + { + g_hash_table_insert (priv->expand_groups, g_strdup (name), + GINT_TO_POINTER (should_expand)); + + if (priv->expand_groups_idle_handler == 0) + { + priv->expand_groups_idle_handler = + g_idle_add ((GSourceFunc) individual_view_expand_idle_cb, + g_object_ref (view)); + } + } g_free (name); } +/* FIXME: This is a workaround for bgo#621076 */ static void individual_view_verify_group_visibility (EmpathyIndividualView *view, GtkTreePath *path) @@ -1532,32 +1472,136 @@ individual_view_store_row_deleted_cb (GtkTreeModel *model, individual_view_verify_group_visibility (view, path); } +static gboolean +individual_view_is_visible_individual (EmpathyIndividualView *self, + FolksIndividual *individual) +{ + EmpathyIndividualViewPriv *priv = GET_PRIV (self); + EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget); + const gchar *str; + GList *personas, *l; + + /* We're only giving the visibility wrt filtering here, not things like + * presence. */ + if (live == NULL || gtk_widget_get_visible (GTK_WIDGET (live)) == FALSE) + return TRUE; + + /* check alias name */ + str = folks_individual_get_alias (individual); + + if (empathy_live_search_match (live, str)) + return TRUE; + + /* check contact id, remove the @server.com part */ + personas = folks_individual_get_personas (individual); + for (l = personas; l; l = l->next) + { + const gchar *p; + gchar *dup_str = NULL; + gboolean visible; + + if (!TPF_IS_PERSONA (l->data)) + continue; + + str = folks_persona_get_display_id (l->data); + p = strstr (str, "@"); + if (p != NULL) + str = dup_str = g_strndup (str, p - str); + + visible = empathy_live_search_match (live, str); + g_free (dup_str); + if (visible) + return TRUE; + } + + /* FIXME: Add more rules here, we could check phone numbers in + * contact's vCard for example. */ + + return FALSE; +} + +static gboolean +individual_view_filter_visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data); + EmpathyIndividualViewPriv *priv = GET_PRIV (self); + FolksIndividual *individual = NULL; + gboolean is_group, is_separator, valid; + GtkTreeIter child_iter; + gboolean visible, is_online; + gboolean is_searching = TRUE; + + if (priv->search_widget == NULL || + !gtk_widget_get_visible (priv->search_widget)) + is_searching = FALSE; + + gtk_tree_model_get (model, iter, + EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, + EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator, + EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online, + EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, + -1); + + if (individual != NULL) + { + if (is_searching == TRUE) + visible = individual_view_is_visible_individual (self, individual); + else + visible = (priv->show_offline || is_online); + + g_object_unref (individual); + + /* FIXME: Work around bgo#626552/bgo#621076 */ + if (visible == TRUE) + { + GtkTreePath *path = gtk_tree_model_get_path (model, iter); + individual_view_verify_group_visibility (self, path); + gtk_tree_path_free (path); + } + + return visible; + } + + if (is_separator) + return TRUE; + + /* Not a contact, not a separator, must be a group */ + g_return_val_if_fail (is_group, FALSE); + + /* only show groups which are not empty */ + for (valid = gtk_tree_model_iter_children (model, &child_iter, iter); + valid; valid = gtk_tree_model_iter_next (model, &child_iter)) + { + gtk_tree_model_get (model, &child_iter, + EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, + EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online, + -1); + + if (individual == NULL) + continue; + + visible = individual_view_is_visible_individual (self, individual); + g_object_unref (individual); + + /* show group if it has at least one visible contact in it */ + if ((is_searching && visible) || + (!is_searching && (priv->show_offline || is_online))) + return TRUE; + } + + return FALSE; +} + static void individual_view_constructed (GObject *object) { EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object); - EmpathyIndividualViewPriv *priv = GET_PRIV (view); GtkCellRenderer *cell; GtkTreeViewColumn *col; guint i; - priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new ( - GTK_TREE_MODEL (priv->store), NULL)); - gtk_tree_model_filter_set_visible_func (priv->filter, - individual_view_filter_visible_func, view, NULL); - - g_signal_connect (priv->filter, "row-has-child-toggled", - G_CALLBACK (individual_view_row_has_child_toggled_cb), view); - gtk_tree_view_set_model (GTK_TREE_VIEW (view), - GTK_TREE_MODEL (priv->filter)); - - tp_g_signal_connect_object (priv->store, "row-changed", - G_CALLBACK (individual_view_store_row_changed_cb), view, 0); - tp_g_signal_connect_object (priv->store, "row-inserted", - G_CALLBACK (individual_view_store_row_changed_cb), view, 0); - tp_g_signal_connect_object (priv->store, "row-deleted", - G_CALLBACK (individual_view_store_row_deleted_cb), view, 0); - /* Setup view */ g_object_set (view, "headers-visible", FALSE, @@ -1734,6 +1778,16 @@ individual_view_dispose (GObject *object) } static void +individual_view_finalize (GObject *object) +{ + EmpathyIndividualViewPriv *priv = GET_PRIV (object); + + g_hash_table_destroy (priv->expand_groups); + + G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object); +} + +static void individual_view_get_property (GObject *object, guint param_id, GValue *value, @@ -1775,7 +1829,7 @@ individual_view_set_property (GObject *object, switch (param_id) { case PROP_STORE: - priv->store = g_value_dup_object (value); + empathy_individual_view_set_store (view, g_value_get_object (value)); break; case PROP_VIEW_FEATURES: individual_view_set_view_features (view, g_value_get_flags (value)); @@ -1802,6 +1856,7 @@ empathy_individual_view_class_init (EmpathyIndividualViewClass *klass) object_class->constructed = individual_view_constructed; object_class->dispose = individual_view_dispose; + object_class->finalize = individual_view_finalize; object_class->get_property = individual_view_get_property; object_class->set_property = individual_view_set_property; @@ -1832,7 +1887,7 @@ empathy_individual_view_class_init (EmpathyIndividualViewClass *klass) "The store of the view", "The store of the view", EMPATHY_TYPE_INDIVIDUAL_STORE, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_VIEW_FEATURES, g_param_spec_flags ("view-features", @@ -1867,6 +1922,9 @@ empathy_individual_view_init (EmpathyIndividualView *view) /* Get saved group states. */ empathy_contact_groups_get_all (); + priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, NULL); + gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view), empathy_individual_store_row_separator_func, NULL, NULL); @@ -2266,3 +2324,66 @@ empathy_individual_view_set_show_offline (EmpathyIndividualView *self, g_object_notify (G_OBJECT (self), "show-offline"); gtk_tree_model_filter_refilter (priv->filter); } + +EmpathyIndividualStore * +empathy_individual_view_get_store (EmpathyIndividualView *self) +{ + g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL); + + return GET_PRIV (self)->store; +} + +void +empathy_individual_view_set_store (EmpathyIndividualView *self, + EmpathyIndividualStore *store) +{ + EmpathyIndividualViewPriv *priv; + + g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self)); + g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store)); + + priv = GET_PRIV (self); + + /* Destroy the old filter and remove the old store */ + if (priv->store != NULL) + { + g_signal_handlers_disconnect_by_func (priv->store, + individual_view_store_row_changed_cb, self); + g_signal_handlers_disconnect_by_func (priv->store, + individual_view_store_row_deleted_cb, self); + + g_signal_handlers_disconnect_by_func (priv->filter, + individual_view_row_has_child_toggled_cb, self); + + gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL); + } + + tp_clear_object (&priv->filter); + tp_clear_object (&priv->store); + + /* Set the new store */ + priv->store = store; + + if (store != NULL) + { + g_object_ref (store); + + /* Create a new filter */ + priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new ( + GTK_TREE_MODEL (priv->store), NULL)); + gtk_tree_model_filter_set_visible_func (priv->filter, + individual_view_filter_visible_func, self, NULL); + + g_signal_connect (priv->filter, "row-has-child-toggled", + G_CALLBACK (individual_view_row_has_child_toggled_cb), self); + gtk_tree_view_set_model (GTK_TREE_VIEW (self), + GTK_TREE_MODEL (priv->filter)); + + tp_g_signal_connect_object (priv->store, "row-changed", + G_CALLBACK (individual_view_store_row_changed_cb), self, 0); + tp_g_signal_connect_object (priv->store, "row-inserted", + G_CALLBACK (individual_view_store_row_changed_cb), self, 0); + tp_g_signal_connect_object (priv->store, "row-deleted", + G_CALLBACK (individual_view_store_row_deleted_cb), self, 0); + } +} |