From 9acc3aabf75d159a0c7b6bb729ccfd9d8daff865 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Wed, 4 Aug 2010 16:49:11 +0100 Subject: [PATCH] Add EmpathyPersonaStore and EmpathyPersonaView Based on stripped-down versions of EmpathyContactListStore and EmpathyContactListView, these allow listing of all the Personas for a given Individual. --- libempathy-gtk/Makefile.am | 4 + libempathy-gtk/empathy-persona-store.c | 1168 ++++++++++++++++++++++++ libempathy-gtk/empathy-persona-store.h | 107 +++ libempathy-gtk/empathy-persona-view.c | 574 ++++++++++++ libempathy-gtk/empathy-persona-view.h | 74 ++ 5 files changed, 1927 insertions(+) create mode 100644 libempathy-gtk/empathy-persona-store.c create mode 100644 libempathy-gtk/empathy-persona-store.h create mode 100644 libempathy-gtk/empathy-persona-view.c create mode 100644 libempathy-gtk/empathy-persona-view.h diff --git a/libempathy-gtk/Makefile.am b/libempathy-gtk/Makefile.am index 56e2af3c..b45fc6e7 100644 --- a/libempathy-gtk/Makefile.am +++ b/libempathy-gtk/Makefile.am @@ -62,6 +62,8 @@ libempathy_gtk_handwritten_source = \ empathy-new-message-dialog.c \ empathy-new-call-dialog.c \ empathy-notify-manager.c \ + empathy-persona-store.c \ + empathy-persona-view.c \ empathy-presence-chooser.c \ empathy-protocol-chooser.c \ empathy-search-bar.c \ @@ -113,6 +115,8 @@ libempathy_gtk_headers = \ empathy-new-message-dialog.h \ empathy-new-call-dialog.h \ empathy-notify-manager.h \ + empathy-persona-store.h \ + empathy-persona-view.h \ empathy-presence-chooser.h \ empathy-protocol-chooser.h \ empathy-search-bar.h \ diff --git a/libempathy-gtk/empathy-persona-store.c b/libempathy-gtk/empathy-persona-store.c new file mode 100644 index 00000000..52a03d6e --- /dev/null +++ b/libempathy-gtk/empathy-persona-store.c @@ -0,0 +1,1168 @@ +/* + * Copyright (C) 2005-2007 Imendio AB + * Copyright (C) 2007-2008, 2010 Collabora Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authors: Mikael Hallendal + * Martyn Russell + * Xavier Claessens + * Philip Withnall + * + * Based off EmpathyContactListStore. + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +#include "empathy-persona-store.h" +#include "empathy-gtk-enum-types.h" +#include "empathy-ui-utils.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_CONTACT +#include + +/* Active users are those which have recently changed state + * (e.g. online, offline or from normal to a busy state). */ + +/* Time in seconds user is shown as active */ +#define ACTIVE_USER_SHOW_TIME 7 + +/* Time in seconds after connecting which we wait before active users are + * enabled */ +#define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5 + +static void add_persona (EmpathyPersonaStore *self, + FolksPersona *persona); +static GtkTreePath * find_persona (EmpathyPersonaStore *self, + FolksPersona *persona); +static void update_persona (EmpathyPersonaStore *self, + FolksPersona *persona); +static void remove_persona (EmpathyPersonaStore *self, + FolksPersona *persona); + +#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyPersonaStore) + +typedef struct +{ + FolksIndividual *individual; /* owned */ + GHashTable *personas; /* owned Persona -> owned GtkTreeRowReference */ + + gboolean show_avatars; + gboolean show_protocols; + gboolean show_active; + EmpathyPersonaStoreSort sort_criterion; + + guint inhibit_active; + guint setup_idle_id; + + GHashTable *status_icons; /* owned icon name -> owned GdkPixbuf */ +} EmpathyPersonaStorePriv; + +enum { + PROP_0, + PROP_INDIVIDUAL, + PROP_SHOW_AVATARS, + PROP_SHOW_PROTOCOLS, + PROP_SORT_CRITERION +}; + +G_DEFINE_TYPE (EmpathyPersonaStore, empathy_persona_store, GTK_TYPE_LIST_STORE); + +static gboolean +inhibit_active_cb (EmpathyPersonaStore *store) +{ + EmpathyPersonaStorePriv *priv; + + priv = GET_PRIV (store); + + priv->show_active = TRUE; + priv->inhibit_active = 0; + + return FALSE; +} + +typedef struct { + EmpathyPersonaStore *store; + FolksPersona *persona; + gboolean remove; + guint timeout; +} ShowActiveData; + +static void persona_active_free (ShowActiveData *data); + +static void +persona_active_invalidated (ShowActiveData *data, + GObject *old_object) +{ + /* Remove the timeout and free the struct, since the persona or persona + * store has disappeared. */ + g_source_remove (data->timeout); + + if (old_object == (GObject *) data->store) + data->store = NULL; + else if (old_object == (GObject *) data->persona) + data->persona = NULL; + else + g_assert_not_reached (); + + persona_active_free (data); +} + +static ShowActiveData * +persona_active_new (EmpathyPersonaStore *self, + FolksPersona *persona, + gboolean remove_) +{ + ShowActiveData *data; + + DEBUG ("Contact:'%s' now active, and %s be removed", + folks_alias_get_alias (FOLKS_ALIAS (persona)), + remove_ ? "WILL" : "WILL NOT"); + + data = g_slice_new0 (ShowActiveData); + + /* We don't actually want to force either the PersonaStore or the + * Persona to stay alive, since the user could quit Empathy or disable + * the account before the persona_active timeout is fired. */ + g_object_weak_ref (G_OBJECT (self), + (GWeakNotify) persona_active_invalidated, data); + g_object_weak_ref (G_OBJECT (persona), + (GWeakNotify) persona_active_invalidated, data); + + data->store = self; + data->persona = persona; + data->remove = remove_; + + return data; +} + +static void +persona_active_free (ShowActiveData *data) +{ + if (data->store != NULL) + { + g_object_weak_unref (G_OBJECT (data->store), + (GWeakNotify) persona_active_invalidated, data); + } + + if (data->persona != NULL) + { + g_object_weak_unref (G_OBJECT (data->persona), + (GWeakNotify) persona_active_invalidated, data); + } + + g_slice_free (ShowActiveData, data); +} + +static void +persona_set_active (EmpathyPersonaStore *self, + FolksPersona *persona, + gboolean active, + gboolean set_changed) +{ + EmpathyPersonaStorePriv *priv; + GtkTreePath *path; + GtkTreeIter iter; + + priv = GET_PRIV (self); + + path = find_persona (self, persona); + if (path == NULL) + return; + + gtk_tree_model_get_iter (GTK_TREE_MODEL (self), &iter, path); + gtk_list_store_set (GTK_LIST_STORE (self), &iter, + EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, active, + -1); + + DEBUG ("Set item %s", active ? "active" : "inactive"); + + if (set_changed) + gtk_tree_model_row_changed (GTK_TREE_MODEL (self), path, &iter); + + gtk_tree_path_free (path); +} + +static gboolean +persona_active_cb (ShowActiveData *data) +{ + const gchar *alias = folks_alias_get_alias (FOLKS_ALIAS (data->persona)); + + if (data->remove) + { + DEBUG ("Contact:'%s' active timeout, removing item", alias); + remove_persona (data->store, data->persona); + } + + DEBUG ("Contact:'%s' no longer active", alias); + persona_set_active (data->store, data->persona, FALSE, TRUE); + + persona_active_free (data); + + return FALSE; +} + +static void +persona_updated_cb (FolksPersona *persona, + GParamSpec *pspec, + EmpathyPersonaStore *self) +{ + DEBUG ("Contact:'%s' updated, checking roster is in sync...", + folks_alias_get_alias (FOLKS_ALIAS (persona))); + + update_persona (self, persona); +} + +static void +add_persona_and_connect (EmpathyPersonaStore *self, + FolksPersona *persona) +{ + /* We don't want any non-Telepathy personas */ + if (!TPF_IS_PERSONA (persona)) + return; + + g_signal_connect (persona, "notify::presence", + (GCallback) persona_updated_cb, self); + g_signal_connect (persona, "notify::presence-message", + (GCallback) persona_updated_cb, self); + g_signal_connect (persona, "notify::alias", + (GCallback) persona_updated_cb, self); + g_signal_connect (persona, "notify::avatar", + (GCallback) persona_updated_cb, self); + + add_persona (self, persona); +} + +static void +remove_persona_and_disconnect (EmpathyPersonaStore *self, + FolksPersona *persona) +{ + if (!TPF_IS_PERSONA (persona)) + return; + + g_signal_handlers_disconnect_by_func (persona, + (GCallback) persona_updated_cb, self); + + remove_persona (self, persona); +} + +static void +add_persona (EmpathyPersonaStore *self, + FolksPersona *persona) +{ + EmpathyPersonaStorePriv *priv; + GtkTreeIter iter; + GtkTreePath *path; + EmpathyContact *contact; + const gchar *alias; + + if (!TPF_IS_PERSONA (persona)) + return; + + priv = GET_PRIV (self); + + alias = folks_alias_get_alias (FOLKS_ALIAS (persona)); + if (EMP_STR_EMPTY (alias)) + return; + + contact = empathy_contact_new (tpf_persona_get_contact ( + TPF_PERSONA (persona))); + + gtk_list_store_insert_with_values (GTK_LIST_STORE (self), &iter, 0, + EMPATHY_PERSONA_STORE_COL_NAME, alias, + EMPATHY_PERSONA_STORE_COL_DISPLAY_ID, + folks_persona_get_display_id (persona), + EMPATHY_PERSONA_STORE_COL_PERSONA, persona, + EMPATHY_PERSONA_STORE_COL_CAN_AUDIO_CALL, + empathy_contact_get_capabilities (contact) & + EMPATHY_CAPABILITIES_AUDIO, + EMPATHY_PERSONA_STORE_COL_CAN_VIDEO_CALL, + empathy_contact_get_capabilities (contact) & + EMPATHY_CAPABILITIES_VIDEO, + -1); + + g_object_unref (contact); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (self), &iter); + g_hash_table_replace (priv->personas, g_object_ref (persona), + gtk_tree_row_reference_new (GTK_TREE_MODEL (self), path)); + gtk_tree_path_free (path); + + update_persona (self, persona); +} + +static void +remove_persona (EmpathyPersonaStore *self, + FolksPersona *persona) +{ + EmpathyPersonaStorePriv *priv; + GtkTreePath *path; + GtkTreeIter iter; + + if (!TPF_IS_PERSONA (persona)) + return; + + priv = GET_PRIV (self); + + path = find_persona (self, persona); + if (path == NULL) + return; + + g_hash_table_remove (priv->personas, persona); + + gtk_tree_model_get_iter (GTK_TREE_MODEL (self), &iter, path); + gtk_list_store_remove (GTK_LIST_STORE (self), &iter); + gtk_tree_path_free (path); +} + +static GdkPixbuf * +get_persona_status_icon (EmpathyPersonaStore *self, + FolksPersona *persona) +{ + EmpathyPersonaStorePriv *priv = GET_PRIV (self); + EmpathyContact *contact; + const gchar *protocol_name = NULL; + gchar *icon_name = NULL; + GdkPixbuf *pixbuf_status = NULL; + const gchar *status_icon_name = NULL; + + contact = empathy_contact_new (tpf_persona_get_contact ( + TPF_PERSONA (persona))); + + status_icon_name = empathy_icon_name_for_contact (contact); + if (status_icon_name == NULL) + { + g_object_unref (contact); + return NULL; + } + + if (priv->show_protocols) + { + protocol_name = empathy_protocol_name_for_contact (contact); + icon_name = g_strdup_printf ("%s-%s", status_icon_name, protocol_name); + } + else + { + icon_name = g_strdup_printf ("%s", status_icon_name); + } + + pixbuf_status = g_hash_table_lookup (priv->status_icons, icon_name); + + if (pixbuf_status == NULL) + { + pixbuf_status = empathy_pixbuf_contact_status_icon_with_icon_name ( + contact, status_icon_name, priv->show_protocols); + + if (pixbuf_status != NULL) + { + g_hash_table_insert (priv->status_icons, g_strdup (icon_name), + pixbuf_status); + } + } + + g_object_unref (contact); + g_free (icon_name); + + return pixbuf_status; +} + +static void +update_persona (EmpathyPersonaStore *self, + FolksPersona *persona) +{ + EmpathyPersonaStorePriv *priv = GET_PRIV (self); + GtkTreePath *path; + gboolean do_set_active = FALSE; + gboolean do_set_refresh = FALSE; + const gchar *alias; + + path = find_persona (self, persona); + alias = folks_alias_get_alias (FOLKS_ALIAS (persona)); + + if (path == NULL) + { + DEBUG ("Contact:'%s' in list:NO, should be:YES", alias); + + add_persona (self, persona); + + if (priv->show_active) + { + do_set_active = TRUE; + DEBUG ("Set active (contact added)"); + } + } + else + { + EmpathyContact *contact; + GtkTreeIter iter; + GdkPixbuf *pixbuf_avatar; + GdkPixbuf *pixbuf_status; + gboolean now_online = FALSE; + gboolean was_online = TRUE; + + DEBUG ("Contact:'%s' in list:YES, should be:YES", alias); + + gtk_tree_model_get_iter (GTK_TREE_MODEL (self), &iter, path); + gtk_tree_path_free (path); + + /* Get online state now. */ + now_online = folks_presence_is_online (FOLKS_PRESENCE (persona)); + + /* Get online state before. */ + gtk_tree_model_get (GTK_TREE_MODEL (self), &iter, + EMPATHY_PERSONA_STORE_COL_IS_ONLINE, &was_online, + -1); + + /* Is this really an update or an online/offline. */ + if (priv->show_active) + { + if (was_online != now_online) + { + do_set_active = TRUE; + do_set_refresh = TRUE; + + DEBUG ("Set active (contact updated %s)", + was_online ? "online -> offline" : "offline -> online"); + } + else + { + /* Was TRUE for presence updates. */ + /* do_set_active = FALSE; */ + do_set_refresh = TRUE; + DEBUG ("Set active (contact updated)"); + } + } + + /* We still need to use EmpathyContact for the capabilities stuff */ + contact = empathy_contact_new (tpf_persona_get_contact ( + TPF_PERSONA (persona))); + + pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact, + 32, 32); + pixbuf_status = get_persona_status_icon (self, persona); + + gtk_list_store_set (GTK_LIST_STORE (self), &iter, + EMPATHY_PERSONA_STORE_COL_ICON_STATUS, pixbuf_status, + EMPATHY_PERSONA_STORE_COL_PIXBUF_AVATAR, pixbuf_avatar, + EMPATHY_PERSONA_STORE_COL_PIXBUF_AVATAR_VISIBLE, priv->show_avatars, + EMPATHY_PERSONA_STORE_COL_NAME, alias, + EMPATHY_PERSONA_STORE_COL_DISPLAY_ID, + folks_persona_get_display_id (persona), + EMPATHY_PERSONA_STORE_COL_PRESENCE_TYPE, + folks_presence_get_presence_type (FOLKS_PRESENCE (persona)), + EMPATHY_PERSONA_STORE_COL_STATUS, + folks_presence_get_presence_message (FOLKS_PRESENCE (persona)), + EMPATHY_PERSONA_STORE_COL_IS_ONLINE, now_online, + EMPATHY_PERSONA_STORE_COL_CAN_AUDIO_CALL, + empathy_contact_get_capabilities (contact) & + EMPATHY_CAPABILITIES_AUDIO, + EMPATHY_PERSONA_STORE_COL_CAN_VIDEO_CALL, + empathy_contact_get_capabilities (contact) & + EMPATHY_CAPABILITIES_VIDEO, + -1); + + g_object_unref (contact); + + if (pixbuf_avatar) + g_object_unref (pixbuf_avatar); + } + + if (priv->show_active && do_set_active) + { + persona_set_active (self, persona, do_set_active, do_set_refresh); + + if (do_set_active) + { + ShowActiveData *data; + + data = persona_active_new (self, persona, FALSE); + data->timeout = g_timeout_add_seconds (ACTIVE_USER_SHOW_TIME, + (GSourceFunc) persona_active_cb, + data); + } + } + + /* FIXME: when someone goes online then offline quickly, the + * first timeout sets the user to be inactive and the second + * timeout removes the user from the contact list, really we + * should remove the first timeout. + */ +} + +static void +individual_notify_personas_cb (GObject *object, + GParamSpec *pspec, + EmpathyPersonaStore *self) +{ + EmpathyPersonaStorePriv *priv = GET_PRIV (self); + GList *old_personas, *new_personas, *removed_personas, *l; + + /* Remove old personas which are no longer in the individual. + * Build a list of such personas to remove from our hash table. + * This is slow. */ + old_personas = g_hash_table_get_keys (priv->personas); + new_personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (object)); + removed_personas = NULL; + + for (l = old_personas; l != NULL; l = l->next) + { + GList *i = g_list_find (new_personas, l->data); + if (i == NULL) + removed_personas = g_list_prepend (removed_personas, l->data); + } + g_list_free (old_personas); + + /* Remove the removed personas. We can't do this from inside the above loop, + * as old_personas is only valid until the hash table is modified. */ + for (l = removed_personas; l != NULL; l = l->next) + remove_persona_and_disconnect (self, FOLKS_PERSONA (l->data)); + g_list_free (removed_personas); + + /* Add each of the new personas to the tree model */ + for (l = new_personas; l != NULL; l = l->next) + { + FolksPersona *persona = FOLKS_PERSONA (l->data); + + if (g_hash_table_lookup (priv->personas, persona) == NULL) + add_persona_and_connect (self, persona); + } +} + +static gint +sort_personas (FolksPersona *persona_a, + FolksPersona *persona_b) +{ + EmpathyContact *contact; + TpAccount *account_a, *account_b; + gint ret_val; + + g_return_val_if_fail (persona_a != NULL || persona_b != NULL, 0); + + /* alias */ + ret_val = g_utf8_collate (folks_alias_get_alias (FOLKS_ALIAS (persona_a)), + folks_alias_get_alias (FOLKS_ALIAS (persona_b))); + + if (ret_val != 0) + goto out; + + /* identifier */ + ret_val = g_utf8_collate (folks_persona_get_display_id (persona_a), + folks_persona_get_display_id (persona_b)); + + if (ret_val != 0) + goto out; + + contact = empathy_contact_new (tpf_persona_get_contact ( + TPF_PERSONA (persona_a))); + account_a = empathy_contact_get_account (contact); + g_object_unref (contact); + + contact = empathy_contact_new (tpf_persona_get_contact ( + TPF_PERSONA (persona_b))); + account_b = empathy_contact_get_account (contact); + g_object_unref (contact); + + /* protocol */ + ret_val = strcmp (tp_account_get_protocol (account_a), + tp_account_get_protocol (account_a)); + + if (ret_val != 0) + goto out; + + /* account ID */ + ret_val = strcmp (tp_proxy_get_object_path (account_a), + tp_proxy_get_object_path (account_a)); + +out: + return ret_val; +} + +static gint +state_sort_func (GtkTreeModel *model, + GtkTreeIter *iter_a, + GtkTreeIter *iter_b, + gpointer user_data) +{ + gint ret_val; + gchar *name_a, *name_b; + FolksPersona *persona_a, *persona_b; + + gtk_tree_model_get (model, iter_a, + EMPATHY_PERSONA_STORE_COL_NAME, &name_a, + EMPATHY_PERSONA_STORE_COL_PERSONA, &persona_a, + -1); + gtk_tree_model_get (model, iter_b, + EMPATHY_PERSONA_STORE_COL_NAME, &name_b, + EMPATHY_PERSONA_STORE_COL_PERSONA, &persona_b, + -1); + + if (persona_a == NULL || persona_b == NULL) { + ret_val = 0; + goto free_and_out; + } + + /* If we managed to get this far, we can start looking at + * the presences. + */ + ret_val = -tp_connection_presence_type_cmp_availability ( + folks_presence_get_presence_type (FOLKS_PRESENCE (persona_a)), + folks_presence_get_presence_type (FOLKS_PRESENCE (persona_b))); + + if (ret_val == 0) { + /* Fallback: compare by name et al. */ + ret_val = sort_personas (persona_a, persona_b); + } + +free_and_out: + g_free (name_a); + g_free (name_b); + + tp_clear_object (&persona_a); + tp_clear_object (&persona_b); + + return ret_val; +} + +static gint +name_sort_func (GtkTreeModel *model, + GtkTreeIter *iter_a, + GtkTreeIter *iter_b, + gpointer user_data) +{ + gchar *name_a, *name_b; + FolksPersona *persona_a, *persona_b; + gint ret_val; + + gtk_tree_model_get (model, iter_a, + EMPATHY_PERSONA_STORE_COL_NAME, &name_a, + EMPATHY_PERSONA_STORE_COL_PERSONA, &persona_a, + -1); + gtk_tree_model_get (model, iter_b, + EMPATHY_PERSONA_STORE_COL_NAME, &name_b, + EMPATHY_PERSONA_STORE_COL_PERSONA, &persona_b, + -1); + + if (persona_a == NULL || persona_b == NULL) + ret_val = 0; + else + ret_val = sort_personas (persona_a, persona_b); + + tp_clear_object (&persona_a); + tp_clear_object (&persona_b); + + return ret_val; +} + +static GtkTreePath * +find_persona (EmpathyPersonaStore *self, + FolksPersona *persona) +{ + EmpathyPersonaStorePriv *priv = GET_PRIV (self); + GtkTreeRowReference *row; + + row = g_hash_table_lookup (priv->personas, persona); + if (row == NULL) + return NULL; + + return gtk_tree_row_reference_get_path (row); +} + +static gboolean +update_list_mode_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + EmpathyPersonaStore *self) +{ + EmpathyPersonaStorePriv *priv; + FolksPersona *persona; + GdkPixbuf *pixbuf_status; + + priv = GET_PRIV (self); + + gtk_tree_model_get (model, iter, + EMPATHY_PERSONA_STORE_COL_PERSONA, &persona, + -1); + + if (persona == NULL) + return FALSE; + + /* get icon from hash_table */ + pixbuf_status = get_persona_status_icon (self, persona); + + gtk_list_store_set (GTK_LIST_STORE (self), iter, + EMPATHY_PERSONA_STORE_COL_ICON_STATUS, pixbuf_status, + EMPATHY_PERSONA_STORE_COL_PIXBUF_AVATAR_VISIBLE, priv->show_avatars, + -1); + + tp_clear_object (&persona); + + return FALSE; +} + +static void +set_up (EmpathyPersonaStore *self) +{ + EmpathyPersonaStorePriv *priv; + GType types[] = { + GDK_TYPE_PIXBUF, /* Status pixbuf */ + GDK_TYPE_PIXBUF, /* Avatar pixbuf */ + G_TYPE_BOOLEAN, /* Avatar pixbuf visible */ + G_TYPE_STRING, /* Name */ + G_TYPE_STRING, /* Display ID */ + G_TYPE_UINT, /* Presence type */ + G_TYPE_STRING, /* Status string */ + FOLKS_TYPE_PERSONA, /* Persona */ + G_TYPE_BOOLEAN, /* Is active */ + G_TYPE_BOOLEAN, /* Is online */ + G_TYPE_BOOLEAN, /* Can make audio calls */ + G_TYPE_BOOLEAN, /* Can make video calls */ + }; + + priv = GET_PRIV (self); + + gtk_list_store_set_column_types (GTK_LIST_STORE (self), + EMPATHY_PERSONA_STORE_COL_COUNT, types); + + /* Set up sorting */ + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (self), + EMPATHY_PERSONA_STORE_COL_NAME, name_sort_func, self, NULL); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (self), + EMPATHY_PERSONA_STORE_COL_STATUS, state_sort_func, self, NULL); + + priv->sort_criterion = EMPATHY_PERSONA_STORE_SORT_NAME; + empathy_persona_store_set_sort_criterion (self, priv->sort_criterion); +} + +static void +empathy_persona_store_init (EmpathyPersonaStore *self) +{ + EmpathyPersonaStorePriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EMPATHY_TYPE_PERSONA_STORE, EmpathyPersonaStorePriv); + + self->priv = priv; + + priv->show_avatars = TRUE; + priv->show_protocols = FALSE; + priv->inhibit_active = g_timeout_add_seconds (ACTIVE_USER_WAIT_TO_ENABLE_TIME, + (GSourceFunc) inhibit_active_cb, self); + + priv->status_icons = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + g_object_unref); + priv->personas = g_hash_table_new_full (g_direct_hash, g_direct_equal, + g_object_unref, (GDestroyNotify) gtk_tree_row_reference_free); + + set_up (self); +} + +static void +get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + EmpathyPersonaStorePriv *priv = GET_PRIV (object); + + switch (param_id) + { + case PROP_INDIVIDUAL: + g_value_set_object (value, priv->individual); + break; + case PROP_SHOW_AVATARS: + g_value_set_boolean (value, priv->show_avatars); + break; + case PROP_SHOW_PROTOCOLS: + g_value_set_boolean (value, priv->show_protocols); + break; + case PROP_SORT_CRITERION: + g_value_set_enum (value, priv->sort_criterion); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + EmpathyPersonaStore *self = EMPATHY_PERSONA_STORE (object); + + switch (param_id) + { + case PROP_INDIVIDUAL: + empathy_persona_store_set_individual (self, g_value_get_object (value)); + break; + case PROP_SHOW_AVATARS: + empathy_persona_store_set_show_avatars (self, + g_value_get_boolean (value)); + break; + case PROP_SHOW_PROTOCOLS: + empathy_persona_store_set_show_protocols (self, + g_value_get_boolean (value)); + break; + case PROP_SORT_CRITERION: + empathy_persona_store_set_sort_criterion (self, + g_value_get_enum (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + EmpathyPersonaStorePriv *priv = GET_PRIV (object); + + empathy_persona_store_set_individual (EMPATHY_PERSONA_STORE (object), NULL); + + if (priv->inhibit_active != 0) + { + g_source_remove (priv->inhibit_active); + priv->inhibit_active = 0; + } + + if (priv->setup_idle_id != 0) + { + g_source_remove (priv->setup_idle_id); + priv->setup_idle_id = 0; + } + + G_OBJECT_CLASS (empathy_persona_store_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + EmpathyPersonaStorePriv *priv = GET_PRIV (object); + + g_hash_table_destroy (priv->status_icons); + g_hash_table_destroy (priv->personas); + + G_OBJECT_CLASS (empathy_persona_store_parent_class)->finalize (object); +} + +static void +empathy_persona_store_class_init (EmpathyPersonaStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + /** + * EmpathyPersonaStore:individual: + * + * The #FolksIndividual whose personas should be listed by the store. This + * may be %NULL, which results in an empty store. + */ + g_object_class_install_property (object_class, PROP_INDIVIDUAL, + g_param_spec_object ("individual", + "Individual", + "The FolksIndividual whose Personas should be listed by the store.", + FOLKS_TYPE_INDIVIDUAL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * EmpathyPersonaStore:show-avatars: + * + * Whether the store should display avatars for personas. This is a property + * of the store rather than of #EmpathyPersonaView for efficiency reasons. + */ + g_object_class_install_property (object_class, PROP_SHOW_AVATARS, + g_param_spec_boolean ("show-avatars", + "Show Avatars", + "Whether the store should display avatars for personas.", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * EmpathyPersonaStore:show-protocols: + * + * Whether the store should display protocol icons for personas. This is a + * property of the store rather than of #EmpathyPersonaView because it is + * closely tied in with #EmpathyPersonaStore:show-avatars. + */ + g_object_class_install_property (object_class, PROP_SHOW_PROTOCOLS, + g_param_spec_boolean ("show-protocols", + "Show Protocols", + "Whether the store should display protocol icons for personas.", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * EmpathyPersonaStore:sort-criterion: + * + * The criterion used to sort the personas in the store. + */ + g_object_class_install_property (object_class, PROP_SORT_CRITERION, + g_param_spec_enum ("sort-criterion", + "Sort criterion", + "The sort criterion to use for sorting the persona list", + EMPATHY_TYPE_PERSONA_STORE_SORT, + EMPATHY_PERSONA_STORE_SORT_NAME, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_type_class_add_private (object_class, sizeof (EmpathyPersonaStorePriv)); +} + +/** + * empathy_persona_store_new: + * @individual: the #FolksIndividual whose personas should be used in the store, + * or %NULL + * + * Create a new #EmpathyPersonaStore with the personas from the given + * @individual. + * + * Return value: a new #EmpathyPersonaStore + */ +EmpathyPersonaStore * +empathy_persona_store_new (FolksIndividual *individual) +{ + g_return_val_if_fail (individual == NULL || FOLKS_IS_INDIVIDUAL (individual), + NULL); + + return g_object_new (EMPATHY_TYPE_PERSONA_STORE, + "individual", individual, NULL); +} + +/** + * empathy_persona_store_get_individual: + * @self: an #EmpathyPersonaStore + * + * Get the value of #EmpathyPersonaStore:individual. + * + * Return value: the individual being displayed by the store, or %NULL + */ +FolksIndividual * +empathy_persona_store_get_individual (EmpathyPersonaStore *self) +{ + g_return_val_if_fail (EMPATHY_IS_PERSONA_STORE (self), NULL); + + return GET_PRIV (self)->individual; +} + +/** + * empathy_persona_store_set_individual: + * @self: an #EmpathyPersonaStore + * @individual: the new individual to display in the store, or %NULL + * + * Set #EmpathyPersonaStore:individual to @individual, replacing the personas + * which were in the store with the personas belonging to @individual, or with + * nothing if @individual is %NULL. + */ +void +empathy_persona_store_set_individual (EmpathyPersonaStore *self, + FolksIndividual *individual) +{ + EmpathyPersonaStorePriv *priv; + + g_return_if_fail (EMPATHY_IS_PERSONA_STORE (self)); + g_return_if_fail (individual == NULL || FOLKS_IS_INDIVIDUAL (individual)); + + priv = GET_PRIV (self); + + /* Remove the old individual */ + if (priv->individual != NULL) + { + GList *personas, *l; + + g_signal_handlers_disconnect_by_func (priv->individual, + (GCallback) individual_notify_personas_cb, self); + + /* Disconnect from and remove all personas belonging to this individual */ + personas = folks_individual_get_personas (priv->individual); + for (l = personas; l != NULL; l = l->next) + remove_persona_and_disconnect (self, FOLKS_PERSONA (l->data)); + + g_object_unref (priv->individual); + } + + priv->individual = individual; + + /* Add the new individual */ + if (individual != NULL) + { + GList *personas, *l; + + g_object_ref (individual); + + g_signal_connect (individual, "notify::personas", + (GCallback) individual_notify_personas_cb, self); + + /* Add pre-existing Personas */ + personas = folks_individual_get_personas (individual); + for (l = personas; l != NULL; l = l->next) + add_persona_and_connect (self, FOLKS_PERSONA (l->data)); + } + + g_object_notify (G_OBJECT (self), "individual"); +} + +/** + * empathy_persona_store_get_show_avatars: + * @self: an #EmpathyPersonaStore + * + * Get the value of #EmpathyPersonaStore:show-avatars. + * + * Return value: %TRUE if avatars are made available by the store, %FALSE + * otherwise + */ +gboolean +empathy_persona_store_get_show_avatars (EmpathyPersonaStore *self) +{ + g_return_val_if_fail (EMPATHY_IS_PERSONA_STORE (self), TRUE); + + return GET_PRIV (self)->show_avatars; +} + +/** + * empathy_persona_store_set_show_avatars: + * @self: an #EmpathyPersonaStore + * @show_avatars: %TRUE to make avatars available through the store, %FALSE + * otherwise + * + * Set #EmpathyPersonaStore:show-avatars to @show_avatars. + */ +void +empathy_persona_store_set_show_avatars (EmpathyPersonaStore *self, + gboolean show_avatars) +{ + EmpathyPersonaStorePriv *priv; + + g_return_if_fail (EMPATHY_IS_PERSONA_STORE (self)); + + priv = GET_PRIV (self); + priv->show_avatars = show_avatars; + + gtk_tree_model_foreach (GTK_TREE_MODEL (self), + (GtkTreeModelForeachFunc) update_list_mode_foreach, self); + + g_object_notify (G_OBJECT (self), "show-avatars"); +} + +/** + * empathy_persona_store_get_show_protocols: + * @self: an #EmpathyPersonaStore + * + * Get the value of #EmpathyPersonaStore:show-protocols. + * + * Return value: %TRUE if protocol images are made available by the store, + * %FALSE otherwise + */ +gboolean +empathy_persona_store_get_show_protocols (EmpathyPersonaStore *self) +{ + g_return_val_if_fail (EMPATHY_IS_PERSONA_STORE (self), TRUE); + + return GET_PRIV (self)->show_protocols; +} + +/** + * empathy_persona_store_set_show_protocols: + * @self: an #EmpathyPersonaStore + * @show_protocols: %TRUE to make protocol images available through the store, + * %FALSE otherwise + * + * Set #EmpathyPersonaStore:show-protocols to @show_protocols. + */ +void +empathy_persona_store_set_show_protocols (EmpathyPersonaStore *self, + gboolean show_protocols) +{ + EmpathyPersonaStorePriv *priv; + + g_return_if_fail (EMPATHY_IS_PERSONA_STORE (self)); + + priv = GET_PRIV (self); + priv->show_protocols = show_protocols; + + gtk_tree_model_foreach (GTK_TREE_MODEL (self), + (GtkTreeModelForeachFunc) update_list_mode_foreach, self); + + g_object_notify (G_OBJECT (self), "show-protocols"); +} + +/** + * empathy_persona_store_get_sort_criterion: + * @self: an #EmpathyPersonaStore + * + * Get the value of #EmpathyPersonaStore:sort-criterion. + * + * Return value: the criterion used to sort the personas in the store + */ +EmpathyPersonaStoreSort +empathy_persona_store_get_sort_criterion (EmpathyPersonaStore *self) +{ + g_return_val_if_fail (EMPATHY_IS_PERSONA_STORE (self), 0); + + return GET_PRIV (self)->sort_criterion; +} + +/** + * empathy_persona_store_set_sort_criterion: + * @self: an #EmpathyPersonaStore + * @show_avatars: a criterion to be used to sort personas in the store + * + * Set #EmpathyPersonaStore:sort-criterion to @sort_criterion. + */ +void +empathy_persona_store_set_sort_criterion (EmpathyPersonaStore *self, + EmpathyPersonaStoreSort sort_criterion) +{ + EmpathyPersonaStorePriv *priv; + + g_return_if_fail (EMPATHY_IS_PERSONA_STORE (self)); + + priv = GET_PRIV (self); + priv->sort_criterion = sort_criterion; + + switch (sort_criterion) + { + case EMPATHY_PERSONA_STORE_SORT_STATE: + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self), + EMPATHY_PERSONA_STORE_COL_STATUS, GTK_SORT_ASCENDING); + break; + case EMPATHY_PERSONA_STORE_SORT_NAME: + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self), + EMPATHY_PERSONA_STORE_COL_NAME, GTK_SORT_ASCENDING); + break; + default: + g_assert_not_reached (); + break; + } + + g_object_notify (G_OBJECT (self), "sort-criterion"); +} diff --git a/libempathy-gtk/empathy-persona-store.h b/libempathy-gtk/empathy-persona-store.h new file mode 100644 index 00000000..8c375998 --- /dev/null +++ b/libempathy-gtk/empathy-persona-store.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2005-2007 Imendio AB + * Copyright (C) 2007-2008, 2010 Collabora Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authors: Mikael Hallendal + * Martyn Russell + * Xavier Claessens + * Philip Withnall + * + * Based off EmpathyContactListStore. + */ + +#ifndef __EMPATHY_PERSONA_STORE_H__ +#define __EMPATHY_PERSONA_STORE_H__ + +#include + +#include + +G_BEGIN_DECLS + +#define EMPATHY_TYPE_PERSONA_STORE (empathy_persona_store_get_type ()) +#define EMPATHY_PERSONA_STORE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), \ + EMPATHY_TYPE_PERSONA_STORE, EmpathyPersonaStore)) +#define EMPATHY_PERSONA_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), \ + EMPATHY_TYPE_PERSONA_STORE, EmpathyPersonaStoreClass)) +#define EMPATHY_IS_PERSONA_STORE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), \ + EMPATHY_TYPE_PERSONA_STORE)) +#define EMPATHY_IS_PERSONA_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), \ + EMPATHY_TYPE_PERSONA_STORE)) +#define EMPATHY_PERSONA_STORE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), \ + EMPATHY_TYPE_PERSONA_STORE, EmpathyPersonaStoreClass)) + +typedef enum +{ + EMPATHY_PERSONA_STORE_SORT_STATE, + EMPATHY_PERSONA_STORE_SORT_NAME +} EmpathyPersonaStoreSort; + +typedef enum +{ + EMPATHY_PERSONA_STORE_COL_ICON_STATUS, + EMPATHY_PERSONA_STORE_COL_PIXBUF_AVATAR, + EMPATHY_PERSONA_STORE_COL_PIXBUF_AVATAR_VISIBLE, + EMPATHY_PERSONA_STORE_COL_NAME, + EMPATHY_PERSONA_STORE_COL_DISPLAY_ID, + EMPATHY_PERSONA_STORE_COL_PRESENCE_TYPE, + EMPATHY_PERSONA_STORE_COL_STATUS, + EMPATHY_PERSONA_STORE_COL_PERSONA, + EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, + EMPATHY_PERSONA_STORE_COL_IS_ONLINE, + EMPATHY_PERSONA_STORE_COL_CAN_AUDIO_CALL, + EMPATHY_PERSONA_STORE_COL_CAN_VIDEO_CALL, + EMPATHY_PERSONA_STORE_COL_COUNT, +} EmpathyPersonaStoreCol; + +typedef struct +{ + GtkListStore parent; + gpointer priv; +} EmpathyPersonaStore; + +typedef struct +{ + GtkListStoreClass parent_class; +} EmpathyPersonaStoreClass; + +GType empathy_persona_store_get_type (void) G_GNUC_CONST; + +EmpathyPersonaStore *empathy_persona_store_new (FolksIndividual *individual); + +FolksIndividual *empathy_persona_store_get_individual ( + EmpathyPersonaStore *self); +void empathy_persona_store_set_individual (EmpathyPersonaStore *self, + FolksIndividual *individual); + +gboolean empathy_persona_store_get_show_avatars (EmpathyPersonaStore *self); +void empathy_persona_store_set_show_avatars (EmpathyPersonaStore *self, + gboolean show_avatars); + +gboolean empathy_persona_store_get_show_protocols (EmpathyPersonaStore *self); +void empathy_persona_store_set_show_protocols (EmpathyPersonaStore *self, + gboolean show_protocols); + +EmpathyPersonaStoreSort empathy_persona_store_get_sort_criterion ( + EmpathyPersonaStore *self); +void empathy_persona_store_set_sort_criterion (EmpathyPersonaStore *self, + EmpathyPersonaStoreSort sort_criterion); + +G_END_DECLS + +#endif /* __EMPATHY_PERSONA_STORE_H__ */ diff --git a/libempathy-gtk/empathy-persona-view.c b/libempathy-gtk/empathy-persona-view.c new file mode 100644 index 00000000..384f0803 --- /dev/null +++ b/libempathy-gtk/empathy-persona-view.c @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2005-2007 Imendio AB + * Copyright (C) 2007-2008, 2010 Collabora Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authors: Mikael Hallendal + * Martyn Russell + * Xavier Claessens + * Philip Withnall + * + * Based off EmpathyContactListView. + */ + +#include "config.h" + +#include + +#include +#include + +#include + +#include +#include + +#include + +#include "empathy-persona-view.h" +#include "empathy-contact-widget.h" +#include "empathy-images.h" +#include "empathy-cell-renderer-text.h" +#include "empathy-cell-renderer-activatable.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_CONTACT +#include + +/** + * SECTION:empathy-persona-view + * @title: EmpathyPersonaView + * @short_description: A tree view which displays personas from an individual + * @include: libempathy-gtk/empathy-persona-view.h + * + * #EmpathyPersonaView is a tree view widget which displays the personas from + * a given #EmpathyPersonaStore. + * + * It supports hiding offline personas and highlighting active personas. Active + * personas are those which have recently changed state (e.g. online, offline or + * from normal to a busy state). + */ + +#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyPersonaView) + +typedef struct +{ + GtkTreeModelFilter *filter; + GtkWidget *tooltip_widget; + gboolean show_offline; +} EmpathyPersonaViewPriv; + +enum +{ + PROP_0, + PROP_MODEL, + PROP_SHOW_OFFLINE, +}; + +G_DEFINE_TYPE (EmpathyPersonaView, empathy_persona_view, GTK_TYPE_TREE_VIEW); + +static gboolean +filter_visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + EmpathyPersonaView *self) +{ + EmpathyPersonaViewPriv *priv = GET_PRIV (self); + gboolean is_online; + + gtk_tree_model_get (model, iter, + EMPATHY_PERSONA_STORE_COL_IS_ONLINE, &is_online, + -1); + + return (priv->show_offline || is_online); +} + +static void +set_model (EmpathyPersonaView *self, + GtkTreeModel *model) +{ + EmpathyPersonaViewPriv *priv = GET_PRIV (self); + + tp_clear_object (&priv->filter); + + priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (model, + NULL)); + gtk_tree_model_filter_set_visible_func (priv->filter, + (GtkTreeModelFilterVisibleFunc) filter_visible_func, self, NULL); + + gtk_tree_view_set_model (GTK_TREE_VIEW (self), GTK_TREE_MODEL (priv->filter)); +} + +static void +tooltip_destroy_cb (GtkWidget *widget, + EmpathyPersonaView *self) +{ + EmpathyPersonaViewPriv *priv = GET_PRIV (self); + + if (priv->tooltip_widget) + { + DEBUG ("Tooltip destroyed"); + g_object_unref (priv->tooltip_widget); + priv->tooltip_widget = NULL; + } +} + +static gboolean +query_tooltip_cb (EmpathyPersonaView *self, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip, + gpointer user_data) +{ + EmpathyPersonaViewPriv *priv = GET_PRIV (self); + FolksPersona *persona; + EmpathyContact *contact; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; + static gint running = 0; + gboolean ret = FALSE; + + /* Avoid an infinite loop. See GNOME bug #574377 */ + if (running > 0) + return FALSE; + running++; + + if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (self), &x, &y, + keyboard_mode, &model, &path, &iter)) + { + goto OUT; + } + + gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (self), tooltip, path); + gtk_tree_path_free (path); + + gtk_tree_model_get (model, &iter, + EMPATHY_PERSONA_STORE_COL_PERSONA, &persona, + -1); + if (persona == NULL) + goto OUT; + + contact = empathy_contact_new (tpf_persona_get_contact ( + TPF_PERSONA (persona))); + + if (priv->tooltip_widget == NULL) + { + priv->tooltip_widget = empathy_contact_widget_new (contact, + EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP | + EMPATHY_CONTACT_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", + (GCallback) tooltip_destroy_cb, self); + gtk_widget_show (priv->tooltip_widget); + } + else + { + empathy_contact_widget_set_contact (priv->tooltip_widget, contact); + } + + gtk_tooltip_set_custom (tooltip, priv->tooltip_widget); + ret = TRUE; + + g_object_unref (contact); + g_object_unref (persona); + +OUT: + running--; + + return ret; +} + +static void +cell_set_background (EmpathyPersonaView *self, + GtkCellRenderer *cell, + gboolean is_active) +{ + GdkColor color; + GtkStyle *style; + + style = gtk_widget_get_style (GTK_WIDGET (self)); + + if (is_active) + { + color = style->bg[GTK_STATE_SELECTED]; + + /* Here we take the current theme colour and add it to + * the colour for white and average the two. This + * gives a colour which is inline with the theme but + * slightly whiter. + */ + color.red = (color.red + (style->white).red) / 2; + color.green = (color.green + (style->white).green) / 2; + color.blue = (color.blue + (style->white).blue) / 2; + + g_object_set (cell, "cell-background-gdk", &color, NULL); + } + else + { + g_object_set (cell, "cell-background-gdk", NULL, NULL); + } +} + +static void +pixbuf_cell_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + EmpathyPersonaView *self) +{ + GdkPixbuf *pixbuf; + gboolean is_active; + + gtk_tree_model_get (model, iter, + EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active, + EMPATHY_PERSONA_STORE_COL_ICON_STATUS, &pixbuf, + -1); + + g_object_set (cell, "pixbuf", pixbuf, NULL); + tp_clear_object (&pixbuf); + + cell_set_background (self, cell, is_active); +} + +static void +audio_call_cell_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + EmpathyPersonaView *self) +{ + gboolean is_active; + gboolean can_audio, can_video; + + gtk_tree_model_get (model, iter, + EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active, + EMPATHY_PERSONA_STORE_COL_CAN_AUDIO_CALL, &can_audio, + EMPATHY_PERSONA_STORE_COL_CAN_VIDEO_CALL, &can_video, + -1); + + g_object_set (cell, + "visible", (can_audio || can_video), + "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP, + NULL); + + cell_set_background (self, cell, is_active); +} + +static void +avatar_cell_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + EmpathyPersonaView *self) +{ + GdkPixbuf *pixbuf; + gboolean show_avatar, is_active; + + gtk_tree_model_get (model, iter, + EMPATHY_PERSONA_STORE_COL_PIXBUF_AVATAR, &pixbuf, + EMPATHY_PERSONA_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar, + EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active, + -1); + + g_object_set (cell, + "visible", show_avatar, + "pixbuf", pixbuf, + NULL); + + tp_clear_object (&pixbuf); + + cell_set_background (self, cell, is_active); +} + +static void +text_cell_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + EmpathyPersonaView *self) +{ + gboolean is_active; + + gtk_tree_model_get (model, iter, + EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active, + -1); + + cell_set_background (self, cell, is_active); +} + +static void +empathy_persona_view_init (EmpathyPersonaView *self) +{ + EmpathyPersonaViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EMPATHY_TYPE_PERSONA_VIEW, EmpathyPersonaViewPriv); + + self->priv = priv; + + /* Connect to tree view signals rather than override. */ + g_signal_connect (self, "query-tooltip", (GCallback) query_tooltip_cb, NULL); +} + +static void +constructed (GObject *object) +{ + EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object); + GtkCellRenderer *cell; + GtkTreeViewColumn *col; + + /* Set up view */ + g_object_set (self, + "headers-visible", FALSE, + "show-expanders", FALSE, + NULL); + + col = gtk_tree_view_column_new (); + + /* State */ + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (col, cell, FALSE); + gtk_tree_view_column_set_cell_data_func (col, cell, + (GtkTreeCellDataFunc) pixbuf_cell_data_func, self, NULL); + + g_object_set (cell, + "xpad", 5, + "ypad", 1, + "visible", TRUE, + NULL); + + /* Name */ + cell = empathy_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (col, cell, TRUE); + gtk_tree_view_column_set_cell_data_func (col, cell, + (GtkTreeCellDataFunc) text_cell_data_func, self, NULL); + + gtk_tree_view_column_add_attribute (col, cell, + "name", EMPATHY_PERSONA_STORE_COL_DISPLAY_ID); + gtk_tree_view_column_add_attribute (col, cell, + "text", EMPATHY_PERSONA_STORE_COL_DISPLAY_ID); + gtk_tree_view_column_add_attribute (col, cell, + "presence-type", EMPATHY_PERSONA_STORE_COL_PRESENCE_TYPE); + gtk_tree_view_column_add_attribute (col, cell, + "status", EMPATHY_PERSONA_STORE_COL_STATUS); + + /* Audio Call Icon */ + cell = empathy_cell_renderer_activatable_new (); + gtk_tree_view_column_pack_start (col, cell, FALSE); + gtk_tree_view_column_set_cell_data_func (col, cell, + (GtkTreeCellDataFunc) audio_call_cell_data_func, self, NULL); + + g_object_set (cell, + "visible", FALSE, + NULL); + + /* Avatar */ + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (col, cell, FALSE); + gtk_tree_view_column_set_cell_data_func (col, cell, + (GtkTreeCellDataFunc) avatar_cell_data_func, self, NULL); + + g_object_set (cell, + "xpad", 0, + "ypad", 0, + "visible", FALSE, + "width", 32, + "height", 32, + NULL); + + /* Actually add the column now we have added all cell renderers */ + gtk_tree_view_append_column (GTK_TREE_VIEW (self), col); +} + +static void +get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + EmpathyPersonaViewPriv *priv = GET_PRIV (object); + + switch (param_id) + { + case PROP_MODEL: + g_value_set_object (value, priv->filter); + break; + case PROP_SHOW_OFFLINE: + g_value_set_boolean (value, priv->show_offline); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object); + + switch (param_id) + { + case PROP_MODEL: + set_model (self, g_value_get_object (value)); + break; + case PROP_SHOW_OFFLINE: + empathy_persona_view_set_show_offline (self, + g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object); + EmpathyPersonaViewPriv *priv = GET_PRIV (self); + + tp_clear_object (&priv->filter); + + if (priv->tooltip_widget) + gtk_widget_destroy (priv->tooltip_widget); + priv->tooltip_widget = NULL; + + G_OBJECT_CLASS (empathy_persona_view_parent_class)->dispose (object); +} + +static void +empathy_persona_view_class_init (EmpathyPersonaViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + /* We override the "model" property so that we can wrap it in a + * GtkTreeModelFilter for showing/hiding offline personas. */ + g_object_class_override_property (object_class, PROP_MODEL, "model"); + + /** + * EmpathyPersonaStore:show-offline: + * + * Whether to display offline personas. + */ + g_object_class_install_property (object_class, PROP_SHOW_OFFLINE, + g_param_spec_boolean ("show-offline", + "Show Offline", + "Whether to display offline personas.", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_type_class_add_private (object_class, sizeof (EmpathyPersonaViewPriv)); +} + +/** + * empathy_persona_view_new: + * @store: an #EmpathyPersonaStore + * + * Create a new #EmpathyPersonaView displaying the personas in + * #EmpathyPersonaStore. + * + * Return value: a new #EmpathyPersonaView + */ +EmpathyPersonaView * +empathy_persona_view_new (EmpathyPersonaStore *store) +{ + g_return_val_if_fail (EMPATHY_IS_PERSONA_STORE (store), NULL); + + return g_object_new (EMPATHY_TYPE_PERSONA_VIEW, "model", store, NULL); +} + +/** + * empathy_persona_view_dup_selected: + * @self: an #EmpathyPersonaView + * + * Return the #FolksPersona associated with the currently selected row. The + * persona is referenced before being returned. If no row is selected, %NULL is + * returned. + * + * Return value: the currently selected #FolksPersona, or %NULL; unref with + * g_object_unref() + */ +FolksPersona * +empathy_persona_view_dup_selected (EmpathyPersonaView *self) +{ + EmpathyPersonaViewPriv *priv; + GtkTreeSelection *selection; + GtkTreeIter iter; + GtkTreeModel *model; + FolksPersona *persona; + + g_return_val_if_fail (EMPATHY_IS_PERSONA_VIEW (self), NULL); + + priv = GET_PRIV (self); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self)); + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) + return NULL; + + gtk_tree_model_get (model, &iter, + EMPATHY_PERSONA_STORE_COL_PERSONA, &persona, + -1); + + return persona; +} + +/** + * empathy_persona_view_get_show_offline: + * @self: an #EmpathyPersonaView + * + * Get the value of the #EmpathyPersonaView:show-offline property. + * + * Return value: %TRUE if offline personas are being shown, %FALSE otherwise + */ +gboolean +empathy_persona_view_get_show_offline (EmpathyPersonaView *self) +{ + g_return_val_if_fail (EMPATHY_IS_PERSONA_VIEW (self), FALSE); + + return GET_PRIV (self)->show_offline; +} + +/** + * empathy_persona_view_set_show_offline: + * @self: an #EmpathyPersonaView + * @show_offline: %TRUE to show personas which are offline, %FALSE otherwise + * + * Set the #EmpathyPersonaView:show-offline property to @show_offline. + */ +void +empathy_persona_view_set_show_offline (EmpathyPersonaView *self, + gboolean show_offline) +{ + EmpathyPersonaViewPriv *priv; + + g_return_if_fail (EMPATHY_IS_PERSONA_VIEW (self)); + + priv = GET_PRIV (self); + priv->show_offline = show_offline; + + gtk_tree_model_filter_refilter (priv->filter); + + g_object_notify (G_OBJECT (self), "show-offline"); +} diff --git a/libempathy-gtk/empathy-persona-view.h b/libempathy-gtk/empathy-persona-view.h new file mode 100644 index 00000000..11fe039e --- /dev/null +++ b/libempathy-gtk/empathy-persona-view.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2005-2007 Imendio AB + * Copyright (C) 2007-2008, 2010 Collabora Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authors: Mikael Hallendal + * Martyn Russell + * Xavier Claessens + * Philip Withnall + * + * Based off EmpathyContactListView. + */ + +#ifndef __EMPATHY_PERSONA_VIEW_H__ +#define __EMPATHY_PERSONA_VIEW_H__ + +#include + +#include + +#include "empathy-persona-store.h" + +G_BEGIN_DECLS + +#define EMPATHY_TYPE_PERSONA_VIEW (empathy_persona_view_get_type ()) +#define EMPATHY_PERSONA_VIEW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), \ + EMPATHY_TYPE_PERSONA_VIEW, EmpathyPersonaView)) +#define EMPATHY_PERSONA_VIEW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), \ + EMPATHY_TYPE_PERSONA_VIEW, EmpathyPersonaViewClass)) +#define EMPATHY_IS_PERSONA_VIEW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), \ + EMPATHY_TYPE_PERSONA_VIEW)) +#define EMPATHY_IS_PERSONA_VIEW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), \ + EMPATHY_TYPE_PERSONA_VIEW)) +#define EMPATHY_PERSONA_VIEW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), \ + EMPATHY_TYPE_PERSONA_VIEW, EmpathyPersonaViewClass)) + +typedef struct +{ + GtkTreeView parent; + gpointer priv; +} EmpathyPersonaView; + +typedef struct +{ + GtkTreeViewClass parent_class; +} EmpathyPersonaViewClass; + +GType empathy_persona_view_get_type (void) G_GNUC_CONST; + +EmpathyPersonaView *empathy_persona_view_new (EmpathyPersonaStore *store); + +FolksPersona *empathy_persona_view_dup_selected (EmpathyPersonaView *self); + +gboolean empathy_persona_view_get_show_offline (EmpathyPersonaView *self); +void empathy_persona_view_set_show_offline (EmpathyPersonaView *self, + gboolean show_offline); + +G_END_DECLS + +#endif /* __EMPATHY_PERSONA_VIEW_H__ */ -- 2.39.2