aboutsummaryrefslogtreecommitdiffstats
path: root/libempathy-gtk/empathy-individual-view.c
diff options
context:
space:
mode:
Diffstat (limited to 'libempathy-gtk/empathy-individual-view.c')
-rw-r--r--libempathy-gtk/empathy-individual-view.c481
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);
+ }
+}