aboutsummaryrefslogtreecommitdiffstats
path: root/libempathy-gtk
diff options
context:
space:
mode:
Diffstat (limited to 'libempathy-gtk')
-rw-r--r--libempathy-gtk/Makefile.am9
-rw-r--r--libempathy-gtk/empathy-avatar-chooser.c4
-rw-r--r--libempathy-gtk/empathy-chat.c4
-rw-r--r--libempathy-gtk/empathy-contact-widget.c307
-rw-r--r--libempathy-gtk/empathy-contact-widget.ui104
-rw-r--r--libempathy-gtk/empathy-groups-widget.c600
-rw-r--r--libempathy-gtk/empathy-groups-widget.h66
-rw-r--r--libempathy-gtk/empathy-individual-edit-dialog.c273
-rw-r--r--libempathy-gtk/empathy-individual-edit-dialog.h59
-rw-r--r--libempathy-gtk/empathy-individual-information-dialog.c276
-rw-r--r--libempathy-gtk/empathy-individual-information-dialog.h60
-rw-r--r--libempathy-gtk/empathy-individual-linker.c56
-rw-r--r--libempathy-gtk/empathy-individual-menu.c613
-rw-r--r--libempathy-gtk/empathy-individual-menu.h21
-rw-r--r--libempathy-gtk/empathy-individual-store.c134
-rw-r--r--libempathy-gtk/empathy-individual-view.c481
-rw-r--r--libempathy-gtk/empathy-individual-view.h5
-rw-r--r--libempathy-gtk/empathy-individual-widget.c1890
-rw-r--r--libempathy-gtk/empathy-individual-widget.h42
-rw-r--r--libempathy-gtk/empathy-individual-widget.ui188
-rw-r--r--libempathy-gtk/empathy-live-search.c2
-rw-r--r--libempathy-gtk/empathy-log-window.ui70
-rw-r--r--libempathy-gtk/empathy-new-call-dialog.c2
-rw-r--r--libempathy-gtk/empathy-persona-store.c9
-rw-r--r--libempathy-gtk/empathy-persona-store.h1
-rw-r--r--libempathy-gtk/empathy-persona-view.c4
-rw-r--r--libempathy-gtk/empathy-share-my-desktop.c2
-rw-r--r--libempathy-gtk/empathy-theme-boxes.c12
-rw-r--r--libempathy-gtk/empathy-ui-utils.c7
-rw-r--r--libempathy-gtk/empathy-ui-utils.h3
30 files changed, 4417 insertions, 887 deletions
diff --git a/libempathy-gtk/Makefile.am b/libempathy-gtk/Makefile.am
index 09cc9ca22..de6cba2f1 100644
--- a/libempathy-gtk/Makefile.am
+++ b/libempathy-gtk/Makefile.am
@@ -54,7 +54,10 @@ libempathy_gtk_handwritten_source = \
empathy-contact-selector-dialog.c \
empathy-contact-widget.c \
empathy-geometry.c \
+ empathy-groups-widget.c \
empathy-individual-dialogs.c \
+ empathy-individual-edit-dialog.c \
+ empathy-individual-information-dialog.c \
empathy-individual-linker.c \
empathy-individual-menu.c \
empathy-individual-store.c \
@@ -110,8 +113,11 @@ libempathy_gtk_headers = \
empathy-contact-selector-dialog.h \
empathy-contact-widget.h \
empathy-geometry.h \
+ empathy-groups-widget.h \
empathy-images.h \
empathy-individual-dialogs.h \
+ empathy-individual-edit-dialog.h \
+ empathy-individual-information-dialog.h \
empathy-individual-linker.h \
empathy-individual-menu.h \
empathy-individual-store.h \
@@ -188,7 +194,8 @@ ui_DATA = \
empathy-log-window.ui \
empathy-chat.ui \
empathy-contact-selector-dialog.ui \
- empathy-search-bar.ui
+ empathy-search-bar.ui \
+ empathy-individual-widget.ui
empathy-gtk-marshal.list: $(libempathy_gtk_la_SOURCES) Makefile.am
$(AM_V_GEN)( cd $(srcdir) && \
diff --git a/libempathy-gtk/empathy-avatar-chooser.c b/libempathy-gtk/empathy-avatar-chooser.c
index 08dbf0337..678e7b9ea 100644
--- a/libempathy-gtk/empathy-avatar-chooser.c
+++ b/libempathy-gtk/empathy-avatar-chooser.c
@@ -567,7 +567,7 @@ avatar_chooser_maybe_convert_and_scale (EmpathyAvatarChooser *chooser,
/* Takes ownership of new_mime_type and best_image_data */
avatar = empathy_avatar_new ((guchar *) best_image_data,
- best_image_size, new_mime_type, NULL, NULL);
+ best_image_size, new_mime_type, NULL);
return avatar;
}
@@ -612,7 +612,7 @@ avatar_chooser_set_image_from_data (EmpathyAvatarChooser *chooser,
}
/* avatar takes ownership of data and mime_type */
- avatar = empathy_avatar_new ((guchar *) data, size, mime_type, NULL, NULL);
+ avatar = empathy_avatar_new ((guchar *) data, size, mime_type, NULL);
avatar_chooser_set_image (chooser, avatar, pixbuf, set_locally);
}
diff --git a/libempathy-gtk/empathy-chat.c b/libempathy-gtk/empathy-chat.c
index 45faea4fb..15da94c81 100644
--- a/libempathy-gtk/empathy-chat.c
+++ b/libempathy-gtk/empathy-chat.c
@@ -241,11 +241,11 @@ account_reconnected (EmpathyChat *chat,
switch (priv->handle_type) {
case TP_HANDLE_TYPE_CONTACT:
empathy_dispatcher_chat_with_contact_id (
- account, priv->id, EMPATHY_DISPATCHER_NON_USER_ACTION);
+ account, priv->id, TP_USER_ACTION_TIME_NOT_USER_ACTION);
break;
case TP_HANDLE_TYPE_ROOM:
empathy_dispatcher_join_muc (account, priv->id,
- EMPATHY_DISPATCHER_NON_USER_ACTION);
+ TP_USER_ACTION_TIME_NOT_USER_ACTION);
break;
case TP_HANDLE_TYPE_NONE:
case TP_HANDLE_TYPE_LIST:
diff --git a/libempathy-gtk/empathy-contact-widget.c b/libempathy-gtk/empathy-contact-widget.c
index de958c32e..da4e002e3 100644
--- a/libempathy-gtk/empathy-contact-widget.c
+++ b/libempathy-gtk/empathy-contact-widget.c
@@ -47,6 +47,7 @@
#include "empathy-account-chooser.h"
#include "empathy-avatar-chooser.h"
#include "empathy-avatar-image.h"
+#include "empathy-groups-widget.h"
#include "empathy-ui-utils.h"
#include "empathy-string-parser.h"
#include "empathy-kludge-label.h"
@@ -114,10 +115,7 @@ typedef struct
#endif
/* Groups */
- GtkWidget *vbox_groups;
- GtkWidget *entry_group;
- GtkWidget *button_group;
- GtkWidget *treeview_groups;
+ GtkWidget *groups_widget;
/* Details */
GtkWidget *vbox_details;
@@ -550,287 +548,27 @@ contact_widget_client_setup (EmpathyContactWidget *information)
}
static void
-contact_widget_cell_toggled (GtkCellRendererToggle *cell,
- gchar *path_string,
- EmpathyContactWidget *information)
-{
- GtkTreeView *view;
- GtkTreeModel *model;
- GtkListStore *store;
- GtkTreePath *path;
- GtkTreeIter iter;
- gboolean was_enabled;
- gchar *group;
-
- view = GTK_TREE_VIEW (information->treeview_groups);
- model = gtk_tree_view_get_model (view);
- store = GTK_LIST_STORE (model);
-
- path = gtk_tree_path_new_from_string (path_string);
-
- gtk_tree_model_get_iter (model, &iter, path);
- gtk_tree_model_get (model, &iter,
- COL_ENABLED, &was_enabled,
- COL_NAME, &group,
- -1);
-
- gtk_list_store_set (store, &iter, COL_ENABLED, !was_enabled, -1);
- gtk_tree_path_free (path);
-
- if (group != NULL)
- {
- empathy_contact_change_group (information->contact, group, !was_enabled);
- g_free (group);
- }
-}
-
-static void
-contact_widget_model_populate_columns (EmpathyContactWidget *information)
-{
- GtkTreeView *view;
- GtkTreeModel *model;
- GtkTreeViewColumn *column;
- GtkCellRenderer *renderer;
- guint col_offset;
-
- view = GTK_TREE_VIEW (information->treeview_groups);
- model = gtk_tree_view_get_model (view);
-
- renderer = gtk_cell_renderer_toggle_new ();
- g_signal_connect (renderer, "toggled",
- G_CALLBACK (contact_widget_cell_toggled), information);
-
- column = gtk_tree_view_column_new_with_attributes (_("Select"), renderer,
- "active", COL_ENABLED, NULL);
-
- gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
- gtk_tree_view_column_set_fixed_width (column, 50);
- gtk_tree_view_append_column (view, column);
-
- renderer = gtk_cell_renderer_text_new ();
- col_offset = gtk_tree_view_insert_column_with_attributes (view,
- -1, _("Group"),
- renderer,
- "text", COL_NAME,
- /* "editable", COL_EDITABLE, */
- NULL);
-
- g_object_set_data (G_OBJECT (renderer),
- "column", GINT_TO_POINTER (COL_NAME));
-
- column = gtk_tree_view_get_column (view, col_offset - 1);
- gtk_tree_view_column_set_sort_column_id (column, COL_NAME);
- gtk_tree_view_column_set_resizable (column,FALSE);
- gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
-}
-
-static void
-contact_widget_model_setup (EmpathyContactWidget *information)
-{
- GtkTreeView *view;
- GtkListStore *store;
- GtkTreeSelection *selection;
-
- view = GTK_TREE_VIEW (information->treeview_groups);
-
- store = gtk_list_store_new (COL_COUNT,
- G_TYPE_STRING, /* name */
- G_TYPE_BOOLEAN, /* enabled */
- G_TYPE_BOOLEAN); /* editable */
-
- gtk_tree_view_set_model (view, GTK_TREE_MODEL (store));
-
- selection = gtk_tree_view_get_selection (view);
- gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
-
- contact_widget_model_populate_columns (information);
-
- gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
- COL_NAME, GTK_SORT_ASCENDING);
-
- g_object_unref (store);
-}
-
-static void
-contact_widget_groups_populate_data (EmpathyContactWidget *information)
-{
- GtkTreeView *view;
- GtkListStore *store;
- GtkTreeIter iter;
- GList *my_groups, *l;
- GList *all_groups;
-
- view = GTK_TREE_VIEW (information->treeview_groups);
- store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
- gtk_list_store_clear (store);
-
- all_groups = empathy_contact_list_get_all_groups (
- EMPATHY_CONTACT_LIST (information->manager));
- my_groups = empathy_contact_list_get_groups (
- EMPATHY_CONTACT_LIST (information->manager),
- information->contact);
-
- for (l = all_groups; l; l = l->next)
- {
- const gchar *group_str;
- gboolean enabled;
-
- group_str = l->data;
-
- enabled = g_list_find_custom (my_groups,
- group_str, (GCompareFunc) strcmp) != NULL;
-
- gtk_list_store_append (store, &iter);
- gtk_list_store_set (store, &iter,
- COL_NAME, group_str,
- COL_EDITABLE, TRUE,
- COL_ENABLED, enabled,
- -1);
- }
-
- g_list_foreach (all_groups, (GFunc) g_free, NULL);
- g_list_foreach (my_groups, (GFunc) g_free, NULL);
- g_list_free (all_groups);
- g_list_free (my_groups);
-}
-
-static gboolean
-contact_widget_model_find_name_foreach (GtkTreeModel *model,
- GtkTreePath *path,
- GtkTreeIter *iter,
- FindName *data)
-{
- gchar *name;
-
- gtk_tree_model_get (model, iter,
- COL_NAME, &name,
- -1);
-
- if (!name)
- return FALSE;
-
- if (data->name && strcmp (data->name, name) == 0)
- {
- data->found = TRUE;
- data->found_iter = *iter;
-
- g_free (name);
-
- return TRUE;
- }
-
- g_free (name);
-
- return FALSE;
-}
-
-static gboolean
-contact_widget_model_find_name (EmpathyContactWidget *information,
- const gchar *name,
- GtkTreeIter *iter)
-{
- GtkTreeView *view;
- GtkTreeModel *model;
- FindName data;
-
- if (EMP_STR_EMPTY (name))
- return FALSE;
-
- data.information = information;
- data.name = name;
- data.found = FALSE;
-
- view = GTK_TREE_VIEW (information->treeview_groups);
- model = gtk_tree_view_get_model (view);
-
- gtk_tree_model_foreach (model,
- (GtkTreeModelForeachFunc) contact_widget_model_find_name_foreach,
- &data);
-
- if (data.found == TRUE)
- {
- *iter = data.found_iter;
- return TRUE;
- }
-
- return FALSE;
-}
-
-static void
-contact_widget_entry_group_changed_cb (GtkEditable *editable,
- EmpathyContactWidget *information)
-{
- GtkTreeIter iter;
- const gchar *group;
-
- group = gtk_entry_get_text (GTK_ENTRY (information->entry_group));
-
- if (contact_widget_model_find_name (information, group, &iter))
- gtk_widget_set_sensitive (GTK_WIDGET (information->button_group), FALSE);
- else
- gtk_widget_set_sensitive (GTK_WIDGET (information->button_group),
- !EMP_STR_EMPTY (group));
-}
-
-static void
-contact_widget_entry_group_activate_cb (GtkEntry *entry,
- EmpathyContactWidget *information)
-{
- gtk_widget_activate (GTK_WIDGET (information->button_group));
-}
-
-static void
-contact_widget_button_group_clicked_cb (GtkButton *button,
- EmpathyContactWidget *information)
-{
- GtkTreeView *view;
- GtkListStore *store;
- GtkTreeIter iter;
- const gchar *group;
-
- view = GTK_TREE_VIEW (information->treeview_groups);
- store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
-
- group = gtk_entry_get_text (GTK_ENTRY (information->entry_group));
-
- gtk_list_store_append (store, &iter);
- gtk_list_store_set (store, &iter,
- COL_NAME, group,
- COL_ENABLED, TRUE,
- -1);
-
- empathy_contact_change_group (information->contact, group, TRUE);
-}
-
-static void
-contact_widget_groups_notify_cb (EmpathyContactWidget *information)
-{
- /* FIXME: not implemented */
-}
-
-static void
-contact_widget_groups_setup (EmpathyContactWidget *information)
-{
- if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_GROUPS)
- {
- contact_widget_model_setup (information);
- }
-}
-
-static void
contact_widget_groups_update (EmpathyContactWidget *information)
{
if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_GROUPS &&
- information->contact)
+ information->contact != NULL)
{
- g_signal_connect_swapped (information->contact, "notify::groups",
- G_CALLBACK (contact_widget_groups_notify_cb), information);
- contact_widget_groups_populate_data (information);
+ FolksPersona *persona =
+ empathy_contact_get_persona (information->contact);
+
+ if (FOLKS_IS_GROUPS (persona))
+ {
+ empathy_groups_widget_set_groupable (
+ EMPATHY_GROUPS_WIDGET (information->groups_widget),
+ FOLKS_GROUPS (persona));
+ gtk_widget_show (information->groups_widget);
- gtk_widget_show (information->vbox_groups);
+ return;
+ }
}
- else
- gtk_widget_hide (information->vbox_groups);
+
+ /* In case of failure */
+ gtk_widget_hide (information->groups_widget);
}
/* Converts the Location's GHashTable's key to a user readable string */
@@ -1421,8 +1159,6 @@ contact_widget_remove_contact (EmpathyContactWidget *information)
contact_widget_presence_notify_cb, information);
g_signal_handlers_disconnect_by_func (information->contact,
contact_widget_avatar_notify_cb, information);
- g_signal_handlers_disconnect_by_func (information->contact,
- contact_widget_groups_notify_cb, information);
tp_contact = empathy_contact_get_tp_contact (information->contact);
if (tp_contact != NULL)
@@ -1871,10 +1607,7 @@ empathy_contact_widget_new (EmpathyContact *contact,
#ifdef HAVE_LIBCHAMPLAIN
"viewport_map", &information->viewport_map,
#endif
- "vbox_groups", &information->vbox_groups,
- "entry_group", &information->entry_group,
- "button_group", &information->button_group,
- "treeview_groups", &information->treeview_groups,
+ "groups_widget", &information->groups_widget,
"vbox_details", &information->vbox_details,
"table_details", &information->table_details,
"hbox_details_requested", &information->hbox_details_requested,
@@ -1886,9 +1619,6 @@ empathy_contact_widget_new (EmpathyContact *contact,
empathy_builder_connect (gui, information,
"vbox_contact_widget", "destroy", contact_widget_destroy_cb,
- "entry_group", "changed", contact_widget_entry_group_changed_cb,
- "entry_group", "activate", contact_widget_entry_group_activate_cb,
- "button_group", "clicked", contact_widget_button_group_clicked_cb,
NULL);
information->table_location = NULL;
@@ -1900,7 +1630,6 @@ empathy_contact_widget_new (EmpathyContact *contact,
/* Create widgets */
contact_widget_contact_setup (information);
- contact_widget_groups_setup (information);
contact_widget_details_setup (information);
contact_widget_client_setup (information);
diff --git a/libempathy-gtk/empathy-contact-widget.ui b/libempathy-gtk/empathy-contact-widget.ui
index 0792dcff9..acfad6a16 100644
--- a/libempathy-gtk/empathy-contact-widget.ui
+++ b/libempathy-gtk/empathy-contact-widget.ui
@@ -172,109 +172,7 @@
</packing>
</child>
<child>
- <object class="GtkVBox" id="vbox_groups">
- <property name="spacing">6</property>
- <child>
- <object class="GtkLabel" id="label672">
- <property name="visible">True</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes">Groups</property>
- <attributes>
- <attribute name="weight" value="bold"/>
- </attributes>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkAlignment" id="alignment33">
- <property name="visible">True</property>
- <property name="left_padding">12</property>
- <child>
- <object class="GtkVBox" id="vbox224">
- <property name="visible">True</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkLabel" id="label679">
- <property name="visible">True</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes">Select the groups you want this contact to appear in. Note that you can select more than one group or no groups.</property>
- <property name="use_markup">True</property>
- <property name="wrap">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkHBox" id="hbox188">
- <property name="visible">True</property>
- <property name="spacing">12</property>
- <child>
- <object class="GtkEntry" id="entry_group">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- </object>
- <packing>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="button_group">
- <property name="label" translatable="yes">_Add Group</property>
- <property name="visible">True</property>
- <property name="sensitive">False</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="use_underline">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkScrolledWindow" id="scrolledwindow17">
- <property name="height_request">100</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="hscrollbar_policy">never</property>
- <property name="vscrollbar_policy">automatic</property>
- <property name="shadow_type">in</property>
- <child>
- <object class="GtkTreeView" id="treeview_groups">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="headers_visible">False</property>
- <property name="enable_search">False</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="position">2</property>
- </packing>
- </child>
- </object>
- </child>
- </object>
- <packing>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
+ <object class="EmpathyGroupsWidget" id="groups_widget"/>
<packing>
<property name="position">2</property>
</packing>
diff --git a/libempathy-gtk/empathy-groups-widget.c b/libempathy-gtk/empathy-groups-widget.c
new file mode 100644
index 000000000..98c2a6a4b
--- /dev/null
+++ b/libempathy-gtk/empathy-groups-widget.c
@@ -0,0 +1,600 @@
+/*
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+#include <telepathy-glib/util.h>
+
+#include <folks/folks.h>
+
+#include <libempathy/empathy-utils.h>
+#include <libempathy/empathy-contact-manager.h>
+
+#include "empathy-groups-widget.h"
+#include "empathy-ui-utils.h"
+
+/**
+ * SECTION:empathy-groups-widget
+ * @title:EmpathyGroupsWidget
+ * @short_description: A widget used to edit the groups of a #FolksGroups
+ * @include: libempathy-gtk/empathy-groups-widget.h
+ *
+ * #EmpathyGroupsWidget is a widget which lists the groups of a #FolksGroups
+ * (i.e. a #FolksPersona or a #FolksIndividual) and allows them to be added and
+ * removed.
+ */
+
+/**
+ * EmpathyGroupsWidget:
+ * @parent: parent object
+ *
+ * Widget which displays and allows editing of the groups of a #FolksGroups
+ * (i.e. a #FolksPersona or #FolksIndividual).
+ */
+
+/* Delay before updating the widget when the id entry changed (seconds) */
+#define ID_CHANGED_TIMEOUT 1
+
+#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyGroupsWidget)
+
+typedef struct
+{
+ /* The object we're actually changing the groups of */
+ FolksGroups *groupable; /* owned */
+ GtkListStore *group_store; /* owned */
+
+ GtkWidget *add_group_entry; /* child widget */
+ GtkWidget *add_group_button; /* child widget */
+} EmpathyGroupsWidgetPriv;
+
+enum {
+ PROP_GROUPABLE = 1,
+};
+
+enum {
+ COL_NAME,
+ COL_ENABLED,
+ COL_EDITABLE
+};
+#define NUM_COLUMNS COL_EDITABLE + 1
+
+G_DEFINE_TYPE (EmpathyGroupsWidget, empathy_groups_widget, GTK_TYPE_BOX);
+
+typedef struct
+{
+ EmpathyGroupsWidget *widget;
+ const gchar *name;
+ gboolean found;
+ GtkTreeIter found_iter;
+} FindNameData;
+
+static gboolean
+model_find_name_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ FindNameData *data)
+{
+ gchar *name;
+
+ gtk_tree_model_get (model, iter,
+ COL_NAME, &name,
+ -1);
+
+ if (name != NULL && strcmp (data->name, name) == 0)
+ {
+ data->found = TRUE;
+ data->found_iter = *iter;
+
+ g_free (name);
+
+ return TRUE;
+ }
+
+ g_free (name);
+
+ return FALSE;
+}
+
+static gboolean
+model_find_name (EmpathyGroupsWidget *self,
+ const gchar *name,
+ GtkTreeIter *iter)
+{
+ EmpathyGroupsWidgetPriv *priv = GET_PRIV (self);
+ FindNameData data;
+
+ if (EMP_STR_EMPTY (name))
+ return FALSE;
+
+ data.widget = self;
+ data.name = name;
+ data.found = FALSE;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (priv->group_store),
+ (GtkTreeModelForeachFunc) model_find_name_foreach, &data);
+
+ if (data.found == TRUE)
+ {
+ *iter = data.found_iter;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+populate_data (EmpathyGroupsWidget *self)
+{
+ EmpathyGroupsWidgetPriv *priv = GET_PRIV (self);
+ EmpathyContactManager *manager;
+ GtkTreeIter iter;
+ GHashTable *my_groups;
+ GList *all_groups, *l;
+
+ /* Remove the old groups */
+ gtk_list_store_clear (priv->group_store);
+
+ /* FIXME: We have to get the whole group list from EmpathyContactManager, as
+ * libfolks hasn't grown API to get the whole group list yet. (bgo#627398) */
+ manager = empathy_contact_manager_dup_singleton ();
+ all_groups = empathy_contact_list_get_all_groups (
+ EMPATHY_CONTACT_LIST (manager));
+ g_object_unref (manager);
+
+ /* Get the list of groups that this #FolksGroups is currently in */
+ my_groups = folks_groups_get_groups (priv->groupable);
+
+ for (l = all_groups; l != NULL; l = l->next)
+ {
+ const gchar *group_str = l->data;
+ gboolean enabled;
+
+ enabled = GPOINTER_TO_UINT (g_hash_table_lookup (my_groups, group_str));
+
+ gtk_list_store_append (priv->group_store, &iter);
+ gtk_list_store_set (priv->group_store, &iter,
+ COL_NAME, group_str,
+ COL_EDITABLE, TRUE,
+ COL_ENABLED, enabled,
+ -1);
+
+ g_free (l->data);
+ }
+
+ g_list_free (all_groups);
+}
+
+static void
+add_group_entry_changed_cb (GtkEditable *editable,
+ EmpathyGroupsWidget *self)
+{
+ EmpathyGroupsWidgetPriv *priv = GET_PRIV (self);
+ GtkTreeIter iter;
+ const gchar *group;
+
+ group = gtk_entry_get_text (GTK_ENTRY (priv->add_group_entry));
+
+ if (model_find_name (self, group, &iter))
+ {
+ gtk_widget_set_sensitive (GTK_WIDGET (priv->add_group_button), FALSE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (GTK_WIDGET (priv->add_group_button),
+ !EMP_STR_EMPTY (group));
+ }
+}
+
+static void
+add_group_entry_activate_cb (GtkEntry *entry,
+ EmpathyGroupsWidget *self)
+{
+ gtk_widget_activate (GTK_WIDGET (GET_PRIV (self)->add_group_button));
+}
+
+static void
+change_group_cb (FolksGroups *groupable,
+ GAsyncResult *async_result,
+ EmpathyGroupsWidget *self)
+{
+ GError *error = NULL;
+
+ folks_groups_change_group_finish (groupable, async_result, &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Failed to change group: %s", error->message);
+ g_clear_error (&error);
+ }
+}
+
+static void
+add_group_button_clicked_cb (GtkButton *button,
+ EmpathyGroupsWidget *self)
+{
+ EmpathyGroupsWidgetPriv *priv = GET_PRIV (self);
+ GtkTreeIter iter;
+ const gchar *group;
+
+ group = gtk_entry_get_text (GTK_ENTRY (priv->add_group_entry));
+
+ gtk_list_store_append (priv->group_store, &iter);
+ gtk_list_store_set (priv->group_store, &iter,
+ COL_NAME, group,
+ COL_ENABLED, TRUE,
+ -1);
+
+ folks_groups_change_group (priv->groupable, group, TRUE,
+ (GAsyncReadyCallback) change_group_cb, self);
+}
+
+static void
+cell_toggled_cb (GtkCellRendererToggle *cell,
+ const gchar *path_string,
+ EmpathyGroupsWidget *self)
+{
+ EmpathyGroupsWidgetPriv *priv = GET_PRIV (self);
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gboolean was_enabled;
+ gchar *group;
+
+ path = gtk_tree_path_new_from_string (path_string);
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->group_store), &iter,
+ path);
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->group_store), &iter,
+ COL_ENABLED, &was_enabled,
+ COL_NAME, &group,
+ -1);
+
+ gtk_list_store_set (priv->group_store, &iter,
+ COL_ENABLED, !was_enabled,
+ -1);
+
+ gtk_tree_path_free (path);
+
+ if (group != NULL)
+ {
+ folks_groups_change_group (priv->groupable, group, !was_enabled,
+ (GAsyncReadyCallback) change_group_cb, self);
+ g_free (group);
+ }
+}
+
+
+static void
+groupable_group_changed_cb (FolksGroups *groups,
+ const gchar *group,
+ gboolean is_member,
+ EmpathyGroupsWidget *self)
+{
+ EmpathyGroupsWidgetPriv *priv = GET_PRIV (self);
+ GtkTreeIter iter;
+
+ if (model_find_name (self, group, &iter) == TRUE)
+ {
+ gtk_list_store_set (priv->group_store, &iter,
+ COL_ENABLED, is_member,
+ -1);
+ }
+}
+
+static void
+set_up (EmpathyGroupsWidget *self)
+{
+ EmpathyGroupsWidgetPriv *priv;
+ GtkWidget *label, *alignment;
+ GtkBox *vbox, *hbox;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ guint col_offset;
+ GtkScrolledWindow *scrolled_window;
+ gchar *markup;
+
+ priv = GET_PRIV (self);
+
+ /* Set up ourself */
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
+ GTK_ORIENTATION_VERTICAL);
+ gtk_box_set_spacing (GTK_BOX (self), 6);
+
+ /* Create our child widgets */
+ label = gtk_label_new (NULL);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+ markup = g_strdup_printf ("<b>%s</b>", _("Groups"));
+ gtk_label_set_markup (GTK_LABEL (label), markup);
+ g_free (markup);
+
+ gtk_box_pack_start (GTK_BOX (self), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 12, 0);
+
+ vbox = GTK_BOX (gtk_vbox_new (FALSE, 6));
+
+ label = gtk_label_new (_("Select the groups you want this contact to appear "
+ "in. Note that you can select more than one group or no groups."));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+
+ gtk_box_pack_start (vbox, label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ hbox = GTK_BOX (gtk_hbox_new (FALSE, 12));
+
+ priv->add_group_entry = gtk_entry_new ();
+ g_signal_connect (priv->add_group_entry, "changed",
+ (GCallback) add_group_entry_changed_cb, self);
+ g_signal_connect (priv->add_group_entry, "activate",
+ (GCallback) add_group_entry_activate_cb, self);
+
+ gtk_box_pack_start (hbox, priv->add_group_entry, TRUE, TRUE, 0);
+ gtk_widget_show (priv->add_group_entry);
+
+ priv->add_group_button = gtk_button_new_with_mnemonic (_("_Add Group"));
+ gtk_widget_set_sensitive (priv->add_group_button, FALSE);
+ gtk_widget_set_receives_default (priv->add_group_button, TRUE);
+ g_signal_connect (priv->add_group_button, "clicked",
+ (GCallback) add_group_button_clicked_cb, self);
+
+ gtk_box_pack_start (hbox, priv->add_group_button, FALSE, FALSE, 0);
+ gtk_widget_show (priv->add_group_button);
+
+ gtk_box_pack_start (vbox, GTK_WIDGET (hbox), FALSE, FALSE, 0);
+ gtk_widget_show (GTK_WIDGET (hbox));
+
+ scrolled_window = GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL));
+ gtk_scrolled_window_set_policy (scrolled_window, GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (scrolled_window, GTK_SHADOW_IN);
+ gtk_widget_set_size_request (GTK_WIDGET (scrolled_window), -1, 100);
+
+ priv->group_store = gtk_list_store_new (NUM_COLUMNS,
+ G_TYPE_STRING, /* name */
+ G_TYPE_BOOLEAN, /* enabled */
+ G_TYPE_BOOLEAN); /* editable */
+
+ tree_view = GTK_TREE_VIEW (gtk_tree_view_new_with_model (
+ GTK_TREE_MODEL (priv->group_store)));
+ gtk_tree_view_set_headers_visible (tree_view, FALSE);
+ gtk_tree_view_set_enable_search (tree_view, FALSE);
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+ renderer = gtk_cell_renderer_toggle_new ();
+ g_signal_connect (renderer, "toggled", (GCallback) cell_toggled_cb, self);
+
+ column = gtk_tree_view_column_new_with_attributes (_("Select"), renderer,
+ "active", COL_ENABLED,
+ NULL);
+
+ gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
+ gtk_tree_view_column_set_fixed_width (column, 50);
+ gtk_tree_view_append_column (tree_view, column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ col_offset = gtk_tree_view_insert_column_with_attributes (tree_view,
+ -1, _("Group"),
+ renderer,
+ "text", COL_NAME,
+ /* "editable", COL_EDITABLE, */
+ NULL);
+
+ column = gtk_tree_view_get_column (tree_view, col_offset - 1);
+ gtk_tree_view_column_set_sort_column_id (column, COL_NAME);
+ gtk_tree_view_column_set_resizable (column, FALSE);
+ gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->group_store),
+ COL_NAME, GTK_SORT_ASCENDING);
+
+ gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (tree_view));
+ gtk_widget_show (GTK_WIDGET (tree_view));
+
+ gtk_box_pack_start (vbox, GTK_WIDGET (scrolled_window), TRUE, TRUE, 0);
+ gtk_widget_show (GTK_WIDGET (scrolled_window));
+
+ gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (vbox));
+ gtk_widget_show (GTK_WIDGET (vbox));
+
+ gtk_box_pack_start (GTK_BOX (self), alignment, TRUE, TRUE, 0);
+ gtk_widget_show (alignment);
+}
+
+static void
+empathy_groups_widget_init (EmpathyGroupsWidget *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EMPATHY_TYPE_GROUPS_WIDGET, EmpathyGroupsWidgetPriv);
+
+ set_up (self);
+}
+
+static void
+get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EmpathyGroupsWidgetPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id)
+ {
+ case PROP_GROUPABLE:
+ g_value_set_object (value, priv->groupable);
+ 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)
+{
+ EmpathyGroupsWidgetPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id)
+ {
+ case PROP_GROUPABLE:
+ empathy_groups_widget_set_groupable (EMPATHY_GROUPS_WIDGET (object),
+ g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ EmpathyGroupsWidgetPriv *priv = GET_PRIV (object);
+
+ tp_clear_object (&priv->groupable);
+ tp_clear_object (&priv->group_store);
+
+ G_OBJECT_CLASS (empathy_groups_widget_parent_class)->dispose (object);
+}
+
+static void
+empathy_groups_widget_class_init (EmpathyGroupsWidgetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->dispose = dispose;
+
+ /**
+ * EmpathyGroupsWidget:groupable:
+ *
+ * The #FolksGroups whose group membership is to be edited by the
+ * #EmpathyGroupsWidget.
+ */
+ g_object_class_install_property (object_class, PROP_GROUPABLE,
+ g_param_spec_object ("groupable",
+ "Groupable",
+ "The #FolksGroups whose groups are being edited.",
+ FOLKS_TYPE_GROUPS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (object_class, sizeof (EmpathyGroupsWidgetPriv));
+}
+
+/**
+ * empathy_groups_widget_new:
+ * @groupable: a #FolksGroups, or %NULL
+ *
+ * Creates a new #EmpathyGroupsWidget to edit the groups of the given
+ * @groupable.
+ *
+ * Return value: a new #EmpathyGroupsWidget
+ */
+GtkWidget *
+empathy_groups_widget_new (FolksGroups *groupable)
+{
+ g_return_val_if_fail (groupable == NULL || FOLKS_IS_GROUPS (groupable),
+ NULL);
+
+ return GTK_WIDGET (g_object_new (EMPATHY_TYPE_GROUPS_WIDGET,
+ "groupable", groupable,
+ NULL));
+}
+
+/**
+ * empathy_groups_widget_get_groupable:
+ * @self: an #EmpathyGroupsWidget
+ *
+ * Get the #FolksGroups whose group membership is being edited by the
+ * #EmpathyGroupsWidget.
+ *
+ * Returns: the #FolksGroups associated with @widget, or %NULL
+ */
+FolksGroups *
+empathy_groups_widget_get_groupable (EmpathyGroupsWidget *self)
+{
+ g_return_val_if_fail (EMPATHY_IS_GROUPS_WIDGET (self), NULL);
+
+ return GET_PRIV (self)->groupable;
+}
+
+/**
+ * empathy_groups_widget_set_groupable:
+ * @self: an #EmpathyGroupsWidget
+ * @groupable: the #FolksGroups whose membership is to be edited, or %NULL
+ *
+ * Change the #FolksGroups whose group membership is to be edited by the
+ * #EmpathyGroupsWidget.
+ */
+void
+empathy_groups_widget_set_groupable (EmpathyGroupsWidget *self,
+ FolksGroups *groupable)
+{
+ EmpathyGroupsWidgetPriv *priv;
+
+ g_return_if_fail (EMPATHY_IS_GROUPS_WIDGET (self));
+ g_return_if_fail (groupable == NULL || FOLKS_IS_GROUPS (groupable));
+
+ priv = GET_PRIV (self);
+
+ if (groupable == priv->groupable)
+ return;
+
+ if (priv->groupable != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (priv->groupable,
+ groupable_group_changed_cb, self);
+ }
+
+ tp_clear_object (&priv->groupable);
+
+ if (groupable != NULL)
+ {
+ priv->groupable = g_object_ref (groupable);
+
+ g_signal_connect (priv->groupable, "group-changed",
+ (GCallback) groupable_group_changed_cb, self);
+
+ populate_data (self);
+ }
+
+ g_object_notify (G_OBJECT (self), "groupable");
+}
diff --git a/libempathy-gtk/empathy-groups-widget.h b/libempathy-gtk/empathy-groups-widget.h
new file mode 100644
index 000000000..120d45e7e
--- /dev/null
+++ b/libempathy-gtk/empathy-groups-widget.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ */
+
+#ifndef __EMPATHY_GROUPS_WIDGET_H__
+#define __EMPATHY_GROUPS_WIDGET_H__
+
+#include <gtk/gtk.h>
+
+#include <folks/folks.h>
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_GROUPS_WIDGET (empathy_groups_widget_get_type ())
+#define EMPATHY_GROUPS_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), \
+ EMPATHY_TYPE_GROUPS_WIDGET, EmpathyGroupsWidget))
+#define EMPATHY_GROUPS_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), \
+ EMPATHY_TYPE_GROUPS_WIDGET, EmpathyGroupsWidgetClass))
+#define EMPATHY_IS_GROUPS_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), \
+ EMPATHY_TYPE_GROUPS_WIDGET))
+#define EMPATHY_IS_GROUPS_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), \
+ EMPATHY_TYPE_GROUPS_WIDGET))
+#define EMPATHY_GROUPS_WIDGET_GET_CLASS(o) ( \
+ G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_GROUPS_WIDGET, \
+ EmpathyGroupsWidgetClass))
+
+typedef struct {
+ GtkBox parent;
+
+ /*<private>*/
+ gpointer priv;
+} EmpathyGroupsWidget;
+
+typedef struct {
+ GtkBoxClass parent_class;
+} EmpathyGroupsWidgetClass;
+
+GType empathy_groups_widget_get_type (void) G_GNUC_CONST;
+
+GtkWidget * empathy_groups_widget_new (FolksGroups *groupable);
+
+FolksGroups * empathy_groups_widget_get_groupable (
+ EmpathyGroupsWidget *self);
+void empathy_groups_widget_set_groupable (EmpathyGroupsWidget *self,
+ FolksGroups *groupable);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_GROUPS_WIDGET_H__ */
diff --git a/libempathy-gtk/empathy-individual-edit-dialog.c b/libempathy-gtk/empathy-individual-edit-dialog.c
new file mode 100644
index 000000000..4b5e2dfd7
--- /dev/null
+++ b/libempathy-gtk/empathy-individual-edit-dialog.c
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ * Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+#include <telepathy-glib/util.h>
+#include <folks/folks.h>
+#include <folks/folks-telepathy.h>
+
+#include <libempathy/empathy-individual-manager.h>
+#include <libempathy/empathy-utils.h>
+
+#include "empathy-individual-edit-dialog.h"
+#include "empathy-individual-widget.h"
+#include "empathy-ui-utils.h"
+
+#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualEditDialog)
+
+typedef struct {
+ FolksIndividual *individual; /* owned */
+} EmpathyIndividualEditDialogPriv;
+
+enum {
+ PROP_INDIVIDUAL = 1,
+};
+
+/* Edit dialogs currently open.
+ * Each dialog contains a referenced pointer to its Individual */
+static GList *edit_dialogs = NULL;
+
+G_DEFINE_TYPE (EmpathyIndividualEditDialog, empathy_individual_edit_dialog,
+ GTK_TYPE_DIALOG);
+
+/* Fairly arbitrary response ID for the "Unlink" button */
+#define RESPONSE_UNLINK 5
+
+static void
+individual_dialogs_response_cb (GtkDialog *dialog,
+ gint response,
+ GList **dialogs)
+{
+ if (response == RESPONSE_UNLINK)
+ {
+ EmpathyIndividualEditDialogPriv *priv = GET_PRIV (dialog);
+ EmpathyIndividualManager *manager =
+ empathy_individual_manager_dup_singleton ();
+
+ empathy_individual_manager_unlink_individual (manager, priv->individual);
+
+ g_object_unref (manager);
+ }
+
+ *dialogs = g_list_remove (*dialogs, dialog);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static gint
+individual_dialogs_find (GObject *object,
+ FolksIndividual *individual)
+{
+ EmpathyIndividualEditDialogPriv *priv = GET_PRIV (object);
+
+ return individual != priv->individual;
+}
+
+void
+empathy_individual_edit_dialog_show (FolksIndividual *individual,
+ GtkWindow *parent)
+{
+ GtkWidget *dialog;
+ GList *l;
+
+ g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
+ g_return_if_fail (parent == NULL || GTK_IS_WINDOW (parent));
+
+ l = g_list_find_custom (edit_dialogs, individual,
+ (GCompareFunc) individual_dialogs_find);
+
+ if (l != NULL)
+ {
+ gtk_window_present (GTK_WINDOW (l->data));
+ return;
+ }
+
+ /* Create dialog */
+ dialog = g_object_new (EMPATHY_TYPE_INDIVIDUAL_EDIT_DIALOG,
+ "individual", individual,
+ NULL);
+
+ edit_dialogs = g_list_prepend (edit_dialogs, dialog);
+ gtk_widget_show (dialog);
+}
+
+static void
+individual_edit_dialog_set_individual (
+ EmpathyIndividualEditDialog *dialog,
+ FolksIndividual *individual)
+{
+ EmpathyIndividualEditDialogPriv *priv;
+ GtkWidget *individual_widget;
+ GList *personas, *l;
+ guint num_personas = 0;
+
+ g_return_if_fail (EMPATHY_INDIVIDUAL_EDIT_DIALOG (dialog));
+ g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
+
+ priv = GET_PRIV (dialog);
+
+ /* Individual info widget */
+ individual_widget = empathy_individual_widget_new (individual,
+ EMPATHY_INDIVIDUAL_WIDGET_EDIT_ALIAS |
+ EMPATHY_INDIVIDUAL_WIDGET_EDIT_GROUPS |
+ EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE);
+ gtk_container_set_border_width (GTK_CONTAINER (individual_widget), 8);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (
+ GTK_DIALOG (dialog))), individual_widget, TRUE, TRUE, 0);
+ gtk_widget_show (individual_widget);
+
+ /* Count how many Telepathy personas we have, to see whether we can
+ * unlink */
+ personas = folks_individual_get_personas (individual);
+ for (l = personas; l != NULL; l = l->next)
+ {
+ if (TPF_IS_PERSONA (l->data))
+ num_personas++;
+ }
+
+ priv->individual = g_object_ref (individual);
+
+ /* Only make the "Unlink" button sensitive if we have enough personas */
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), RESPONSE_UNLINK,
+ (num_personas > 1) ? TRUE : FALSE);
+}
+
+static void
+individual_edit_dialog_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EmpathyIndividualEditDialogPriv *priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_INDIVIDUAL:
+ g_value_set_object (value, priv->individual);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+individual_edit_dialog_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EmpathyIndividualEditDialog *dialog =
+ EMPATHY_INDIVIDUAL_EDIT_DIALOG (object);
+
+ switch (param_id) {
+ case PROP_INDIVIDUAL:
+ individual_edit_dialog_set_individual (dialog,
+ FOLKS_INDIVIDUAL (g_value_get_object (value)));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+individual_edit_dialog_dispose (GObject *object)
+{
+ EmpathyIndividualEditDialogPriv *priv = GET_PRIV (object);
+
+ if (priv->individual != NULL)
+ g_object_unref (priv->individual);
+ priv->individual = NULL;
+
+ G_OBJECT_CLASS (
+ empathy_individual_edit_dialog_parent_class)->dispose (object);
+}
+
+static void
+empathy_individual_edit_dialog_class_init (
+ EmpathyIndividualEditDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = individual_edit_dialog_get_property;
+ object_class->set_property = individual_edit_dialog_set_property;
+ object_class->dispose = individual_edit_dialog_dispose;
+
+ g_object_class_install_property (object_class,
+ PROP_INDIVIDUAL,
+ g_param_spec_object ("individual",
+ "Folks Individual",
+ "Folks Individual to edit using the dialog.",
+ FOLKS_TYPE_INDIVIDUAL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (object_class,
+ sizeof (EmpathyIndividualEditDialogPriv));
+}
+
+static void
+empathy_individual_edit_dialog_init (
+ EmpathyIndividualEditDialog *dialog)
+{
+ GtkWidget *button, *action_area;
+ EmpathyIndividualEditDialogPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (
+ dialog, EMPATHY_TYPE_INDIVIDUAL_EDIT_DIALOG,
+ EmpathyIndividualEditDialogPriv);
+
+ dialog->priv = priv;
+ priv->individual = NULL;
+
+ gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Edit Contact Information"));
+
+ /* Unlink button */
+ button = gtk_button_new_with_mnemonic (
+ C_("Unlink individual (button)", "_Unlink"));
+ gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, RESPONSE_UNLINK);
+
+ action_area = gtk_dialog_get_action_area (GTK_DIALOG (dialog));
+ gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (action_area), button,
+ TRUE);
+
+ gtk_widget_show (button);
+
+ /* Close button */
+ button = gtk_button_new_with_label (GTK_STOCK_CLOSE);
+ gtk_button_set_use_stock (GTK_BUTTON (button), TRUE);
+ gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
+ GTK_RESPONSE_CLOSE);
+ gtk_widget_set_can_default (button, TRUE);
+ gtk_window_set_default (GTK_WINDOW (dialog), button);
+ gtk_widget_show (button);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (individual_dialogs_response_cb), &edit_dialogs);
+}
diff --git a/libempathy-gtk/empathy-individual-edit-dialog.h b/libempathy-gtk/empathy-individual-edit-dialog.h
new file mode 100644
index 000000000..4f580fb03
--- /dev/null
+++ b/libempathy-gtk/empathy-individual-edit-dialog.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Philip Withnall <philip.withnall@collabora.co.uk>
+ * Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+#ifndef __EMPATHY_INDIVIDUAL_EDIT_DIALOG_H__
+#define __EMPATHY_INDIVIDUAL_EDIT_DIALOG_H__
+
+#include <gtk/gtk.h>
+
+#include <folks/folks.h>
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_INDIVIDUAL_EDIT_DIALOG (empathy_individual_edit_dialog_get_type ())
+#define EMPATHY_INDIVIDUAL_EDIT_DIALOG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_INDIVIDUAL_EDIT_DIALOG, EmpathyIndividualEditDialog))
+#define EMPATHY_INDIVIDUAL_EDIT_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), EMPATHY_TYPE_INDIVIDUAL_EDIT_DIALOG, EmpathyIndividualEditDialogClass))
+#define EMPATHY_IS_INDIVIDUAL_EDIT_DIALOG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_INDIVIDUAL_EDIT_DIALOG))
+#define EMPATHY_IS_INDIVIDUAL_EDIT_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_INDIVIDUAL_EDIT_DIALOG))
+#define EMPATHY_INDIVIDUAL_EDIT_DIALOG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_INDIVIDUAL_EDIT_DIALOG, EmpathyIndividualEditDialogClass))
+
+typedef struct _EmpathyIndividualEditDialog EmpathyIndividualEditDialog;
+typedef struct _EmpathyIndividualEditDialogClass EmpathyIndividualEditDialogClass;
+
+struct _EmpathyIndividualEditDialog {
+ GtkDialog parent;
+
+ /*<private>*/
+ gpointer priv;
+};
+
+struct _EmpathyIndividualEditDialogClass {
+ GtkDialogClass parent_class;
+};
+
+GType empathy_individual_edit_dialog_get_type (void) G_GNUC_CONST;
+
+void empathy_individual_edit_dialog_show (FolksIndividual *individual,
+ GtkWindow *parent);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_INDIVIDUAL_EDIT_DIALOG_H__ */
diff --git a/libempathy-gtk/empathy-individual-information-dialog.c b/libempathy-gtk/empathy-individual-information-dialog.c
new file mode 100644
index 000000000..486770684
--- /dev/null
+++ b/libempathy-gtk/empathy-individual-information-dialog.c
@@ -0,0 +1,276 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Philip Withnall <philip.withnall@collabora.co.uk>
+ * Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+#include <telepathy-glib/util.h>
+#include <folks/folks.h>
+#include <folks/folks-telepathy.h>
+
+#include <libempathy/empathy-individual-manager.h>
+#include <libempathy/empathy-utils.h>
+
+#include "empathy-individual-information-dialog.h"
+#include "empathy-individual-widget.h"
+#include "empathy-ui-utils.h"
+
+#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualInformationDialog)
+typedef struct {
+ FolksIndividual *individual;
+} EmpathyIndividualInformationDialogPriv;
+
+enum {
+ PROP_0,
+ PROP_INDIVIDUAL,
+};
+
+/* Info dialogs currently open.
+ * Each dialog contains a referenced pointer to its Individual */
+static GList *information_dialogs = NULL;
+
+G_DEFINE_TYPE (EmpathyIndividualInformationDialog,
+ empathy_individual_information_dialog, GTK_TYPE_DIALOG);
+
+static void
+individual_dialogs_response_cb (GtkDialog *dialog,
+ gint response,
+ GList **dialogs)
+{
+ *dialogs = g_list_remove (*dialogs, dialog);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static gint
+individual_dialogs_find (GObject *object,
+ FolksIndividual *individual)
+{
+ EmpathyIndividualInformationDialogPriv *priv = GET_PRIV (object);
+
+ return individual != priv->individual;
+}
+
+void
+empathy_individual_information_dialog_show (FolksIndividual *individual,
+ GtkWindow *parent)
+{
+ GtkWidget *dialog;
+ GList *l;
+
+ g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
+ g_return_if_fail (parent == NULL || GTK_IS_WINDOW (parent));
+
+ l = g_list_find_custom (information_dialogs, individual,
+ (GCompareFunc) individual_dialogs_find);
+
+ if (l != NULL)
+ {
+ gtk_window_present (GTK_WINDOW (l->data));
+ return;
+ }
+
+ /* Create dialog */
+ dialog = g_object_new (EMPATHY_TYPE_INDIVIDUAL_INFORMATION_DIALOG,
+ "individual", individual,
+ NULL);
+
+ information_dialogs = g_list_prepend (information_dialogs, dialog);
+ gtk_widget_show (dialog);
+}
+
+static void
+individual_information_dialog_set_individual (
+ EmpathyIndividualInformationDialog *dialog,
+ FolksIndividual *individual)
+{
+ EmpathyIndividualInformationDialogPriv *priv;
+ GtkWidget *individual_widget;
+ GtkBox *content_area;
+ GList *personas, *l;
+ guint num_personas = 0;
+
+ g_return_if_fail (EMPATHY_INDIVIDUAL_INFORMATION_DIALOG (dialog));
+ g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
+
+ priv = GET_PRIV (dialog);
+
+ gtk_window_set_title (GTK_WINDOW (dialog),
+ folks_individual_get_alias (individual));
+
+ content_area = GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog)));
+
+ /* Determine how many personas we have, because we only want to display the
+ * label if we have more than one persona. */
+ personas = folks_individual_get_personas (individual);
+ for (l = personas; l != NULL; l = l->next)
+ {
+ if (TPF_IS_PERSONA (l->data))
+ num_personas++;
+ }
+
+ /* Label */
+ if (num_personas > 1)
+ {
+ gchar *label_string;
+ GtkWidget *label;
+
+ /* Translators: the heading at the top of the Information dialogue */
+ label_string = g_strdup_printf ("<b>%s</b>", _("Linked Contacts"));
+ label = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (label), label_string);
+ g_free (label_string);
+
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_misc_set_padding (GTK_MISC (label), 6, 6);
+ gtk_box_pack_start (content_area, label, FALSE, TRUE, 0);
+ gtk_widget_show (label);
+ }
+
+ /* Individual widget */
+ individual_widget = empathy_individual_widget_new (individual,
+ EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
+ EMPATHY_INDIVIDUAL_WIDGET_SHOW_DETAILS |
+ EMPATHY_INDIVIDUAL_WIDGET_SHOW_PERSONAS);
+ gtk_container_set_border_width (GTK_CONTAINER (individual_widget), 6);
+ gtk_box_pack_start (content_area, individual_widget, TRUE, TRUE, 0);
+ gtk_widget_show (individual_widget);
+
+ priv->individual = g_object_ref (individual);
+}
+
+static void
+individual_information_dialog_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EmpathyIndividualInformationDialogPriv *priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_INDIVIDUAL:
+ g_value_set_object (value, priv->individual);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+individual_information_dialog_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EmpathyIndividualInformationDialog *dialog =
+ EMPATHY_INDIVIDUAL_INFORMATION_DIALOG (object);
+
+ switch (param_id) {
+ case PROP_INDIVIDUAL:
+ individual_information_dialog_set_individual (dialog,
+ FOLKS_INDIVIDUAL (g_value_get_object (value)));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+individual_information_dialog_constructed (GObject *object)
+{
+ GtkDialog *dialog = GTK_DIALOG (object);
+ GtkWidget *button;
+
+ gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+ gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE);
+
+ /* Close button */
+ button = gtk_button_new_with_label (GTK_STOCK_CLOSE);
+ gtk_button_set_use_stock (GTK_BUTTON (button), TRUE);
+ gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
+ GTK_RESPONSE_CLOSE);
+ gtk_widget_set_can_default (button, TRUE);
+ gtk_window_set_default (GTK_WINDOW (dialog), button);
+ gtk_widget_show (button);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (individual_dialogs_response_cb), &information_dialogs);
+}
+
+static void
+individual_information_dialog_finalize (GObject *object)
+{
+ EmpathyIndividualInformationDialog *dialog;
+ EmpathyIndividualInformationDialogPriv *priv;
+
+ dialog = EMPATHY_INDIVIDUAL_INFORMATION_DIALOG (object);
+ priv = GET_PRIV (dialog);
+
+ g_object_unref (priv->individual);
+
+ G_OBJECT_CLASS (
+ empathy_individual_information_dialog_parent_class)->finalize (object);
+}
+
+static void
+empathy_individual_information_dialog_class_init (
+ EmpathyIndividualInformationDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = individual_information_dialog_finalize;
+ object_class->get_property = individual_information_dialog_get_property;
+ object_class->set_property = individual_information_dialog_set_property;
+ object_class->constructed = individual_information_dialog_constructed;
+
+ g_object_class_install_property (object_class,
+ PROP_INDIVIDUAL,
+ g_param_spec_object ("individual",
+ "Folks Individual",
+ "Folks Individual to base the dialog upon",
+ FOLKS_TYPE_INDIVIDUAL,
+ G_PARAM_CONSTRUCT |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (object_class,
+ sizeof (EmpathyIndividualInformationDialogPriv));
+}
+
+static void
+empathy_individual_information_dialog_init (
+ EmpathyIndividualInformationDialog *dialog)
+{
+ EmpathyIndividualInformationDialogPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (
+ dialog, EMPATHY_TYPE_INDIVIDUAL_INFORMATION_DIALOG,
+ EmpathyIndividualInformationDialogPriv);
+
+ dialog->priv = priv;
+ priv->individual = NULL;
+}
diff --git a/libempathy-gtk/empathy-individual-information-dialog.h b/libempathy-gtk/empathy-individual-information-dialog.h
new file mode 100644
index 000000000..aad26702f
--- /dev/null
+++ b/libempathy-gtk/empathy-individual-information-dialog.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Philip Withnall <philip.withnall@collabora.co.uk>
+ * Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+#ifndef __EMPATHY_INDIVIDUAL_INFORMATION_DIALOG_H__
+#define __EMPATHY_INDIVIDUAL_INFORMATION_DIALOG_H__
+
+#include <gtk/gtk.h>
+
+#include <folks/folks.h>
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_INDIVIDUAL_INFORMATION_DIALOG (empathy_individual_information_dialog_get_type ())
+#define EMPATHY_INDIVIDUAL_INFORMATION_DIALOG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_INDIVIDUAL_INFORMATION_DIALOG, EmpathyIndividualInformationDialog))
+#define EMPATHY_INDIVIDUAL_INFORMATION_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), EMPATHY_TYPE_INDIVIDUAL_INFORMATION_DIALOG, EmpathyIndividualInformationDialogClass))
+#define EMPATHY_IS_INDIVIDUAL_INFORMATION_DIALOG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_INDIVIDUAL_INFORMATION_DIALOG))
+#define EMPATHY_IS_INDIVIDUAL_INFORMATION_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_INDIVIDUAL_INFORMATION_DIALOG))
+#define EMPATHY_INDIVIDUAL_INFORMATION_DIALOG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_INDIVIDUAL_INFORMATION_DIALOG, EmpathyIndividualInformationDialogClass))
+
+typedef struct _EmpathyIndividualInformationDialog EmpathyIndividualInformationDialog;
+typedef struct _EmpathyIndividualInformationDialogClass EmpathyIndividualInformationDialogClass;
+
+struct _EmpathyIndividualInformationDialog {
+ GtkDialog parent;
+
+ /*<private>*/
+ gpointer priv;
+};
+
+struct _EmpathyIndividualInformationDialogClass {
+ GtkDialogClass parent_class;
+};
+
+GType empathy_individual_information_dialog_get_type (void) G_GNUC_CONST;
+
+void empathy_individual_information_dialog_show (FolksIndividual *individual,
+ GtkWindow *parent);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_INDIVIDUAL_INFORMATION_DIALOG_H__ */
diff --git a/libempathy-gtk/empathy-individual-linker.c b/libempathy-gtk/empathy-individual-linker.c
index c623aaddc..e94ea66b8 100644
--- a/libempathy-gtk/empathy-individual-linker.c
+++ b/libempathy-gtk/empathy-individual-linker.c
@@ -177,7 +177,7 @@ toggle_individual_row (EmpathyIndividualLinker *self,
GtkTreeModel *tree_model;
gboolean individual_added;
- tree_model = GTK_TREE_MODEL (priv->individual_store);
+ tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->individual_view));
gtk_tree_model_get_iter (tree_model, &iter, path);
gtk_tree_model_get (tree_model, &iter,
@@ -235,19 +235,31 @@ set_up (EmpathyIndividualLinker *self)
EmpathyIndividualLinkerPriv *priv;
EmpathyIndividualManager *individual_manager;
GtkCellRenderer *cell_renderer;
+ GtkWidget *top_vbox;
GtkPaned *paned;
GtkWidget *label, *scrolled_window, *search_bar;
GtkBox *vbox;
EmpathyPersonaView *persona_view;
+ gchar *tmp;
+ GtkWidget *alignment;
priv = GET_PRIV (self);
+ top_vbox = gtk_vbox_new (FALSE, 6);
+
/* Layout panes */
paned = GTK_PANED (gtk_hpaned_new ());
/* Left column heading */
+ alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 0, 6);
+ gtk_widget_show (alignment);
+
vbox = GTK_BOX (gtk_vbox_new (FALSE, 6));
- label = gtk_label_new (_("Select contacts to link"));
+ label = gtk_label_new (NULL);
+ tmp = g_strdup_printf ("<b>%s</b>", _("Select contacts to link"));
+ gtk_label_set_markup (GTK_LABEL (label), tmp);
+ g_free (tmp);
gtk_box_pack_start (vbox, label, FALSE, TRUE, 0);
gtk_widget_show (label);
@@ -290,18 +302,26 @@ set_up (EmpathyIndividualLinker *self)
gtk_box_pack_end (vbox, search_bar, FALSE, TRUE, 0);
- gtk_paned_pack1 (paned, GTK_WIDGET (vbox), TRUE, FALSE);
+ gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (vbox));
+ gtk_paned_pack1 (paned, alignment, TRUE, FALSE);
gtk_widget_show (GTK_WIDGET (vbox));
/* Right column heading */
+ alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 6, 0);
+ gtk_widget_show (alignment);
+
vbox = GTK_BOX (gtk_vbox_new (FALSE, 6));
- label = gtk_label_new (_("New contact preview"));
+ label = gtk_label_new (NULL);
+ tmp = g_strdup_printf ("<b>%s</b>", _("New contact preview"));
+ gtk_label_set_markup (GTK_LABEL (label), tmp);
+ g_free (tmp);
gtk_box_pack_start (vbox, label, FALSE, TRUE, 0);
gtk_widget_show (label);
/* New individual preview */
priv->preview_widget = empathy_individual_widget_new (priv->new_individual,
- EMPATHY_CONTACT_WIDGET_SHOW_DETAILS);
+ EMPATHY_INDIVIDUAL_WIDGET_SHOW_DETAILS);
gtk_box_pack_start (vbox, priv->preview_widget, FALSE, TRUE, 0);
gtk_widget_show (priv->preview_widget);
@@ -324,12 +344,26 @@ set_up (EmpathyIndividualLinker *self)
gtk_box_pack_start (vbox, scrolled_window, TRUE, TRUE, 0);
gtk_widget_show (scrolled_window);
- gtk_paned_pack2 (paned, GTK_WIDGET (vbox), TRUE, FALSE);
+ gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (vbox));
+ gtk_paned_pack2 (paned, alignment, TRUE, FALSE);
gtk_widget_show (GTK_WIDGET (vbox));
- /* Add the panes to the bin */
- gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (paned));
gtk_widget_show (GTK_WIDGET (paned));
+
+ /* Footer label */
+ label = gtk_label_new (NULL);
+ tmp = g_strdup_printf ("<i>%s</i>",
+ _("Contacts selected in the list on the left will be linked together."));
+ gtk_label_set_markup (GTK_LABEL (label), tmp);
+ g_free (tmp);
+ gtk_widget_show (label);
+
+ gtk_box_pack_start (GTK_BOX (top_vbox), GTK_WIDGET (paned), TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (top_vbox), label, FALSE, TRUE, 0);
+
+ /* Add the main vbox to the bin */
+ gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (top_vbox));
+ gtk_widget_show (GTK_WIDGET (top_vbox));
}
static void
@@ -557,8 +591,8 @@ empathy_individual_linker_set_start_individual (EmpathyIndividualLinker *self,
priv->start_individual = g_object_ref (individual);
priv->new_individual = folks_individual_new (
folks_individual_get_personas (individual));
- gtk_tree_view_set_model (GTK_TREE_VIEW (priv->individual_view),
- GTK_TREE_MODEL (priv->individual_store));
+ empathy_individual_view_set_store (priv->individual_view,
+ priv->individual_store);
}
else
{
@@ -567,7 +601,7 @@ empathy_individual_linker_set_start_individual (EmpathyIndividualLinker *self,
/* We only display Individuals in the individual view if we have a
* new_individual to link them into */
- gtk_tree_view_set_model (GTK_TREE_VIEW (priv->individual_view), NULL);
+ empathy_individual_view_set_store (priv->individual_view, NULL);
}
empathy_individual_widget_set_individual (
diff --git a/libempathy-gtk/empathy-individual-menu.c b/libempathy-gtk/empathy-individual-menu.c
index 4e625308f..99dea7382 100644
--- a/libempathy-gtk/empathy-individual-menu.c
+++ b/libempathy-gtk/empathy-individual-menu.c
@@ -28,6 +28,8 @@
#include <gtk/gtk.h>
#include <telepathy-glib/util.h>
#include <telepathy-logger/log-manager.h>
+#include <folks/folks.h>
+#include <folks/folks-telepathy.h>
#include <libempathy/empathy-call-factory.h>
#include <libempathy/empathy-dispatcher.h>
@@ -41,10 +43,132 @@
#include "empathy-log-window.h"
#include "empathy-contact-dialogs.h"
#include "empathy-individual-dialogs.h"
+#include "empathy-individual-edit-dialog.h"
+#include "empathy-individual-information-dialog.h"
#include "empathy-ui-utils.h"
#include "empathy-share-my-desktop.h"
#include "empathy-linking-dialog.h"
+static void
+individual_menu_add_personas (GtkMenuShell *menu,
+ FolksIndividual *individual,
+ EmpathyIndividualFeatureFlags features)
+{
+ GtkWidget *item;
+ GList *personas, *l;
+ guint persona_count = 0;
+
+ g_return_if_fail (GTK_IS_MENU (menu));
+ g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
+ g_return_if_fail (empathy_folks_individual_contains_contact (individual));
+
+ personas = folks_individual_get_personas (individual);
+
+ /* Make sure we've got enough valid entries for these menu items to add
+ * functionality */
+ for (l = personas; l != NULL; l = l->next)
+ {
+ if (!TPF_IS_PERSONA (l->data))
+ continue;
+
+ persona_count++;
+ }
+
+ /* return early if these entries would add nothing beyond the "quick" items */
+ if (persona_count <= 1)
+ return;
+
+ /* add a separator before the list of personas */
+ item = gtk_separator_menu_item_new ();
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ personas = folks_individual_get_personas (individual);
+ for (l = personas; l != NULL; l = l->next)
+ {
+ GtkWidget *image;
+ GtkWidget *contact_item;
+ GtkWidget *contact_submenu;
+ TpContact *tp_contact;
+ EmpathyContact *contact;
+ TpfPersona *persona = l->data;
+ gchar *label;
+ FolksPersonaStore *store;
+ const gchar *account;
+ GtkWidget *action;
+
+ if (!TPF_IS_PERSONA (persona))
+ continue;
+
+ tp_contact = tpf_persona_get_contact (persona);
+ contact = empathy_contact_dup_from_tp_contact (tp_contact);
+
+ store = folks_persona_get_store (FOLKS_PERSONA (persona));
+ account = folks_persona_store_get_display_name (store);
+ label = g_strdup_printf (_("%s (%s)"),
+ folks_persona_get_display_id (FOLKS_PERSONA (persona)), account);
+
+ contact_item = gtk_image_menu_item_new_with_label (label);
+ contact_submenu = gtk_menu_new ();
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (contact_item), contact_submenu);
+ image = gtk_image_new_from_icon_name (
+ empathy_icon_name_for_contact (contact), GTK_ICON_SIZE_MENU);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (contact_item), image);
+ gtk_widget_show (image);
+
+ /* Chat */
+ if (features & EMPATHY_INDIVIDUAL_FEATURE_CHAT)
+ {
+ action = empathy_individual_chat_menu_item_new (NULL, contact);
+ gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
+ gtk_widget_show (action);
+ }
+
+ if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL)
+ {
+ /* Audio Call */
+ action = empathy_individual_audio_call_menu_item_new (NULL, contact);
+ gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
+ gtk_widget_show (action);
+
+ /* Video Call */
+ action = empathy_individual_video_call_menu_item_new (NULL, contact);
+ gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
+ gtk_widget_show (action);
+ }
+
+ /* Log */
+ if (features & EMPATHY_INDIVIDUAL_FEATURE_LOG)
+ {
+ action = empathy_individual_log_menu_item_new (NULL, contact);
+ gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
+ gtk_widget_show (action);
+ }
+
+ /* Invite */
+ action = empathy_individual_invite_menu_item_new (NULL, contact);
+ gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
+ gtk_widget_show (action);
+
+ /* File transfer */
+ action = empathy_individual_file_transfer_menu_item_new (NULL, contact);
+ gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
+ gtk_widget_show (action);
+
+ /* Share my desktop */
+ action = empathy_individual_share_my_desktop_menu_item_new (NULL,
+ contact);
+ gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
+ gtk_widget_show (action);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), contact_item);
+ gtk_widget_show (contact_item);
+
+ g_free (label);
+ g_object_unref (contact);
+ }
+}
+
GtkWidget *
empathy_individual_menu_new (FolksIndividual *individual,
EmpathyIndividualFeatureFlags features)
@@ -72,7 +196,7 @@ empathy_individual_menu_new (FolksIndividual *individual,
/* Chat */
if (features & EMPATHY_INDIVIDUAL_FEATURE_CHAT)
{
- item = empathy_individual_chat_menu_item_new (individual);
+ item = empathy_individual_chat_menu_item_new (individual, NULL);
if (item != NULL)
{
gtk_menu_shell_append (shell, item);
@@ -83,12 +207,12 @@ empathy_individual_menu_new (FolksIndividual *individual,
if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL)
{
/* Audio Call */
- 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 Call */
- 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);
}
@@ -96,28 +220,31 @@ empathy_individual_menu_new (FolksIndividual *individual,
/* Log */
if (features & EMPATHY_INDIVIDUAL_FEATURE_LOG)
{
- item = empathy_individual_log_menu_item_new (individual);
+ item = empathy_individual_log_menu_item_new (individual, NULL);
gtk_menu_shell_append (shell, item);
gtk_widget_show (item);
}
/* Invite */
- item = empathy_individual_invite_menu_item_new (individual);
+ item = empathy_individual_invite_menu_item_new (individual, NULL);
gtk_menu_shell_append (shell, item);
gtk_widget_show (item);
/* File transfer */
- item = empathy_individual_file_transfer_menu_item_new (individual);
+ item = empathy_individual_file_transfer_menu_item_new (individual, NULL);
gtk_menu_shell_append (shell, item);
gtk_widget_show (item);
/* Share my desktop */
/* FIXME we should add the "Share my desktop" menu item if Vino is
a registered handler in MC5 */
- item = empathy_individual_share_my_desktop_menu_item_new (individual);
+ item = empathy_individual_share_my_desktop_menu_item_new (individual, NULL);
gtk_menu_shell_append (shell, item);
gtk_widget_show (item);
+ /* Menu items to target specific contacts */
+ individual_menu_add_personas (GTK_MENU_SHELL (menu), individual, features);
+
/* Separator */
if (features & (EMPATHY_INDIVIDUAL_FEATURE_EDIT |
EMPATHY_INDIVIDUAL_FEATURE_INFO |
@@ -245,29 +372,92 @@ out:
return item;
}
-static void
-empathy_individual_chat_menu_item_activated (GtkMenuItem *item,
- FolksIndividual *individual)
+typedef gboolean (*SensitivityPredicate) (EmpathyContact *contact);
+
+/* Like menu_item_set_first_contact(), but always operates upon the given
+ * contact */
+static gboolean
+menu_item_set_contact (GtkWidget *item,
+ EmpathyContact *contact,
+ GCallback activate_callback,
+ SensitivityPredicate sensitivity_predicate)
{
- EmpathyContact *contact;
+ gboolean contact_valid = TRUE;
- contact = empathy_contact_dup_from_folks_individual (individual);
+ if (sensitivity_predicate != NULL)
+ {
+ contact_valid = sensitivity_predicate (contact);
+ gtk_widget_set_sensitive (item, sensitivity_predicate (contact));
+ }
- g_return_if_fail (contact != NULL);
+ g_signal_connect (item, "activate", G_CALLBACK (activate_callback),
+ contact);
- empathy_dispatcher_chat_with_contact (contact, gtk_get_current_event_time ());
+ return contact_valid;
+}
- g_object_unref (contact);
+/**
+ * Set the given menu @item to call @activate_callback upon the first valid
+ * TpContact associated with @individual whenever @item is activated.
+ *
+ * @sensitivity_predicate is an optional function to determine whether the menu
+ * item should be insensitive (if the function returns @FALSE). Otherwise, the
+ * menu item's sensitivity will not change.
+ */
+static GtkWidget *
+menu_item_set_first_contact (GtkWidget *item,
+ FolksIndividual *individual,
+ GCallback activate_callback,
+ SensitivityPredicate sensitivity_predicate)
+{
+ GList *personas, *l;
+
+ personas = folks_individual_get_personas (individual);
+ for (l = personas; l != NULL; l = l->next)
+ {
+ TpContact *tp_contact;
+ EmpathyContact *contact;
+ TpfPersona *persona = l->data;
+ gboolean contact_valid = TRUE;
+
+ if (!TPF_IS_PERSONA (persona))
+ continue;
+
+ tp_contact = tpf_persona_get_contact (persona);
+ contact = empathy_contact_dup_from_tp_contact (tp_contact);
+
+ contact_valid = menu_item_set_contact (item, contact,
+ G_CALLBACK (activate_callback), sensitivity_predicate);
+
+ g_object_unref (contact);
+
+ /* stop after the first valid match */
+ if (contact_valid)
+ break;
+ }
+
+ return item;
+}
+
+static void
+empathy_individual_chat_menu_item_activated (GtkMenuItem *item,
+ EmpathyContact *contact)
+{
+ g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+
+ empathy_dispatcher_chat_with_contact (contact, gtk_get_current_event_time ());
}
GtkWidget *
-empathy_individual_chat_menu_item_new (FolksIndividual *individual)
+empathy_individual_chat_menu_item_new (FolksIndividual *individual,
+ EmpathyContact *contact)
{
GtkWidget *item;
GtkWidget *image;
- g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
- g_return_val_if_fail (empathy_folks_individual_contains_contact (individual),
+ g_return_val_if_fail ((FOLKS_IS_INDIVIDUAL (individual) &&
+ empathy_folks_individual_contains_contact (individual)) ||
+ EMPATHY_IS_CONTACT (contact),
NULL);
item = gtk_image_menu_item_new_with_mnemonic (_("_Chat"));
@@ -276,128 +466,120 @@ empathy_individual_chat_menu_item_new (FolksIndividual *individual)
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
gtk_widget_show (image);
- g_signal_connect (item, "activate",
- G_CALLBACK (empathy_individual_chat_menu_item_activated), individual);
+ if (contact != NULL)
+ {
+ menu_item_set_contact (item, contact,
+ G_CALLBACK (empathy_individual_chat_menu_item_activated), NULL);
+ }
+ else
+ {
+ menu_item_set_first_contact (item, individual,
+ G_CALLBACK (empathy_individual_chat_menu_item_activated), NULL);
+ }
return item;
}
static void
empathy_individual_audio_call_menu_item_activated (GtkMenuItem *item,
- FolksIndividual *individual)
+ EmpathyContact *contact)
{
- EmpathyContact *contact;
-
- contact = empathy_contact_dup_from_folks_individual (individual);
-
g_return_if_fail (EMPATHY_IS_CONTACT (contact));
empathy_call_factory_new_call_with_streams (contact, TRUE, FALSE,
gtk_get_current_event_time (), NULL);
- g_object_unref (contact);
}
GtkWidget *
-empathy_individual_audio_call_menu_item_new (FolksIndividual *individual)
+empathy_individual_audio_call_menu_item_new (FolksIndividual *individual,
+ EmpathyContact *contact)
{
GtkWidget *item;
GtkWidget *image;
- EmpathyContact *contact;
- g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
-
- contact = empathy_contact_dup_from_folks_individual (individual);
-
- g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+ g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
+ EMPATHY_IS_CONTACT (contact),
+ NULL);
item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Audio Call"));
image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VOIP, GTK_ICON_SIZE_MENU);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
- gtk_widget_set_sensitive (item, empathy_contact_can_voip_audio (contact));
gtk_widget_show (image);
- g_signal_connect (item, "activate",
- G_CALLBACK (empathy_individual_audio_call_menu_item_activated),
- individual);
-
- g_object_unref (contact);
+ if (contact != NULL)
+ {
+ menu_item_set_contact (item, contact,
+ G_CALLBACK (empathy_individual_audio_call_menu_item_activated),
+ empathy_contact_can_voip_audio);
+ }
+ else
+ {
+ menu_item_set_first_contact (item, individual,
+ G_CALLBACK (empathy_individual_audio_call_menu_item_activated),
+ empathy_contact_can_voip_audio);
+ }
return item;
}
static void
empathy_individual_video_call_menu_item_activated (GtkMenuItem *item,
- FolksIndividual *individual)
+ EmpathyContact *contact)
{
- EmpathyContact *contact;
-
- contact = empathy_contact_dup_from_folks_individual (individual);
-
g_return_if_fail (EMPATHY_IS_CONTACT (contact));
empathy_call_factory_new_call_with_streams (contact, TRUE, TRUE,
gtk_get_current_event_time (), NULL);
- g_object_unref (contact);
}
GtkWidget *
-empathy_individual_video_call_menu_item_new (FolksIndividual *individual)
+empathy_individual_video_call_menu_item_new (FolksIndividual *individual,
+ EmpathyContact *contact)
{
GtkWidget *item;
GtkWidget *image;
- EmpathyContact *contact;
-
- g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
- contact = empathy_contact_dup_from_folks_individual (individual);
-
- g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+ g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
+ EMPATHY_IS_CONTACT (contact),
+ NULL);
item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Video Call"));
image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VIDEO_CALL,
GTK_ICON_SIZE_MENU);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
- gtk_widget_set_sensitive (item, empathy_contact_can_voip_video (contact));
gtk_widget_show (image);
- g_signal_connect (item, "activate",
- G_CALLBACK (empathy_individual_video_call_menu_item_activated),
- individual);
-
- g_object_unref (contact);
+ if (contact != NULL)
+ {
+ menu_item_set_contact (item, contact,
+ G_CALLBACK (empathy_individual_video_call_menu_item_activated),
+ empathy_contact_can_voip_video);
+ }
+ else
+ {
+ menu_item_set_first_contact (item, individual,
+ G_CALLBACK (empathy_individual_video_call_menu_item_activated),
+ empathy_contact_can_voip_video);
+ }
return item;
}
static void
-individual_log_menu_item_activate_cb (FolksIndividual *individual)
+empathy_individual_log_menu_item_activated (GtkMenuItem *item,
+ EmpathyContact *contact)
{
- EmpathyContact *contact;
-
- contact = empathy_contact_dup_from_folks_individual (individual);
-
g_return_if_fail (EMPATHY_IS_CONTACT (contact));
empathy_log_window_show (empathy_contact_get_account (contact),
empathy_contact_get_id (contact), FALSE, NULL);
-
- g_object_unref (contact);
}
-GtkWidget *
-empathy_individual_log_menu_item_new (FolksIndividual *individual)
+static gboolean
+contact_has_log (EmpathyContact *contact)
{
TplLogManager *manager;
gboolean have_log;
- GtkWidget *item;
- GtkWidget *image;
- EmpathyContact *contact;
-
- g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
-
- contact = empathy_contact_dup_from_folks_individual (individual);
-
- g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
manager = tpl_log_manager_dup_singleton ();
have_log = tpl_log_manager_exists (manager,
@@ -405,101 +587,120 @@ empathy_individual_log_menu_item_new (FolksIndividual *individual)
FALSE);
g_object_unref (manager);
+ return have_log;
+}
+
+GtkWidget *
+empathy_individual_log_menu_item_new (FolksIndividual *individual,
+ EmpathyContact *contact)
+{
+ GtkWidget *item;
+ GtkWidget *image;
+
+ g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
+ EMPATHY_IS_CONTACT (contact),
+ NULL);
+
item = gtk_image_menu_item_new_with_mnemonic (_("_Previous Conversations"));
image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_LOG, GTK_ICON_SIZE_MENU);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
- gtk_widget_set_sensitive (item, have_log);
gtk_widget_show (image);
- g_signal_connect_swapped (item, "activate",
- G_CALLBACK (individual_log_menu_item_activate_cb), individual);
-
- g_object_unref (contact);
+ if (contact != NULL)
+ {
+ menu_item_set_contact (item, contact,
+ G_CALLBACK (empathy_individual_log_menu_item_activated),
+ contact_has_log);
+ }
+ else
+ {
+ menu_item_set_first_contact (item, individual,
+ G_CALLBACK (empathy_individual_log_menu_item_activated),
+ contact_has_log);
+ }
return item;
}
static void
-individual_file_transfer_menu_item_activate_cb (FolksIndividual *individual)
+empathy_individual_file_transfer_menu_item_activated (GtkMenuItem *item,
+ EmpathyContact *contact)
{
- EmpathyContact *contact;
-
- contact = empathy_contact_dup_from_folks_individual (individual);
-
g_return_if_fail (EMPATHY_IS_CONTACT (contact));
empathy_send_file_with_file_chooser (contact);
-
- g_object_unref (contact);
}
GtkWidget *
-empathy_individual_file_transfer_menu_item_new (FolksIndividual *individual)
+empathy_individual_file_transfer_menu_item_new (FolksIndividual *individual,
+ EmpathyContact *contact)
{
GtkWidget *item;
GtkWidget *image;
- EmpathyContact *contact;
-
- g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
-
- contact = empathy_contact_dup_from_folks_individual (individual);
- g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+ g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
+ EMPATHY_IS_CONTACT (contact),
+ NULL);
item = gtk_image_menu_item_new_with_mnemonic (_("Send File"));
image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
- GTK_ICON_SIZE_MENU);
- gtk_widget_set_sensitive (item, empathy_contact_can_send_files (contact));
+ GTK_ICON_SIZE_MENU);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
gtk_widget_show (image);
- g_signal_connect_swapped (item, "activate",
- G_CALLBACK (individual_file_transfer_menu_item_activate_cb), individual);
-
- g_object_unref (contact);
+ if (contact != NULL)
+ {
+ menu_item_set_contact (item, contact,
+ G_CALLBACK (empathy_individual_file_transfer_menu_item_activated),
+ empathy_contact_can_send_files);
+ }
+ else
+ {
+ menu_item_set_first_contact (item, individual,
+ G_CALLBACK (empathy_individual_file_transfer_menu_item_activated),
+ empathy_contact_can_send_files);
+ }
return item;
}
static void
-individual_share_my_desktop_menu_item_activate_cb (FolksIndividual *individual)
+empathy_individual_share_my_desktop_menu_item_activated (GtkMenuItem *item,
+ EmpathyContact *contact)
{
- EmpathyContact *contact;
-
- contact = empathy_contact_dup_from_folks_individual (individual);
-
g_return_if_fail (EMPATHY_IS_CONTACT (contact));
empathy_share_my_desktop_share_with_contact (contact);
-
- g_object_unref (contact);
}
GtkWidget *
-empathy_individual_share_my_desktop_menu_item_new (FolksIndividual *individual)
+empathy_individual_share_my_desktop_menu_item_new (FolksIndividual *individual,
+ EmpathyContact *contact)
{
GtkWidget *item;
GtkWidget *image;
- EmpathyContact *contact;
-
- g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
-
- contact = empathy_contact_dup_from_folks_individual (individual);
- g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+ g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
+ EMPATHY_IS_CONTACT (contact),
+ NULL);
item = gtk_image_menu_item_new_with_mnemonic (_("Share My Desktop"));
image = gtk_image_new_from_icon_name (GTK_STOCK_NETWORK, GTK_ICON_SIZE_MENU);
- gtk_widget_set_sensitive (item,
- empathy_contact_can_use_rfb_stream_tube (contact));
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
gtk_widget_show (image);
- g_signal_connect_swapped (item, "activate",
- G_CALLBACK (individual_share_my_desktop_menu_item_activate_cb),
- individual);
-
- g_object_unref (contact);
+ if (contact != NULL)
+ {
+ menu_item_set_contact (item, contact,
+ G_CALLBACK (empathy_individual_share_my_desktop_menu_item_activated),
+ empathy_contact_can_use_rfb_stream_tube);
+ }
+ else
+ {
+ menu_item_set_first_contact (item, individual,
+ G_CALLBACK (empathy_individual_share_my_desktop_menu_item_activated),
+ empathy_contact_can_use_rfb_stream_tube);
+ }
return item;
}
@@ -531,12 +732,7 @@ empathy_individual_favourite_menu_item_new (FolksIndividual *individual)
static void
individual_info_menu_item_activate_cb (FolksIndividual *individual)
{
- EmpathyContact *contact;
-
- contact = empathy_contact_dup_from_folks_individual (individual);
- empathy_contact_information_dialog_show (contact, NULL);
-
- tp_clear_object (&contact);
+ empathy_individual_information_dialog_show (individual, NULL);
}
GtkWidget *
@@ -565,12 +761,7 @@ empathy_individual_info_menu_item_new (FolksIndividual *individual)
static void
individual_edit_menu_item_activate_cb (FolksIndividual *individual)
{
- EmpathyContact *contact;
-
- contact = empathy_contact_dup_from_folks_individual (individual);
- empathy_contact_edit_dialog_show (contact, NULL);
-
- tp_clear_object (&contact);
+ empathy_individual_edit_dialog_show (individual, NULL);
}
GtkWidget *
@@ -650,25 +841,32 @@ empathy_individual_link_menu_item_new (FolksIndividual *individual)
typedef struct
{
FolksIndividual *individual;
+ EmpathyContact *contact;
EmpathyChatroom *chatroom;
} RoomSubMenuData;
static RoomSubMenuData *
room_sub_menu_data_new (FolksIndividual *individual,
- EmpathyChatroom *chatroom)
+ EmpathyContact *contact,
+ EmpathyChatroom *chatroom)
{
RoomSubMenuData *data;
- data = g_slice_new (RoomSubMenuData);
- data->individual = g_object_ref (individual);
+ data = g_slice_new0 (RoomSubMenuData);
+ if (individual != NULL)
+ data->individual = g_object_ref (individual);
+ if (contact != NULL)
+ data->contact = g_object_ref (contact);
data->chatroom = g_object_ref (chatroom);
+
return data;
}
static void
room_sub_menu_data_free (RoomSubMenuData *data)
{
- g_object_unref (data->individual);
+ tp_clear_object (&data->individual);
+ tp_clear_object (&data->contact);
g_object_unref (data->chatroom);
g_slice_free (RoomSubMenuData, data);
}
@@ -678,7 +876,9 @@ room_sub_menu_activate_cb (GtkWidget *item,
RoomSubMenuData *data)
{
EmpathyTpChat *chat;
- EmpathyContact *contact;
+ EmpathyChatroomManager *mgr;
+ EmpathyContact *contact = NULL;
+ GList *personas, *l;
chat = empathy_chatroom_get_tp_chat (data->chatroom);
if (chat == NULL)
@@ -687,7 +887,45 @@ room_sub_menu_activate_cb (GtkWidget *item,
return;
}
- contact = empathy_contact_dup_from_folks_individual (data->individual);
+ mgr = empathy_chatroom_manager_dup_singleton (NULL);
+
+ if (data->contact != NULL)
+ contact = g_object_ref (data->contact);
+ else
+ {
+ /* find the first of this Individual's contacts who can join this room */
+ personas = folks_individual_get_personas (data->individual);
+ for (l = personas; l != NULL && contact == NULL; l = g_list_next (l))
+ {
+ TpfPersona *persona = l->data;
+ TpContact *tp_contact;
+ GList *rooms;
+
+ if (!TPF_IS_PERSONA (persona))
+ continue;
+
+ tp_contact = tpf_persona_get_contact (persona);
+ contact = empathy_contact_dup_from_tp_contact (tp_contact);
+
+ rooms = empathy_chatroom_manager_get_chatrooms (mgr,
+ empathy_contact_get_account (contact));
+
+ if (g_list_find (rooms, data->chatroom) == NULL)
+ tp_clear_object (&contact);
+
+ /* if contact != NULL here, we've found our match */
+
+ g_list_free (rooms);
+ }
+ }
+
+ g_object_unref (mgr);
+
+ if (contact == NULL)
+ {
+ /* contact disappeared. Ignoring */
+ goto out;
+ }
g_return_if_fail (EMPATHY_IS_CONTACT (contact));
@@ -695,18 +933,20 @@ room_sub_menu_activate_cb (GtkWidget *item,
empathy_contact_list_add (EMPATHY_CONTACT_LIST (chat),
contact, _("Inviting you to this room"));
+out:
g_object_unref (contact);
}
static GtkWidget *
create_room_sub_menu (FolksIndividual *individual,
+ EmpathyContact *contact,
EmpathyChatroom *chatroom)
{
GtkWidget *item;
RoomSubMenuData *data;
item = gtk_menu_item_new_with_label (empathy_chatroom_get_name (chatroom));
- data = room_sub_menu_data_new (individual, chatroom);
+ data = room_sub_menu_data_new (individual, contact, chatroom);
g_signal_connect_data (item, "activate",
G_CALLBACK (room_sub_menu_activate_cb), data,
(GClosureNotify) room_sub_menu_data_free, 0);
@@ -715,21 +955,28 @@ create_room_sub_menu (FolksIndividual *individual,
}
GtkWidget *
-empathy_individual_invite_menu_item_new (FolksIndividual *individual)
+empathy_individual_invite_menu_item_new (FolksIndividual *individual,
+ EmpathyContact *contact)
{
GtkWidget *item;
GtkWidget *image;
GtkWidget *room_item;
EmpathyChatroomManager *mgr;
- GList *rooms, *l;
+ GList *personas;
+ GList *rooms = NULL;
+ GList *names = NULL;
+ GList *l;
GtkWidget *submenu = NULL;
- EmpathyContact *contact;
-
- g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+ /* map of chat room names to their objects; just a utility to remove
+ * duplicates and to make construction of the alphabetized list easier */
+ GHashTable *name_room_map;
- contact = empathy_contact_dup_from_folks_individual (individual);
+ g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
+ EMPATHY_IS_CONTACT (contact),
+ NULL);
- g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+ name_room_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ g_object_unref);
item = gtk_image_menu_item_new_with_mnemonic (_("_Invite to Chat Room"));
image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_GROUP_MESSAGE,
@@ -737,24 +984,75 @@ empathy_individual_invite_menu_item_new (FolksIndividual *individual)
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
mgr = empathy_chatroom_manager_dup_singleton (NULL);
- rooms = empathy_chatroom_manager_get_chatrooms (mgr,
- empathy_contact_get_account (contact));
+ if (contact != NULL)
+ {
+ rooms = empathy_chatroom_manager_get_chatrooms (mgr,
+ empathy_contact_get_account (contact));
+ }
+ else
+ {
+ /* collect the rooms from amongst all accounts for this Individual */
+ personas = folks_individual_get_personas (individual);
+ for (l = personas; l != NULL; l = g_list_next (l))
+ {
+ TpfPersona *persona = l->data;
+ GList *rooms_cur;
+ TpContact *tp_contact;
+ EmpathyContact *contact_cur;
+
+ if (!TPF_IS_PERSONA (persona))
+ continue;
+
+ tp_contact = tpf_persona_get_contact (persona);
+ contact_cur = empathy_contact_dup_from_tp_contact (tp_contact);
+
+ rooms_cur = empathy_chatroom_manager_get_chatrooms (mgr,
+ empathy_contact_get_account (contact_cur));
+ rooms = g_list_concat (rooms, rooms_cur);
+
+ g_object_unref (contact_cur);
+ }
+ }
+
+ /* alphabetize the rooms */
for (l = rooms; l != NULL; l = g_list_next (l))
{
EmpathyChatroom *chatroom = l->data;
+ gboolean existed;
if (empathy_chatroom_get_tp_chat (chatroom) != NULL)
{
- if (G_UNLIKELY (submenu == NULL))
- submenu = gtk_menu_new ();
-
- room_item = create_room_sub_menu (individual, chatroom);
- gtk_menu_shell_append ((GtkMenuShell *) submenu, room_item);
- gtk_widget_show (room_item);
+ const gchar *name;
+
+ name = empathy_chatroom_get_name (chatroom);
+ existed = (g_hash_table_lookup (name_room_map, name) != NULL);
+ g_hash_table_insert (name_room_map, (gpointer) name,
+ g_object_ref (chatroom));
+
+ /* this will take care of duplicates in rooms */
+ if (!existed)
+ {
+ names = g_list_insert_sorted (names, (gpointer) name,
+ (GCompareFunc) g_strcmp0);
+ }
}
}
+ for (l = names; l != NULL; l = g_list_next (l))
+ {
+ const gchar *name = l->data;
+ EmpathyChatroom *chatroom;
+
+ if (G_UNLIKELY (submenu == NULL))
+ submenu = gtk_menu_new ();
+
+ chatroom = g_hash_table_lookup (name_room_map, name);
+ room_item = create_room_sub_menu (individual, contact, chatroom);
+ gtk_menu_shell_append ((GtkMenuShell *) submenu, room_item);
+ gtk_widget_show (room_item);
+ }
+
if (submenu)
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
else
@@ -762,8 +1060,9 @@ empathy_individual_invite_menu_item_new (FolksIndividual *individual)
gtk_widget_show (image);
- g_object_unref (contact);
+ g_hash_table_destroy (name_room_map);
g_object_unref (mgr);
+ g_list_free (names);
g_list_free (rooms);
return item;
diff --git a/libempathy-gtk/empathy-individual-menu.h b/libempathy-gtk/empathy-individual-menu.h
index 1c0d8318d..a00de3dfb 100644
--- a/libempathy-gtk/empathy-individual-menu.h
+++ b/libempathy-gtk/empathy-individual-menu.h
@@ -42,21 +42,28 @@ typedef enum {
GtkWidget * empathy_individual_menu_new (FolksIndividual *individual,
EmpathyIndividualFeatureFlags features);
GtkWidget * empathy_individual_add_menu_item_new (FolksIndividual *individual);
-GtkWidget * empathy_individual_chat_menu_item_new (FolksIndividual *individual);
+GtkWidget * empathy_individual_chat_menu_item_new (FolksIndividual *individual,
+ EmpathyContact *contact);
GtkWidget * empathy_individual_audio_call_menu_item_new (
- FolksIndividual *individual);
+ FolksIndividual *individual,
+ EmpathyContact *contact);
GtkWidget * empathy_individual_video_call_menu_item_new (
- FolksIndividual *individual);
-GtkWidget * empathy_individual_log_menu_item_new (FolksIndividual *individual);
+ FolksIndividual *individual,
+ EmpathyContact *contact);
+GtkWidget * empathy_individual_log_menu_item_new (FolksIndividual *individual,
+ EmpathyContact *contact);
GtkWidget * empathy_individual_info_menu_item_new (FolksIndividual *individual);
GtkWidget * empathy_individual_edit_menu_item_new (FolksIndividual *individual);
GtkWidget * empathy_individual_link_menu_item_new (FolksIndividual *individual);
GtkWidget * empathy_individual_invite_menu_item_new (
- FolksIndividual *individual);
+ FolksIndividual *individual,
+ EmpathyContact *contact);
GtkWidget * empathy_individual_file_transfer_menu_item_new (
- FolksIndividual *individual);
+ FolksIndividual *individual,
+ EmpathyContact *contact);
GtkWidget * empathy_individual_share_my_desktop_menu_item_new (
- FolksIndividual *individual);
+ FolksIndividual *individual,
+ EmpathyContact *contact);
GtkWidget * empathy_individual_favourite_menu_item_new (
FolksIndividual *individual);
diff --git a/libempathy-gtk/empathy-individual-store.c b/libempathy-gtk/empathy-individual-store.c
index 161787299..1d6d5d6b2 100644
--- a/libempathy-gtk/empathy-individual-store.c
+++ b/libempathy-gtk/empathy-individual-store.c
@@ -71,6 +71,8 @@ typedef struct
guint setup_idle_id;
gboolean dispose_has_run;
GHashTable *status_icons;
+ /* List of owned GCancellables for each pending avatar load operation */
+ GList *avatar_cancellables;
} EmpathyIndividualStorePriv;
typedef struct
@@ -552,13 +554,16 @@ individual_store_contact_active_cb (ShowActiveData *data)
return FALSE;
}
+typedef struct {
+ EmpathyIndividualStore *store; /* weak */
+ GCancellable *cancellable; /* owned */
+} LoadAvatarData;
+
static void
-individual_avatar_pixbuf_received_cb (GObject *object,
+individual_avatar_pixbuf_received_cb (FolksIndividual *individual,
GAsyncResult *result,
- gpointer user_data)
+ LoadAvatarData *data)
{
- FolksIndividual *individual = FOLKS_INDIVIDUAL (object);
- EmpathyIndividualStore *self = user_data;
GError *error = NULL;
GdkPixbuf *pixbuf;
@@ -572,18 +577,32 @@ individual_avatar_pixbuf_received_cb (GObject *object,
error->message);
g_clear_error (&error);
}
- else
+ else if (data->store != NULL)
{
GList *iters, *l;
- iters = individual_store_find_contact (self, individual);
+ iters = individual_store_find_contact (data->store, individual);
for (l = iters; l; l = l->next)
{
- gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
+ gtk_tree_store_set (GTK_TREE_STORE (data->store), l->data,
EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, pixbuf,
-1);
}
}
+
+ /* Free things */
+ if (data->store != NULL)
+ {
+ EmpathyIndividualStorePriv *priv = GET_PRIV (data->store);
+
+ g_object_remove_weak_pointer (G_OBJECT (data->store),
+ (gpointer *) &data->store);
+ priv->avatar_cancellables = g_list_remove (priv->avatar_cancellables,
+ data->cancellable);
+ }
+
+ g_object_unref (data->cancellable);
+ g_slice_free (LoadAvatarData, data);
}
static void
@@ -604,6 +623,7 @@ individual_store_contact_update (EmpathyIndividualStore *self,
gboolean do_set_refresh = FALSE;
gboolean show_avatar = FALSE;
GdkPixbuf *pixbuf_status;
+ LoadAvatarData *load_avatar_data;
priv = GET_PRIV (self);
@@ -678,8 +698,19 @@ individual_store_contact_update (EmpathyIndividualStore *self,
show_avatar = TRUE;
}
- empathy_pixbuf_avatar_from_individual_scaled_async (individual,
- individual_avatar_pixbuf_received_cb, 32, 32, self);
+ /* Load the avatar asynchronously */
+ load_avatar_data = g_slice_new (LoadAvatarData);
+ load_avatar_data->store = self;
+ g_object_add_weak_pointer (G_OBJECT (self),
+ (gpointer *) &load_avatar_data->store);
+ load_avatar_data->cancellable = g_cancellable_new ();
+
+ priv->avatar_cancellables = g_list_prepend (priv->avatar_cancellables,
+ load_avatar_data->cancellable);
+ empathy_pixbuf_avatar_from_individual_scaled_async (individual, 32, 32,
+ load_avatar_data->cancellable,
+ (GAsyncReadyCallback) individual_avatar_pixbuf_received_cb,
+ load_avatar_data);
pixbuf_status =
empathy_individual_store_get_individual_status_icon (self, individual);
@@ -771,7 +802,7 @@ static void
individual_store_add_individual_and_connect (EmpathyIndividualStore *self,
FolksIndividual *individual)
{
- EmpathyContact *contact;
+ GList *personas, *l;
g_signal_connect (individual, "notify::avatar",
G_CALLBACK (individual_store_individual_updated_cb), self);
@@ -783,26 +814,67 @@ individual_store_add_individual_and_connect (EmpathyIndividualStore *self,
G_CALLBACK (individual_store_individual_updated_cb), self);
/* FIXME: libfolks hasn't grown capabilities support yet, so we have to go
- * through the EmpathyContact for them. */
- contact = empathy_contact_dup_from_folks_individual (individual);
- g_object_set_data (G_OBJECT (contact), "individual", individual);
- g_signal_connect (contact, "notify::capabilities",
- G_CALLBACK (individual_store_contact_updated_cb), self);
- g_object_unref (contact);
+ * through the EmpathyContacts for them. */
+ personas = folks_individual_get_personas (individual);
+ for (l = personas; l != NULL; l = l->next)
+ {
+ TpContact *tp_contact;
+ EmpathyContact *contact;
+
+ if (!TPF_IS_PERSONA (l->data))
+ continue;
+
+ tp_contact = tpf_persona_get_contact (TPF_PERSONA (l->data));
+ contact = empathy_contact_dup_from_tp_contact (tp_contact);
+ empathy_contact_set_persona (contact, FOLKS_PERSONA (l->data));
+
+ g_object_set_data (G_OBJECT (contact), "individual", individual);
+ g_signal_connect (contact, "notify::capabilities",
+ G_CALLBACK (individual_store_contact_updated_cb), self);
+
+ g_object_unref (contact);
+ }
individual_store_add_individual (self, individual);
}
static void
-individual_store_remove_individual_and_disconnect (
- EmpathyIndividualStore *self,
+individual_store_disconnect_individual (EmpathyIndividualStore *self,
FolksIndividual *individual)
{
+ GList *personas, *l;
+
g_signal_handlers_disconnect_by_func (individual,
G_CALLBACK (individual_store_individual_updated_cb), self);
- g_signal_handlers_disconnect_by_func (individual,
- G_CALLBACK (individual_store_contact_updated_cb), self);
+ /* FIXME: libfolks hasn't grown capabilities support yet, so we have to go
+ * through the EmpathyContacts for them. */
+ personas = folks_individual_get_personas (individual);
+ for (l = personas; l != NULL; l = l->next)
+ {
+ TpContact *tp_contact;
+ EmpathyContact *contact;
+
+ if (!TPF_IS_PERSONA (l->data))
+ continue;
+
+ tp_contact = tpf_persona_get_contact (TPF_PERSONA (l->data));
+ contact = empathy_contact_dup_from_tp_contact (tp_contact);
+ empathy_contact_set_persona (contact, FOLKS_PERSONA (l->data));
+
+ g_signal_handlers_disconnect_by_func (contact,
+ G_CALLBACK (individual_store_contact_updated_cb), self);
+
+ g_object_unref (contact);
+ }
+}
+
+static void
+individual_store_remove_individual_and_disconnect (
+ EmpathyIndividualStore *self,
+ FolksIndividual *individual)
+{
+ individual_store_disconnect_individual (self, individual);
individual_store_remove_individual (self, individual);
}
@@ -951,21 +1023,27 @@ static void
individual_store_dispose (GObject *object)
{
EmpathyIndividualStorePriv *priv = GET_PRIV (object);
- GList *contacts, *l;
+ GList *individuals, *l;
if (priv->dispose_has_run)
return;
priv->dispose_has_run = TRUE;
- contacts = empathy_individual_manager_get_members (priv->manager);
- for (l = contacts; l; l = l->next)
+ /* Cancel any pending avatar load operations */
+ for (l = priv->avatar_cancellables; l != NULL; l = l->next)
+ {
+ /* The cancellables are freed in individual_avatar_pixbuf_received_cb() */
+ g_cancellable_cancel (G_CANCELLABLE (l->data));
+ }
+ g_list_free (priv->avatar_cancellables);
+
+ individuals = empathy_individual_manager_get_members (priv->manager);
+ for (l = individuals; l; l = l->next)
{
- g_signal_handlers_disconnect_by_func (l->data,
- G_CALLBACK (individual_store_individual_updated_cb), object);
- g_signal_handlers_disconnect_by_func (l->data,
- G_CALLBACK (individual_store_contact_updated_cb), object);
+ individual_store_disconnect_individual (EMPATHY_INDIVIDUAL_STORE (object),
+ FOLKS_INDIVIDUAL (l->data));
}
- g_list_free (contacts);
+ g_list_free (individuals);
g_signal_handlers_disconnect_by_func (priv->manager,
G_CALLBACK (individual_store_member_renamed_cb), object);
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);
+ }
+}
diff --git a/libempathy-gtk/empathy-individual-view.h b/libempathy-gtk/empathy-individual-view.h
index 9d6cec0fe..003ff2ab3 100644
--- a/libempathy-gtk/empathy-individual-view.h
+++ b/libempathy-gtk/empathy-individual-view.h
@@ -105,5 +105,10 @@ void empathy_individual_view_set_show_offline (
gboolean empathy_individual_view_is_searching (
EmpathyIndividualView *view);
+EmpathyIndividualStore *empathy_individual_view_get_store (
+ EmpathyIndividualView *self);
+void empathy_individual_view_set_store (EmpathyIndividualView *self,
+ EmpathyIndividualStore *store);
+
G_END_DECLS
#endif /* __EMPATHY_INDIVIDUAL_VIEW_H__ */
diff --git a/libempathy-gtk/empathy-individual-widget.c b/libempathy-gtk/empathy-individual-widget.c
index 2d2e9af7d..a42c5a999 100644
--- a/libempathy-gtk/empathy-individual-widget.c
+++ b/libempathy-gtk/empathy-individual-widget.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 Collabora Ltd.
+ * Copyright (C) 2007-2010 Collabora Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -15,7 +15,8 @@
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
- * Authors: Philip Withnall <philip.withnall@collabora.co.uk>
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Philip Withnall <philip.withnall@collabora.co.uk>
*/
#include <config.h>
@@ -28,12 +29,28 @@
#include <telepathy-glib/util.h>
-#include <libempathy/empathy-utils.h>
-
+#include <folks/folks.h>
#include <folks/folks-telepathy.h>
-#include "empathy-individual-widget.h"
+#ifdef HAVE_LIBCHAMPLAIN
+#include <champlain/champlain.h>
+#include <champlain-gtk/champlain-gtk.h>
+#endif
+
+#include <libempathy/empathy-utils.h>
+#include <libempathy/empathy-location.h>
+#include <libempathy/empathy-time.h>
+
+#include "empathy-avatar-image.h"
+#include "empathy-groups-widget.h"
#include "empathy-gtk-enum-types.h"
+#include "empathy-individual-widget.h"
+#include "empathy-kludge-label.h"
+#include "empathy-string-parser.h"
+#include "empathy-ui-utils.h"
+
+#define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
+#include <libempathy/empathy-debug.h>
/**
* SECTION:empathy-individual-widget
@@ -53,16 +70,49 @@
*
* Widget which displays appropriate widgets with details about an individual,
* also allowing changing these details, if desired.
- *
- * Currently, it's just a thin wrapper around #EmpathyContactWidget, and
- * displays the details of the first eligible persona found in the individual.
*/
#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualWidget)
typedef struct {
- FolksIndividual *individual;
+ FolksIndividual *individual; /* owned */
EmpathyIndividualWidgetFlags flags;
+
+ /* weak pointer to the contact whose contact details we're displaying */
+ TpContact *contact_info_contact;
+
+ /* unowned Persona (borrowed from priv->individual) -> GtkTable child */
+ GHashTable *persona_tables;
+ /* Table containing the information for the individual as whole, or NULL */
+ GtkTable *individual_table;
+
+ /* Individual */
+ GtkWidget *hbox_presence;
+ GtkWidget *vbox_individual_widget;
+ GtkWidget *scrolled_window_individual;
+ GtkWidget *viewport_individual;
+ GtkWidget *vbox_individual;
+
+ /* Location */
+ GtkWidget *vbox_location;
+ GtkWidget *subvbox_location;
+ GtkWidget *table_location;
+ GtkWidget *label_location;
+#ifdef HAVE_LIBCHAMPLAIN
+ GtkWidget *viewport_map;
+ GtkWidget *map_view_embed;
+ ChamplainView *map_view;
+#endif
+
+ /* Groups */
+ GtkWidget *groups_widget;
+
+ /* Details */
+ GtkWidget *vbox_details;
+ GtkWidget *table_details;
+ GtkWidget *hbox_details_requested;
+ GtkWidget *details_spinner;
+ GCancellable *details_cancellable; /* owned */
} EmpathyIndividualWidgetPriv;
G_DEFINE_TYPE (EmpathyIndividualWidget, empathy_individual_widget,
@@ -74,13 +124,1749 @@ enum {
};
static void
+details_set_up (EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+
+ gtk_widget_hide (priv->vbox_details);
+
+ priv->details_spinner = gtk_spinner_new ();
+ gtk_box_pack_end (GTK_BOX (priv->hbox_details_requested),
+ priv->details_spinner, TRUE, TRUE, 0);
+ gtk_widget_show (priv->details_spinner);
+}
+
+typedef struct
+{
+ const gchar *field_name;
+ const gchar *title;
+ gboolean linkify;
+} InfoFieldData;
+
+static InfoFieldData info_field_data[] =
+{
+ { "fn", N_("Full name:"), FALSE },
+ { "tel", N_("Phone number:"), FALSE },
+ { "email", N_("E-mail address:"), TRUE },
+ { "url", N_("Website:"), TRUE },
+ { "bday", N_("Birthday:"), FALSE },
+ { NULL, NULL }
+};
+
+static InfoFieldData *
+find_info_field_data (const gchar *field_name)
+{
+ guint i;
+
+ for (i = 0; info_field_data[i].field_name != NULL; i++)
+ {
+ if (tp_strdiff (info_field_data[i].field_name, field_name) == FALSE)
+ return info_field_data + i;
+ }
+ return NULL;
+}
+
+static gint
+contact_info_field_name_cmp (const gchar *name1,
+ const gchar *name2)
+{
+ guint i;
+
+ if (tp_strdiff (name1, name2) == FALSE)
+ return 0;
+
+ /* We use the order of info_field_data */
+ for (i = 0; info_field_data[i].field_name != NULL; i++)
+ {
+ if (tp_strdiff (info_field_data[i].field_name, name1) == FALSE)
+ return -1;
+ if (tp_strdiff (info_field_data[i].field_name, name2) == FALSE)
+ return +1;
+ }
+
+ return g_strcmp0 (name1, name2);
+}
+
+static gint
+contact_info_field_cmp (TpContactInfoField *field1,
+ TpContactInfoField *field2)
+{
+ return contact_info_field_name_cmp (field1->field_name, field2->field_name);
+}
+
+typedef struct {
+ EmpathyIndividualWidget *widget; /* weak */
+ TpContact *contact; /* owned */
+} DetailsData;
+
+static void
+details_data_free (DetailsData *data)
+{
+ if (data->widget != NULL)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (data->widget),
+ (gpointer *) &data->widget);
+ }
+ g_object_unref (data->contact);
+ g_slice_free (DetailsData, data);
+}
+
+static guint
+details_update_show (EmpathyIndividualWidget *self,
+ TpContact *contact)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ GList *info, *l;
+ guint n_rows = 0;
+
+ info = tp_contact_get_contact_info (contact);
+ info = g_list_sort (info, (GCompareFunc) contact_info_field_cmp);
+ for (l = info; l != NULL; l = l->next)
+ {
+ TpContactInfoField *field = l->data;
+ InfoFieldData *field_data;
+ const gchar *value;
+ GtkWidget *w;
+
+ if (field->field_value == NULL || field->field_value[0] == NULL)
+ continue;
+
+ value = field->field_value[0];
+
+ field_data = find_info_field_data (field->field_name);
+ if (field_data == NULL)
+ {
+ DEBUG ("Unhandled ContactInfo field: %s", field->field_name);
+ continue;
+ }
+
+ /* Add Title */
+ w = gtk_label_new (_(field_data->title));
+ gtk_table_attach (GTK_TABLE (priv->table_details),
+ w, 0, 1, n_rows, n_rows + 1, GTK_FILL, 0, 0, 0);
+ gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
+ gtk_widget_show (w);
+
+ /* Add Value */
+ w = gtk_label_new (value);
+ if (field_data->linkify == TRUE)
+ {
+ gchar *markup;
+
+ markup = empathy_add_link_markup (value);
+ gtk_label_set_markup (GTK_LABEL (w), markup);
+ g_free (markup);
+ }
+
+ if (!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP))
+ gtk_label_set_selectable (GTK_LABEL (w), TRUE);
+
+ gtk_table_attach_defaults (GTK_TABLE (priv->table_details),
+ w, 1, 2, n_rows, n_rows + 1);
+ gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
+ gtk_widget_show (w);
+
+ n_rows++;
+ }
+ g_list_free (info);
+
+ return n_rows;
+}
+
+static void
+details_notify_cb (TpContact *contact,
+ GParamSpec *pspec,
+ EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ guint n_rows;
+
+ gtk_container_foreach (GTK_CONTAINER (priv->table_details),
+ (GtkCallback) gtk_widget_destroy, NULL);
+
+ n_rows = details_update_show (self, contact);
+
+ if (n_rows > 0)
+ {
+ gtk_widget_show (priv->vbox_details);
+ gtk_widget_show (priv->table_details);
+ }
+ else
+ {
+ gtk_widget_hide (priv->vbox_details);
+ }
+
+ gtk_widget_hide (priv->hbox_details_requested);
+ gtk_spinner_stop (GTK_SPINNER (priv->details_spinner));
+}
+
+static void
+details_request_cb (TpContact *contact,
+ GAsyncResult *res,
+ DetailsData *data)
+{
+ EmpathyIndividualWidget *self = data->widget;
+ gboolean hide_widget = FALSE;
+ GError *error = NULL;
+
+ if (tp_contact_request_contact_info_finish (contact, res, &error) == TRUE)
+ details_notify_cb (contact, NULL, self);
+ else
+ hide_widget = TRUE;
+
+ g_clear_error (&error);
+
+ if (self != NULL)
+ {
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+
+ if (hide_widget == TRUE)
+ gtk_widget_hide (GET_PRIV (self)->vbox_details);
+
+ tp_clear_object (&priv->details_cancellable);
+
+ /* We need a (weak) pointer to the contact so that we can disconnect the
+ * signal handler on deconstruction. */
+ if (priv->contact_info_contact != NULL)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (priv->contact_info_contact),
+ (gpointer *) &priv->contact_info_contact);
+ }
+
+ priv->contact_info_contact = contact;
+ g_object_add_weak_pointer (G_OBJECT (contact),
+ (gpointer *) &priv->contact_info_contact);
+
+ g_signal_connect (contact, "notify::contact-info",
+ (GCallback) details_notify_cb, self);
+ }
+
+ details_data_free (data);
+}
+
+static void
+details_feature_prepared_cb (TpConnection *connection,
+ GAsyncResult *res,
+ DetailsData *data)
+{
+ EmpathyIndividualWidget *self = data->widget;
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+
+ if (tp_proxy_prepare_finish (connection, res, NULL) == FALSE)
+ {
+ gtk_widget_hide (priv->vbox_details);
+ details_data_free (data);
+ return;
+ }
+
+ /* Request the Individual's info */
+ gtk_widget_show (priv->vbox_details);
+ gtk_widget_show (priv->hbox_details_requested);
+ gtk_widget_hide (priv->table_details);
+ gtk_spinner_start (GTK_SPINNER (priv->details_spinner));
+
+ if (priv->details_cancellable == NULL)
+ {
+ priv->details_cancellable = g_cancellable_new ();
+ tp_contact_request_contact_info_async (data->contact,
+ priv->details_cancellable, (GAsyncReadyCallback) details_request_cb,
+ data);
+ }
+}
+
+static void
+details_update (EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ TpContact *tp_contact = NULL;
+
+ if (!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_SHOW_DETAILS))
+ return;
+
+ gtk_widget_hide (priv->vbox_details);
+
+ if (priv->individual != NULL)
+ {
+ /* FIXME: We take the first TpContact we find and only use its details.
+ * It would be a lot better if we would get the details for every
+ * TpContact in the Individual and merge them all, but that requires
+ * vCard support in libfolks for it to not be hideously complex.
+ * (bgo#627399) */
+ GList *personas, *l;
+
+ personas = folks_individual_get_personas (priv->individual);
+ for (l = personas; l != NULL; l = l->next)
+ {
+ if (TPF_IS_PERSONA (l->data))
+ {
+ tp_contact = tpf_persona_get_contact (TPF_PERSONA (l->data));
+ if (tp_contact != NULL)
+ break;
+ }
+ }
+ }
+
+ if (tp_contact != NULL)
+ {
+ GQuark features[] = { TP_CONNECTION_FEATURE_CONTACT_INFO, 0 };
+ TpConnection *connection;
+ DetailsData *data;
+
+ data = g_slice_new (DetailsData);
+ data->widget = self;
+ g_object_add_weak_pointer (G_OBJECT (self), (gpointer *) &data->widget);
+ data->contact = g_object_ref (tp_contact);
+
+ /* First, make sure the CONTACT_INFO feature is ready on the connection */
+ connection = tp_contact_get_connection (tp_contact);
+ tp_proxy_prepare_async (connection, features,
+ (GAsyncReadyCallback) details_feature_prepared_cb, data);
+ }
+}
+
+static void
+groups_update (EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_GROUPS &&
+ priv->individual != NULL)
+ {
+ empathy_groups_widget_set_groupable (
+ EMPATHY_GROUPS_WIDGET (priv->groups_widget),
+ FOLKS_GROUPS (priv->individual));
+ gtk_widget_show (priv->groups_widget);
+ }
+ else
+ {
+ gtk_widget_hide (priv->groups_widget);
+ }
+}
+
+/* Converts the Location's GHashTable's key to a user readable string */
+static const gchar *
+location_key_to_label (const gchar *key)
+{
+ if (tp_strdiff (key, EMPATHY_LOCATION_COUNTRY_CODE) == FALSE)
+ return _("Country ISO Code:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_COUNTRY) == FALSE)
+ return _("Country:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_REGION) == FALSE)
+ return _("State:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_LOCALITY) == FALSE)
+ return _("City:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_AREA) == FALSE)
+ return _("Area:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_POSTAL_CODE) == FALSE)
+ return _("Postal Code:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_STREET) == FALSE)
+ return _("Street:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_BUILDING) == FALSE)
+ return _("Building:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_FLOOR) == FALSE)
+ return _("Floor:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_ROOM) == FALSE)
+ return _("Room:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_TEXT) == FALSE)
+ return _("Text:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_DESCRIPTION) == FALSE)
+ return _("Description:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_URI) == FALSE)
+ return _("URI:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_ACCURACY_LEVEL) == FALSE)
+ return _("Accuracy Level:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_ERROR) == FALSE)
+ return _("Error:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_VERTICAL_ERROR_M) == FALSE)
+ return _("Vertical Error (meters):");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_HORIZONTAL_ERROR_M) == FALSE)
+ return _("Horizontal Error (meters):");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_SPEED) == FALSE)
+ return _("Speed:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_BEARING) == FALSE)
+ return _("Bearing:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_CLIMB) == FALSE)
+ return _("Climb Speed:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_TIMESTAMP) == FALSE)
+ return _("Last Updated on:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_LON) == FALSE)
+ return _("Longitude:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_LAT) == FALSE)
+ return _("Latitude:");
+ else if (tp_strdiff (key, EMPATHY_LOCATION_ALT) == FALSE)
+ return _("Altitude:");
+ else
+ {
+ DEBUG ("Unexpected Location key: %s", key);
+ return key;
+ }
+}
+
+static void
+location_update (EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ EmpathyContact *contact = NULL;
+ GHashTable *location = NULL;
+ GValue *value;
+ GtkWidget *label;
+ guint row = 0;
+ static const gchar* ordered_geolocation_keys[] = {
+ EMPATHY_LOCATION_TEXT,
+ EMPATHY_LOCATION_URI,
+ EMPATHY_LOCATION_DESCRIPTION,
+ EMPATHY_LOCATION_BUILDING,
+ EMPATHY_LOCATION_FLOOR,
+ EMPATHY_LOCATION_ROOM,
+ EMPATHY_LOCATION_STREET,
+ EMPATHY_LOCATION_AREA,
+ EMPATHY_LOCATION_LOCALITY,
+ EMPATHY_LOCATION_REGION,
+ EMPATHY_LOCATION_COUNTRY,
+ NULL
+ };
+ int i;
+ const gchar *skey;
+ gboolean display_map = FALSE;
+ GList *personas, *l;
+
+ if (!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION))
+ {
+ gtk_widget_hide (priv->vbox_location);
+ return;
+ }
+
+ /* FIXME: For the moment, we just display the first location data we can
+ * find amongst the Individual's Personas. Once libfolks grows a location
+ * interface, we can use that. (bgo#627400) */
+ personas = folks_individual_get_personas (priv->individual);
+ for (l = personas; l != NULL; l = l->next)
+ {
+ FolksPersona *persona = FOLKS_PERSONA (l->data);
+
+ if (TPF_IS_PERSONA (persona))
+ {
+ TpContact *tp_contact;
+
+ /* Get the contact. If it turns out to have location information, we
+ * have to keep it alive for the duration of the function, since we're
+ * accessing its private data. */
+ tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+ contact = empathy_contact_dup_from_tp_contact (tp_contact);
+ empathy_contact_set_persona (contact, persona);
+
+ /* Try and get a location */
+ location = empathy_contact_get_location (contact);
+ if (location != NULL && g_hash_table_size (location) > 0)
+ break;
+
+ location = NULL;
+ tp_clear_object (&contact);
+ }
+ }
+
+ if (contact == NULL || location == NULL)
+ {
+ gtk_widget_hide (priv->vbox_location);
+ tp_clear_object (&contact);
+ return;
+ }
+
+ value = g_hash_table_lookup (location, EMPATHY_LOCATION_TIMESTAMP);
+ if (value == NULL)
+ {
+ gchar *loc = g_strdup_printf ("<b>%s</b>", _("Location"));
+ gtk_label_set_markup (GTK_LABEL (priv->label_location), loc);
+ g_free (loc);
+ }
+ else
+ {
+ gchar *user_date;
+ gchar *text;
+ gint64 stamp;
+ time_t time_;
+
+ stamp = g_value_get_int64 (value);
+ time_ = stamp;
+
+ user_date = empathy_time_to_string_relative (time_);
+
+ text = g_strconcat ( _("<b>Location</b>, "), user_date, NULL);
+ gtk_label_set_markup (GTK_LABEL (priv->label_location), text);
+ g_free (user_date);
+ g_free (text);
+ }
+
+ /* Prepare the location information table */
+ if (priv->table_location != NULL)
+ gtk_widget_destroy (priv->table_location);
+
+ priv->table_location = gtk_table_new (1, 2, FALSE);
+ gtk_box_pack_start (GTK_BOX (priv->subvbox_location),
+ priv->table_location, FALSE, FALSE, 5);
+
+
+ for (i = 0; (skey = ordered_geolocation_keys[i]); i++)
+ {
+ const gchar* user_label;
+ GValue *gvalue;
+ char *svalue = NULL;
+
+ gvalue = g_hash_table_lookup (location, (gpointer) skey);
+ if (gvalue == NULL)
+ continue;
+
+ user_label = location_key_to_label (skey);
+
+ label = gtk_label_new (user_label);
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+ gtk_table_attach (GTK_TABLE (priv->table_location),
+ label, 0, 1, row, row + 1, GTK_FILL, GTK_FILL, 10, 0);
+ gtk_widget_show (label);
+
+ if (G_VALUE_TYPE (gvalue) == G_TYPE_DOUBLE)
+ {
+ gdouble dvalue;
+ dvalue = g_value_get_double (gvalue);
+ svalue = g_strdup_printf ("%f", dvalue);
+ }
+ else if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING)
+ {
+ svalue = g_value_dup_string (gvalue);
+ }
+ else if (G_VALUE_TYPE (gvalue) == G_TYPE_INT64)
+ {
+ time_t time_;
+
+ time_ = g_value_get_int64 (value);
+ svalue = empathy_time_to_string_utc (time_, _("%B %e, %Y at %R UTC"));
+ }
+
+ if (svalue != NULL)
+ {
+ label = gtk_label_new (svalue);
+ gtk_table_attach_defaults (GTK_TABLE (priv->table_location),
+ label, 1, 2, row, row + 1);
+ gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+ gtk_widget_show (label);
+
+ if (!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP))
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+ }
+
+ g_free (svalue);
+ row++;
+ }
+
+ tp_clear_object (&contact);
+
+#ifdef HAVE_LIBCHAMPLAIN
+ if ((g_hash_table_lookup (location, EMPATHY_LOCATION_LAT) != NULL) &&
+ (g_hash_table_lookup (location, EMPATHY_LOCATION_LON) != NULL) &&
+ !(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP))
+ {
+ /* Cannot be displayed in tooltips until Clutter-Gtk can deal with such
+ * windows */
+ display_map = TRUE;
+ }
+#endif
+
+ if (row > 0)
+ {
+ /* We can display some fields */
+ gtk_widget_show (priv->table_location);
+ }
+ else if (display_map == FALSE)
+ {
+ /* Can't display either fields or map */
+ gtk_widget_hide (priv->vbox_location);
+ return;
+ }
+
+#ifdef HAVE_LIBCHAMPLAIN
+ if (display_map == TRUE)
+ {
+ GPtrArray *markers;
+ ChamplainLayer *layer;
+
+ priv->map_view_embed = gtk_champlain_embed_new ();
+ priv->map_view = gtk_champlain_embed_get_view (
+ GTK_CHAMPLAIN_EMBED (priv->map_view_embed));
+
+ gtk_container_add (GTK_CONTAINER (priv->viewport_map),
+ priv->map_view_embed);
+ g_object_set (G_OBJECT (priv->map_view),
+ "show-license", TRUE,
+ "scroll-mode", CHAMPLAIN_SCROLL_MODE_KINETIC,
+ "zoom-level", 10,
+ NULL);
+
+ layer = champlain_layer_new ();
+ champlain_view_add_layer (priv->map_view, layer);
+ markers = g_ptr_array_new ();
+
+ /* FIXME: For now, we have to do this manually. Once libfolks grows a
+ * location interface, we can use that. (bgo#627400) */
+ personas = folks_individual_get_personas (priv->individual);
+ for (l = personas; l != NULL; l = l->next)
+ {
+ FolksPersona *persona = FOLKS_PERSONA (l->data);
+
+ if (TPF_IS_PERSONA (persona))
+ {
+ gdouble lat = 0.0, lon = 0.0;
+ ClutterActor *marker;
+ TpContact *tp_contact;
+
+ /* Get the contact */
+ tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+ contact = empathy_contact_dup_from_tp_contact (tp_contact);
+ empathy_contact_set_persona (contact, persona);
+
+ /* Try and get a location */
+ location = empathy_contact_get_location (contact);
+ if (location == NULL || g_hash_table_size (location) == 0)
+ {
+ g_object_unref (contact);
+ continue;
+ }
+
+ /* Get this persona's latitude and longitude */
+ value = g_hash_table_lookup (location, EMPATHY_LOCATION_LAT);
+ if (value == NULL)
+ {
+ g_object_unref (contact);
+ continue;
+ }
+
+ lat = g_value_get_double (value);
+
+ value = g_hash_table_lookup (location, EMPATHY_LOCATION_LON);
+ if (value == NULL)
+ {
+ g_object_unref (contact);
+ continue;
+ }
+
+ lon = g_value_get_double (value);
+
+ /* Add a marker to the map */
+ marker = champlain_marker_new_with_text (
+ folks_alias_get_alias (FOLKS_ALIAS (persona)), NULL, NULL,
+ NULL);
+ champlain_base_marker_set_position (
+ CHAMPLAIN_BASE_MARKER (marker), lat, lon);
+ clutter_container_add (CLUTTER_CONTAINER (layer), marker, NULL);
+
+ g_ptr_array_add (markers, marker);
+
+ g_object_unref (contact);
+ }
+ }
+
+ /* Zoom to show all of the markers */
+ g_ptr_array_add (markers, NULL); /* NULL-terminate the array */
+ champlain_view_ensure_markers_visible (priv->map_view,
+ (ChamplainBaseMarker **) markers->pdata, FALSE);
+ g_ptr_array_free (markers, TRUE);
+
+ gtk_widget_show_all (priv->viewport_map);
+ }
+#endif
+
+ gtk_widget_show (priv->vbox_location);
+}
+
+static EmpathyAvatar *
+persona_dup_avatar (FolksPersona *persona)
+{
+ TpContact *tp_contact;
+ EmpathyContact *contact;
+ EmpathyAvatar *avatar;
+
+ if (!TPF_IS_PERSONA (persona))
+ return NULL;
+
+ tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+ contact = empathy_contact_dup_from_tp_contact (tp_contact);
+ empathy_contact_set_persona (contact, persona);
+
+ avatar = empathy_contact_get_avatar (contact);
+ if (avatar != NULL)
+ empathy_avatar_ref (avatar);
+ g_object_unref (contact);
+
+ return avatar;
+}
+
+static EmpathyAvatar *
+individual_dup_avatar (FolksIndividual *individual)
+{
+ GList *personas, *l;
+ EmpathyAvatar *avatar = NULL;
+
+ /* FIXME: We just choose the first Persona which has an avatar, and save that.
+ * The avatar handling in EmpathyContact needs to be moved into libfolks as
+ * much as possible, and this code rewritten to use FolksAvatar.
+ * (bgo#627401) */
+ personas = folks_individual_get_personas (individual);
+ for (l = personas; l != NULL; l = l->next)
+ {
+ avatar = persona_dup_avatar (FOLKS_PERSONA (l->data));
+ if (avatar != NULL)
+ break;
+ }
+
+ return avatar;
+}
+
+static void
+save_avatar_menu_activate_cb (GtkWidget *widget,
+ EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ GtkWidget *dialog;
+ EmpathyAvatar *avatar;
+ gchar *ext = NULL, *filename;
+
+ dialog = gtk_file_chooser_dialog_new (_("Save Avatar"),
+ NULL,
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
+ TRUE);
+
+ avatar = individual_dup_avatar (priv->individual);
+ if (avatar == NULL)
+ return;
+
+ /* look for the avatar extension */
+ if (avatar->format != NULL)
+ {
+ gchar **splitted;
+
+ splitted = g_strsplit (avatar->format, "/", 2);
+ if (splitted[0] != NULL && splitted[1] != NULL)
+ ext = g_strdup (splitted[1]);
+
+ g_strfreev (splitted);
+ }
+ else
+ {
+ /* Avatar was loaded from the cache so was converted to PNG */
+ ext = g_strdup ("png");
+ }
+
+ if (ext != NULL)
+ {
+ gchar *id;
+
+ id = tp_escape_as_identifier (folks_individual_get_id (priv->individual));
+
+ filename = g_strdup_printf ("%s.%s", id, ext);
+ gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename);
+
+ g_free (id);
+ g_free (ext);
+ g_free (filename);
+ }
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+ {
+ GError *error = NULL;
+
+ filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
+
+ if (empathy_avatar_save_to_file (avatar, filename, &error) == FALSE)
+ {
+ /* Save error */
+ GtkWidget *error_dialog;
+
+ error_dialog = gtk_message_dialog_new (NULL, 0,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ _("Unable to save avatar"));
+
+ gtk_message_dialog_format_secondary_text (
+ GTK_MESSAGE_DIALOG (error_dialog), "%s", error->message);
+
+ g_signal_connect (error_dialog, "response",
+ (GCallback) gtk_widget_destroy, NULL);
+
+ gtk_window_present (GTK_WINDOW (error_dialog));
+
+ g_clear_error (&error);
+ }
+
+ g_free (filename);
+ }
+
+ gtk_widget_destroy (dialog);
+ empathy_avatar_unref (avatar);
+}
+
+static gboolean
+popup_avatar_menu (EmpathyIndividualWidget *self,
+ GtkWidget *parent,
+ GdkEventButton *event)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ GtkWidget *menu, *item;
+ EmpathyAvatar *avatar;
+ gint button, event_time;
+
+ if (priv->individual == NULL)
+ return FALSE;
+
+ avatar = individual_dup_avatar (priv->individual);
+ if (avatar == NULL)
+ return FALSE;
+ empathy_avatar_unref (avatar);
+
+ menu = gtk_menu_new ();
+
+ /* Add "Save as..." entry */
+ item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SAVE_AS, NULL);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ g_signal_connect (item, "activate",
+ (GCallback) save_avatar_menu_activate_cb, self);
+
+ if (event != NULL)
+ {
+ button = event->button;
+ event_time = event->time;
+ }
+ else
+ {
+ button = 0;
+ event_time = gtk_get_current_event_time ();
+ }
+
+ gtk_menu_attach_to_widget (GTK_MENU (menu), parent, NULL);
+ gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, event_time);
+ g_object_ref_sink (menu);
+ g_object_unref (menu);
+
+ return TRUE;
+}
+
+static gboolean
+avatar_widget_popup_menu_cb (GtkWidget *widget,
+ EmpathyIndividualWidget *self)
+{
+ return popup_avatar_menu (self, widget, NULL);
+}
+
+static gboolean
+avatar_widget_button_press_event_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ EmpathyIndividualWidget *self)
+{
+ /* Ignore double-clicks and triple-clicks */
+ if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
+ return popup_avatar_menu (self, widget, event);
+
+ return FALSE;
+}
+
+/* Returns the TpAccount for the user as a convenience. Note that it has a ref
+ * added. */
+static TpAccount *
+individual_is_user (FolksIndividual *individual)
+{
+ GList *personas, *l;
+
+ /* FIXME: This should move into libfolks when libfolks grows a way of
+ * determining "self". (bgo#627402) */
+ personas = folks_individual_get_personas (individual);
+ for (l = personas; l != NULL; l = l->next)
+ {
+ FolksPersona *persona = FOLKS_PERSONA (l->data);
+
+ if (TPF_IS_PERSONA (persona))
+ {
+ TpContact *tp_contact;
+ EmpathyContact *contact;
+
+ /* Get the contact */
+ tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+ contact = empathy_contact_dup_from_tp_contact (tp_contact);
+ empathy_contact_set_persona (contact, persona);
+
+ /* Determine if the contact is the user */
+ if (empathy_contact_is_user (contact))
+ {
+ g_object_unref (contact);
+ return g_object_ref (empathy_contact_get_account (contact));
+ }
+
+ g_object_unref (contact);
+ }
+ }
+
+ return NULL;
+}
+
+static void
+set_nickname_cb (TpAccount *account,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GError *error = NULL;
+
+ if (tp_account_set_nickname_finish (account, result, &error) == FALSE)
+ {
+ DEBUG ("Failed to set Account.Nickname: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static gboolean
+entry_alias_focus_event_cb (GtkEditable *editable,
+ GdkEventFocus *event,
+ EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+
+ if (priv->individual != NULL)
+ {
+ const gchar *alias;
+ TpAccount *account;
+
+ alias = gtk_entry_get_text (GTK_ENTRY (editable));
+ account = individual_is_user (priv->individual);
+
+ if (account != NULL)
+ {
+ DEBUG ("Set Account.Nickname to %s", alias);
+ tp_account_set_nickname_async (account, alias,
+ (GAsyncReadyCallback) set_nickname_cb, NULL);
+ g_object_unref (account);
+ }
+ else
+ {
+ folks_alias_set_alias (FOLKS_ALIAS (priv->individual), alias);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+favourite_toggled_cb (GtkToggleButton *button,
+ EmpathyIndividualWidget *self)
+{
+ gboolean active = gtk_toggle_button_get_active (button);
+ folks_favourite_set_is_favourite (
+ FOLKS_FAVOURITE (GET_PRIV (self)->individual), active);
+}
+
+static void
+notify_avatar_cb (gpointer folks_object,
+ GParamSpec *pspec,
+ EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ EmpathyAvatar *avatar = NULL;
+ GObject *table;
+ GtkWidget *avatar_widget;
+
+ if (FOLKS_IS_INDIVIDUAL (folks_object))
+ {
+ avatar = individual_dup_avatar (FOLKS_INDIVIDUAL (folks_object));
+ table = G_OBJECT (priv->individual_table);
+ }
+ else if (FOLKS_IS_PERSONA (folks_object))
+ {
+ avatar = persona_dup_avatar (FOLKS_PERSONA (folks_object));
+ table = g_hash_table_lookup (priv->persona_tables, folks_object);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ if (table == NULL)
+ return;
+
+ avatar_widget = g_object_get_data (table, "avatar-widget");
+ empathy_avatar_image_set (EMPATHY_AVATAR_IMAGE (avatar_widget), avatar);
+
+ if (avatar != NULL)
+ empathy_avatar_unref (avatar);
+}
+
+static void
+notify_alias_cb (gpointer folks_object,
+ GParamSpec *pspec,
+ EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ GObject *table;
+ GtkWidget *alias_widget;
+
+ if (FOLKS_IS_INDIVIDUAL (folks_object))
+ table = G_OBJECT (priv->individual_table);
+ else if (FOLKS_IS_PERSONA (folks_object))
+ table = g_hash_table_lookup (priv->persona_tables, folks_object);
+ else
+ g_assert_not_reached ();
+
+ if (table == NULL)
+ return;
+
+ alias_widget = g_object_get_data (table, "alias-widget");
+
+ if (GTK_IS_ENTRY (alias_widget))
+ {
+ gtk_entry_set_text (GTK_ENTRY (alias_widget),
+ folks_alias_get_alias (FOLKS_ALIAS (folks_object)));
+ }
+ else
+ {
+ gtk_label_set_label (GTK_LABEL (alias_widget),
+ folks_alias_get_alias (FOLKS_ALIAS (folks_object)));
+ }
+}
+
+static void
+notify_presence_cb (gpointer folks_object,
+ GParamSpec *pspec,
+ EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ GObject *table;
+ GtkWidget *status_label, *state_image;
+ const gchar *message;
+ gchar *markup_text = NULL;
+
+ if (FOLKS_IS_INDIVIDUAL (folks_object))
+ table = G_OBJECT (priv->individual_table);
+ else if (FOLKS_IS_PERSONA (folks_object))
+ table = g_hash_table_lookup (priv->persona_tables, folks_object);
+ else
+ g_assert_not_reached ();
+
+ if (table == NULL)
+ return;
+
+ status_label = g_object_get_data (table, "status-label");
+ state_image = g_object_get_data (table, "state-image");
+
+ /* FIXME: Default messages should be moved into libfolks (bgo#627403) */
+ message = folks_presence_get_presence_message (FOLKS_PRESENCE (folks_object));
+ if (EMP_STR_EMPTY (message))
+ {
+ message = empathy_presence_get_default_message (
+ folks_presence_get_presence_type (FOLKS_PRESENCE (folks_object)));
+ }
+
+ if (message != NULL)
+ markup_text = empathy_add_link_markup (message);
+ gtk_label_set_markup (GTK_LABEL (status_label), markup_text);
+ g_free (markup_text);
+
+ gtk_image_set_from_icon_name (GTK_IMAGE (state_image),
+ empathy_icon_name_for_presence (
+ folks_presence_get_presence_type (FOLKS_PRESENCE (folks_object))),
+ GTK_ICON_SIZE_BUTTON);
+ gtk_widget_show (state_image);
+}
+
+static void
+notify_is_favourite_cb (gpointer folks_object,
+ GParamSpec *pspec,
+ EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ GObject *table;
+ GtkWidget *favourite_widget;
+
+ if (FOLKS_IS_INDIVIDUAL (folks_object))
+ table = G_OBJECT (priv->individual_table);
+ else if (FOLKS_IS_PERSONA (folks_object))
+ table = g_hash_table_lookup (priv->persona_tables, folks_object);
+ else
+ g_assert_not_reached ();
+
+ if (table == NULL)
+ return;
+
+ favourite_widget = g_object_get_data (table, "favourite-widget");
+
+ if (GTK_IS_TOGGLE_BUTTON (favourite_widget))
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (favourite_widget),
+ folks_favourite_get_is_favourite (FOLKS_FAVOURITE (folks_object)));
+ }
+}
+
+static void
+alias_presence_avatar_favourite_set_up (EmpathyIndividualWidget *self,
+ GtkTable *table,
+ guint starting_row)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ GtkWidget *label, *alias, *image, *avatar, *alignment;
+ guint current_row = starting_row;
+
+ /* Alias */
+ label = gtk_label_new (_("Alias:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_table_attach (table, label, 0, 1, current_row, current_row + 1, GTK_FILL,
+ GTK_FILL, 0, 0);
+ gtk_widget_show (label);
+
+ /* Set up alias label/entry */
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_ALIAS)
+ {
+ alias = gtk_entry_new ();
+
+ g_signal_connect (alias, "focus-out-event",
+ (GCallback) entry_alias_focus_event_cb, self);
+
+ /* Make return activate the window default (the Close button) */
+ gtk_entry_set_activates_default (GTK_ENTRY (alias), TRUE);
+ }
+ else
+ {
+ alias = gtk_label_new (NULL);
+ if (!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP))
+ gtk_label_set_selectable (GTK_LABEL (alias), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (alias), 0.0, 0.5);
+ }
+
+ g_object_set_data (G_OBJECT (table), "alias-widget", alias);
+ gtk_table_attach (table, alias, 1, 2, current_row, current_row + 1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+ gtk_widget_show (alias);
+
+ current_row++;
+
+ /* Presence */
+ priv->hbox_presence = gtk_hbox_new (FALSE, 6);
+
+ /* Presence image */
+ image = gtk_image_new_from_stock (GTK_STOCK_MISSING_IMAGE,
+ GTK_ICON_SIZE_BUTTON);
+ g_object_set_data (G_OBJECT (table), "state-image", image);
+ gtk_box_pack_start (GTK_BOX (priv->hbox_presence), image, FALSE,
+ FALSE, 0);
+ gtk_widget_show (image);
+
+ /* Set up status_label as a KludgeLabel */
+ label = empathy_kludge_label_new ("");
+ gtk_label_set_line_wrap_mode (GTK_LABEL (label), PANGO_WRAP_WORD_CHAR);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+
+ gtk_label_set_selectable (GTK_LABEL (label),
+ (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP) ? FALSE : TRUE);
+
+ g_object_set_data (G_OBJECT (table), "status-label", label);
+ gtk_box_pack_start (GTK_BOX (priv->hbox_presence), label, TRUE,
+ TRUE, 0);
+ gtk_widget_show (label);
+
+ gtk_table_attach (table, priv->hbox_presence, 0, 2, current_row,
+ current_row + 1, GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+ gtk_widget_show (priv->hbox_presence);
+
+ current_row++;
+
+ /* Set up favourite toggle button */
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
+ {
+ GtkWidget *favourite = gtk_check_button_new_with_label (_("Favorite"));
+
+ g_signal_connect (favourite, "toggled",
+ (GCallback) favourite_toggled_cb, self);
+
+ g_object_set_data (G_OBJECT (table), "favourite-widget", favourite);
+ gtk_table_attach (table, favourite, 0, 2, current_row, current_row + 1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+ gtk_widget_show (favourite);
+
+ current_row++;
+ }
+
+ /* Set up avatar display */
+ avatar = empathy_avatar_image_new ();
+
+ if (!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP))
+ {
+ g_signal_connect (avatar, "popup-menu",
+ (GCallback) avatar_widget_popup_menu_cb, self);
+ g_signal_connect (avatar, "button-press-event",
+ (GCallback) avatar_widget_button_press_event_cb, self);
+ }
+
+ g_object_set_data (G_OBJECT (table), "avatar-widget", avatar);
+
+ alignment = gtk_alignment_new (1.0, 0.0, 0.0, 0.0);
+ gtk_container_add (GTK_CONTAINER (alignment), avatar);
+ gtk_widget_show (avatar);
+
+ gtk_table_attach (table, alignment, 2, 3, 0, current_row,
+ GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 6, 6);
+ gtk_widget_show (alignment);
+}
+
+static void
+update_persona (EmpathyIndividualWidget *self, FolksPersona *persona)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ TpContact *tp_contact;
+ EmpathyContact *contact;
+ TpAccount *account;
+ GtkTable *table;
+ GtkLabel *label;
+ GtkImage *image;
+ const gchar *id;
+
+ table = g_hash_table_lookup (priv->persona_tables, persona);
+
+ g_assert (table != NULL);
+
+ tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+ contact = empathy_contact_dup_from_tp_contact (tp_contact);
+ empathy_contact_set_persona (contact, persona);
+
+ account = empathy_contact_get_account (contact);
+
+ /* Update account widget */
+ if (account != NULL)
+ {
+ const gchar *name;
+
+ label = g_object_get_data (G_OBJECT (table), "account-label");
+ image = g_object_get_data (G_OBJECT (table), "account-image");
+
+ name = tp_account_get_display_name (account);
+ gtk_label_set_label (label, name);
+
+ name = tp_account_get_icon_name (account);
+ gtk_image_set_from_icon_name (image, name, GTK_ICON_SIZE_MENU);
+ }
+
+ /* Update id widget */
+ label = g_object_get_data (G_OBJECT (table), "id-widget");
+ id = folks_persona_get_display_id (persona);
+ gtk_label_set_label (label, (id != NULL) ? id : "");
+
+ /* Update other widgets */
+ notify_alias_cb (persona, NULL, self);
+ notify_presence_cb (persona, NULL, self);
+ notify_avatar_cb (persona, NULL, self);
+
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
+ notify_is_favourite_cb (persona, NULL, self);
+}
+
+static void
+add_persona (EmpathyIndividualWidget *self,
+ FolksPersona *persona)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ GtkBox *hbox;
+ GtkTable *table;
+ GtkWidget *label, *account_label, *account_image, *separator;
+ guint current_row = 0;
+
+ if (!TPF_IS_PERSONA (persona))
+ return;
+
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
+ table = GTK_TABLE (gtk_table_new (5, 3, FALSE));
+ else
+ table = GTK_TABLE (gtk_table_new (4, 3, FALSE));
+ gtk_table_set_row_spacings (table, 6);
+ gtk_table_set_col_spacings (table, 6);
+
+ /* Account and Identifier */
+ label = gtk_label_new (_("Account:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_table_attach (table, label, 0, 1, current_row, current_row + 1,
+ GTK_FILL, GTK_FILL, 0, 0);
+ gtk_widget_show (label);
+
+ /* Pack the protocol icon with the account name in an hbox */
+ hbox = GTK_BOX (gtk_hbox_new (FALSE, 6));
+
+ account_label = gtk_label_new (NULL);
+ gtk_label_set_selectable (GTK_LABEL (account_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (account_label), 0.0, 0.5);
+ gtk_widget_show (account_label);
+
+ account_image = gtk_image_new ();
+ gtk_widget_show (account_image);
+
+ gtk_box_pack_start (hbox, account_image, FALSE, FALSE, 0);
+ gtk_box_pack_start (hbox, account_label, FALSE, TRUE, 0);
+
+ g_object_set_data (G_OBJECT (table), "account-image", account_image);
+ g_object_set_data (G_OBJECT (table), "account-label", account_label);
+ gtk_table_attach (table, GTK_WIDGET (hbox), 1, 2, current_row,
+ current_row + 1, GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+ gtk_widget_show (GTK_WIDGET (hbox));
+
+ current_row++;
+
+ /* Translators: Identifier to connect to Instant Messaging network */
+ label = gtk_label_new (_("Identifier:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_table_attach (table, label, 0, 1, current_row, current_row + 1,
+ GTK_FILL, GTK_FILL, 0, 0);
+ gtk_widget_show (label);
+
+ /* Set up ID label */
+ label = gtk_label_new (NULL);
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+ g_object_set_data (G_OBJECT (table), "id-widget", label);
+ gtk_table_attach (table, label, 1, 2, current_row, current_row + 1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+ gtk_widget_show (label);
+
+ current_row++;
+
+ alias_presence_avatar_favourite_set_up (self, table, current_row);
+
+ /* Connect to signals and display the table */
+ g_signal_connect (persona, "notify::alias",
+ (GCallback) notify_alias_cb, self);
+ g_signal_connect (persona, "notify::avatar",
+ (GCallback) notify_avatar_cb, self);
+ g_signal_connect (persona, "notify::presence-type",
+ (GCallback) notify_presence_cb, self);
+ g_signal_connect (persona, "notify::presence-message",
+ (GCallback) notify_presence_cb, self);
+
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
+ {
+ g_signal_connect (persona, "notify::is-favourite",
+ (GCallback) notify_is_favourite_cb, self);
+ }
+
+ gtk_box_pack_start (GTK_BOX (priv->vbox_individual),
+ GTK_WIDGET (table), FALSE, TRUE, 0);
+ gtk_widget_show (GTK_WIDGET (table));
+
+ /* Pack a separator after the table */
+ separator = gtk_hseparator_new ();
+ g_object_set_data (G_OBJECT (table), "separator", separator);
+ gtk_box_pack_start (GTK_BOX (priv->vbox_individual), separator, FALSE, FALSE,
+ 0);
+ gtk_widget_show (separator);
+
+ g_hash_table_replace (priv->persona_tables, persona, table);
+
+ /* Update the new widgets */
+ update_persona (self, persona);
+}
+
+static void
+remove_persona (EmpathyIndividualWidget *self,
+ FolksPersona *persona)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ GtkWidget *separator;
+ GtkTable *table;
+
+ if (!TPF_IS_PERSONA (persona))
+ return;
+
+ table = g_hash_table_lookup (priv->persona_tables, persona);
+ if (table == NULL)
+ return;
+
+ g_signal_handlers_disconnect_by_func (persona, notify_alias_cb, self);
+ g_signal_handlers_disconnect_by_func (persona, notify_avatar_cb, self);
+ g_signal_handlers_disconnect_by_func (persona, notify_presence_cb, self);
+
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
+ {
+ g_signal_handlers_disconnect_by_func (persona, notify_is_favourite_cb,
+ self);
+ }
+
+ /* Remove the separator */
+ separator = g_object_get_data (G_OBJECT (table), "separator");
+ if (separator != NULL)
+ gtk_container_remove (GTK_CONTAINER (priv->vbox_individual), separator);
+
+ /* Remove the widget */
+ gtk_container_remove (GTK_CONTAINER (priv->vbox_individual),
+ GTK_WIDGET (table));
+
+ g_hash_table_remove (priv->persona_tables, persona);
+}
+
+static void
+update_individual_table (EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+
+ notify_alias_cb (priv->individual, NULL, self);
+ notify_presence_cb (priv->individual, NULL, self);
+ notify_avatar_cb (priv->individual, NULL, self);
+
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
+ notify_is_favourite_cb (priv->individual, NULL, self);
+}
+
+static void
+individual_table_set_up (EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ GtkTable *table;
+ guint current_row = 0;
+ guint nb_rows = 2;
+
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
+ nb_rows++;
+
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP)
+ nb_rows++;
+
+ table = GTK_TABLE (gtk_table_new (nb_rows, 3, FALSE));
+ gtk_table_set_row_spacings (table, 6);
+ gtk_table_set_col_spacings (table, 6);
+
+ /* We only display the number of personas in tooltips */
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP)
+ {
+ gchar *message;
+ GtkWidget *label;
+ GList *personas, *l;
+ guint num_personas = 0;
+
+ /* Meta-contacts message displaying how many Telepathy personas we have */
+ personas = folks_individual_get_personas (priv->individual);
+ for (l = personas; l != NULL; l = l->next)
+ {
+ if (TPF_IS_PERSONA (l->data))
+ num_personas++;
+ }
+
+ message = g_strdup_printf (ngettext ("Meta-contact containing %u contact",
+ "Meta-contact containing %u contacts", num_personas), num_personas);
+ label = gtk_label_new (message);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ g_free (message);
+
+ gtk_table_attach (table, label, 0, 2, current_row, current_row + 1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+ gtk_widget_show (label);
+
+ current_row++;
+ }
+
+ alias_presence_avatar_favourite_set_up (self, table, current_row);
+
+ /* Display the table */
+ gtk_box_pack_start (GTK_BOX (priv->vbox_individual), GTK_WIDGET (table),
+ FALSE, TRUE, 0);
+ gtk_widget_show (GTK_WIDGET (table));
+
+ priv->individual_table = table;
+
+ /* Update the table */
+ update_individual_table (self);
+}
+
+static void
+individual_table_destroy (EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+
+ if (priv->individual_table == NULL)
+ return;
+
+ gtk_container_remove (GTK_CONTAINER (priv->vbox_individual),
+ GTK_WIDGET (priv->individual_table));
+ priv->individual_table = NULL;
+}
+
+static void
+notify_personas_cb (FolksIndividual *individual,
+ GParamSpec *pspec,
+ EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ GList *personas, *l, *children, *remove_personas = NULL;
+ GHashTableIter iter;
+ FolksPersona *persona;
+ GtkTable *table;
+ gboolean is_last;
+ guint old_num_personas, new_num_personas;
+
+ personas = folks_individual_get_personas (individual);
+ old_num_personas = g_hash_table_size (priv->persona_tables);
+ new_num_personas = g_list_length (personas);
+
+ /* Remove Personas */
+ g_hash_table_iter_init (&iter, priv->persona_tables);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &persona,
+ (gpointer *) &table))
+ {
+ /* FIXME: This is slow. bgo#626725 */
+ /* Old persona or we were displaying all personas and now just want to
+ * display the individual table.
+ * We can't remove the persona inside this loop, as that would invalidate
+ * the hash table iter. */
+ if (g_list_find (personas, persona) == NULL ||
+ (!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_SHOW_PERSONAS) &&
+ new_num_personas > 1))
+ {
+ remove_personas = g_list_prepend (remove_personas, persona);
+ }
+ }
+
+ for (l = remove_personas; l != NULL; l = l->next)
+ remove_persona (self, FOLKS_PERSONA (l->data));
+ g_list_free (remove_personas);
+
+ individual_table_destroy (self);
+
+ /* If we're !SHOW_PERSONAS and have more than one Persona, we only display
+ * the Individual's alias, avatar and presence, and a label saying
+ * "Meta-contact containing x contacts". (i.e. One table.)
+ * If we're SHOW_PERSONAS or have only one Persona, we display the
+ * alias, avatar, presence, account and identifier for each of the
+ * Individual's Personas. (i.e. One table per Persona.) */
+ if (!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_SHOW_PERSONAS) &&
+ new_num_personas > 1)
+ {
+ individual_table_set_up (self);
+ }
+ else
+ {
+ /* Add Personas */
+ for (l = personas; l != NULL; l = l->next)
+ {
+ persona = FOLKS_PERSONA (l->data);
+
+ if (!TPF_IS_PERSONA (persona))
+ continue;
+
+ /* New persona or we were displaying the individual table and we
+ * now want to display all personas */
+ if ((!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_SHOW_PERSONAS) &&
+ new_num_personas <= 1) ||
+ g_hash_table_lookup (priv->persona_tables, persona) == NULL)
+ {
+ add_persona (self, persona);
+ }
+ }
+ }
+
+ /* Hide the last separator and show the others */
+ children = gtk_container_get_children (GTK_CONTAINER (priv->vbox_individual));
+ children = g_list_reverse (children);
+ is_last = TRUE;
+
+ for (l = children; l != NULL; l = l->next)
+ {
+ if (GTK_IS_SEPARATOR (l->data))
+ {
+ gtk_widget_set_visible (GTK_WIDGET (l->data), !is_last);
+ is_last = FALSE;
+ }
+ }
+
+ g_list_free (children);
+}
+
+static void
+remove_individual (EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+ if (priv->individual != NULL)
+ {
+ GList *personas, *l;
+
+ g_signal_handlers_disconnect_by_func (priv->individual,
+ notify_alias_cb, self);
+ g_signal_handlers_disconnect_by_func (priv->individual,
+ notify_presence_cb, self);
+ g_signal_handlers_disconnect_by_func (priv->individual,
+ notify_avatar_cb, self);
+ g_signal_handlers_disconnect_by_func (priv->individual,
+ notify_personas_cb, self);
+
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
+ {
+ g_signal_handlers_disconnect_by_func (priv->individual,
+ notify_is_favourite_cb, self);
+ }
+
+ personas = folks_individual_get_personas (priv->individual);
+ for (l = personas; l != NULL; l = l->next)
+ remove_persona (self, FOLKS_PERSONA (l->data));
+
+ if (priv->contact_info_contact != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (priv->contact_info_contact,
+ details_notify_cb, self);
+ g_object_remove_weak_pointer (G_OBJECT (priv->contact_info_contact),
+ (gpointer *) &priv->contact_info_contact);
+ priv->contact_info_contact = NULL;
+ }
+
+ tp_clear_object (&priv->individual);
+ }
+
+ if (priv->details_cancellable != NULL)
+ g_cancellable_cancel (priv->details_cancellable);
+}
+
+static void
+individual_update (EmpathyIndividualWidget *self)
+{
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (self);
+
+ /* Connect and get info from new Individual */
+ if (priv->individual != NULL)
+ {
+ g_signal_connect (priv->individual, "notify::alias",
+ (GCallback) notify_alias_cb, self);
+ g_signal_connect (priv->individual, "notify::presence-type",
+ (GCallback) notify_presence_cb, self);
+ g_signal_connect (priv->individual, "notify::presence-message",
+ (GCallback) notify_presence_cb, self);
+ g_signal_connect (priv->individual, "notify::avatar",
+ (GCallback) notify_avatar_cb, self);
+ g_signal_connect (priv->individual, "notify::personas",
+ (GCallback) notify_personas_cb, self);
+
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE)
+ {
+ g_signal_connect (priv->individual, "notify::is-favourite",
+ (GCallback) notify_is_favourite_cb, self);
+ }
+
+ /* Update individual table */
+ notify_personas_cb (priv->individual, NULL, self);
+ }
+
+ if (priv->individual == NULL)
+ {
+ gtk_widget_hide (priv->vbox_individual);
+ }
+ else if (priv->individual_table != NULL)
+ {
+ /* We only need to update the details for the Individual as a whole */
+ update_individual_table (self);
+ gtk_widget_show (priv->vbox_individual);
+ }
+ else
+ {
+ /* We need to update the details for every Persona in the Individual */
+ GList *personas, *l;
+
+ personas = folks_individual_get_personas (priv->individual);
+ for (l = personas; l != NULL; l = l->next)
+ {
+ if (!TPF_IS_PERSONA (l->data))
+ continue;
+
+ update_persona (self, FOLKS_PERSONA (l->data));
+ }
+
+ gtk_widget_show (priv->vbox_individual);
+ }
+}
+
+static void
empathy_individual_widget_init (EmpathyIndividualWidget *self)
{
- self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ EmpathyIndividualWidgetPriv *priv;
+ GtkBuilder *gui;
+ gchar *filename;
+
+ priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
EMPATHY_TYPE_INDIVIDUAL_WIDGET, EmpathyIndividualWidgetPriv);
+ self->priv = priv;
gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
GTK_ORIENTATION_VERTICAL);
+
+ filename = empathy_file_lookup ("empathy-individual-widget.ui",
+ "libempathy-gtk");
+ gui = empathy_builder_get_file (filename,
+ "scrolled_window_individual", &priv->scrolled_window_individual,
+ "viewport_individual", &priv->viewport_individual,
+ "vbox_individual_widget", &priv->vbox_individual_widget,
+ "vbox_individual", &priv->vbox_individual,
+ "vbox_location", &priv->vbox_location,
+ "subvbox_location", &priv->subvbox_location,
+ "label_location", &priv->label_location,
+#ifdef HAVE_LIBCHAMPLAIN
+ "viewport_map", &priv->viewport_map,
+#endif
+ "groups_widget", &priv->groups_widget,
+ "vbox_details", &priv->vbox_details,
+ "table_details", &priv->table_details,
+ "hbox_details_requested", &priv->hbox_details_requested,
+ NULL);
+ g_free (filename);
+
+ priv->table_location = NULL;
+
+ gtk_box_pack_start (GTK_BOX (self), priv->vbox_individual_widget, TRUE, TRUE,
+ 0);
+ gtk_widget_show (priv->vbox_individual_widget);
+
+ priv->persona_tables = g_hash_table_new (NULL, NULL);
+ priv->individual_table = NULL;
+
+ /* Create widgets */
+ details_set_up (self);
+
+ g_object_unref (gui);
+}
+
+static void
+constructed (GObject *object)
+{
+ GObjectClass *klass = G_OBJECT_CLASS (empathy_individual_widget_parent_class);
+ EmpathyIndividualWidgetPriv *priv = GET_PRIV (object);
+ GtkScrolledWindow *scrolled_window =
+ GTK_SCROLLED_WINDOW (priv->scrolled_window_individual);
+
+ /* Allow scrolling of the list of Personas if we're showing Personas. */
+ if (priv->flags & EMPATHY_INDIVIDUAL_WIDGET_SHOW_PERSONAS)
+ {
+ gtk_scrolled_window_set_shadow_type (scrolled_window, GTK_SHADOW_IN);
+ gtk_scrolled_window_set_policy (scrolled_window,
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ gtk_box_set_child_packing (GTK_BOX (priv->vbox_individual_widget),
+ priv->scrolled_window_individual, TRUE, TRUE, 0, GTK_PACK_START);
+
+ gtk_container_set_border_width (GTK_CONTAINER (priv->viewport_individual),
+ 6);
+ gtk_widget_set_size_request (GTK_WIDGET (scrolled_window), -1, 100);
+ }
+ else
+ {
+ gtk_scrolled_window_set_shadow_type (scrolled_window, GTK_SHADOW_NONE);
+ gtk_scrolled_window_set_policy (scrolled_window,
+ GTK_POLICY_NEVER, GTK_POLICY_NEVER);
+ gtk_box_set_child_packing (GTK_BOX (priv->vbox_individual_widget),
+ priv->scrolled_window_individual, FALSE, TRUE, 0, GTK_PACK_START);
+
+ gtk_container_set_border_width (GTK_CONTAINER (priv->viewport_individual),
+ 0);
+ }
+
+ /* Chain up */
+ if (klass->constructed != NULL)
+ klass->constructed (object);
}
static void
@@ -131,11 +1917,19 @@ set_property (GObject *object,
static void
dispose (GObject *object)
{
+ remove_individual (EMPATHY_INDIVIDUAL_WIDGET (object));
+
+ G_OBJECT_CLASS (empathy_individual_widget_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
EmpathyIndividualWidgetPriv *priv = GET_PRIV (object);
- tp_clear_object (&priv->individual);
+ g_hash_table_destroy (priv->persona_tables);
- G_OBJECT_CLASS (empathy_individual_widget_parent_class)->dispose (object);
+ G_OBJECT_CLASS (empathy_individual_widget_parent_class)->finalize (object);
}
static void
@@ -143,9 +1937,11 @@ empathy_individual_widget_class_init (EmpathyIndividualWidgetClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->constructed = constructed;
object_class->get_property = get_property;
object_class->set_property = set_property;
object_class->dispose = dispose;
+ object_class->finalize = finalize;
/**
* EmpathyIndividualWidget:individual:
@@ -169,7 +1965,7 @@ empathy_individual_widget_class_init (EmpathyIndividualWidgetClass *klass)
"Flags",
"A set of flags which affect the widget's behaviour.",
EMPATHY_TYPE_INDIVIDUAL_WIDGET_FLAGS,
- EMPATHY_INDIVIDUAL_WIDGET_EDIT_NONE,
+ EMPATHY_INDIVIDUAL_WIDGET_NONE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
g_type_class_add_private (object_class, sizeof (EmpathyIndividualWidgetPriv));
@@ -192,9 +1988,19 @@ empathy_individual_widget_new (FolksIndividual *individual,
NULL);
return g_object_new (EMPATHY_TYPE_INDIVIDUAL_WIDGET,
- "individual", individual, "flags", flags, NULL);
+ "individual", individual,
+ "flags", flags,
+ NULL);
}
+/**
+ * empathy_individual_widget_get_individual:
+ * @self: an #EmpathyIndividualWidget
+ *
+ * Returns the #FolksIndividual being displayed by the widget.
+ *
+ * Return value: the #FolksIndividual being displayed, or %NULL
+ */
FolksIndividual *
empathy_individual_widget_get_individual (EmpathyIndividualWidget *self)
{
@@ -203,55 +2009,41 @@ empathy_individual_widget_get_individual (EmpathyIndividualWidget *self)
return GET_PRIV (self)->individual;
}
+/**
+ * empathy_individual_widget_set_individual:
+ * @self: an #EmpathyIndividualWidget
+ * @individual: the #FolksIndividual to display, or %NULL
+ *
+ * Set the #FolksIndividual to be displayed by the widget:
+ * #EmpathyIndividualWidget:individual.
+ *
+ * The @individual may be %NULL in order to display nothing in the widget.
+ */
void
empathy_individual_widget_set_individual (EmpathyIndividualWidget *self,
FolksIndividual *individual)
{
EmpathyIndividualWidgetPriv *priv;
- GList *personas = NULL, *l;
g_return_if_fail (EMPATHY_IS_INDIVIDUAL_WIDGET (self));
g_return_if_fail (individual == NULL || FOLKS_IS_INDIVIDUAL (individual));
priv = GET_PRIV (self);
+ if (individual == priv->individual)
+ return;
+
/* Out with the old… */
- gtk_container_foreach (GTK_CONTAINER (self), (GtkCallback) gtk_widget_destroy,
- NULL);
- tp_clear_object (&priv->individual);
+ remove_individual (self);
/* …and in with the new. */
- priv->individual = individual;
if (individual != NULL)
- {
- g_object_ref (individual);
- personas = folks_individual_get_personas (individual);
- }
-
- for (l = personas; l != NULL; l = l->next)
- {
- GtkWidget *contact_widget;
- TpContact *tp_contact;
- EmpathyContact *contact;
- TpfPersona *persona = l->data;
-
- if (!TPF_IS_PERSONA (persona))
- continue;
-
- tp_contact = tpf_persona_get_contact (persona);
- contact = empathy_contact_dup_from_tp_contact (tp_contact);
-
- /* Contact info widget */
- contact_widget = empathy_contact_widget_new (contact, priv->flags);
- gtk_container_set_border_width (GTK_CONTAINER (contact_widget), 8);
- gtk_box_pack_start (GTK_BOX (self), contact_widget, TRUE, TRUE, 0);
- gtk_widget_show (contact_widget);
-
- g_object_unref (contact);
+ g_object_ref (individual);
+ priv->individual = individual;
- /* If we're not meant to display all of the personas, bail after the first
- * one. */
- if (!(priv->flags & EMPATHY_INDIVIDUAL_WIDGET_SHOW_PERSONAS))
- break;
- }
+ /* Update information for widgets */
+ individual_update (self);
+ groups_update (self);
+ details_update (self);
+ location_update (self);
}
diff --git a/libempathy-gtk/empathy-individual-widget.h b/libempathy-gtk/empathy-individual-widget.h
index 89066f60d..a968d14f3 100644
--- a/libempathy-gtk/empathy-individual-widget.h
+++ b/libempathy-gtk/empathy-individual-widget.h
@@ -31,40 +31,38 @@ G_BEGIN_DECLS
/**
* EmpathyIndividualWidgetFlags:
- * @EMPATHY_INDIVIDUAL_WIDGET_EDIT_NONE: Don't show any widgets to edit any
- * details of the individual. This should be the option for widgets that merely
- * display information about an individual.
+ * @EMPATHY_INDIVIDUAL_WIDGET_NONE: Don't show any widgets to edit any
+ * details of the individual, or to display any extended details of the
+ * individual. This should be the option for widgets that merely display basic
+ * information about an individual.
* @EMPATHY_INDIVIDUAL_WIDGET_EDIT_ALIAS: Show a #GtkEntry allowing changes to
* the individual's alias.
- * @EMPATHY_INDIVIDUAL_WIDGET_EDIT_AVATAR: Show an #EmpathyAvatarChooser
- * allowing changes to the individual's avatar.
- * @EMPATHY_INDIVIDUAL_WIDGET_EDIT_ACCOUNT: Show an #EmpathyAccountChooser
- * allowing changes to the individual's account.
- * @EMPATHY_INDIVIDUAL_WIDGET_EDIT_ID: Show a #GtkEntry allowing changes to the
- * individual's identifier.
+ * @EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE: Show a checkbutton
+ * allowing changes to the individual's favourite status.
* @EMPATHY_INDIVIDUAL_WIDGET_EDIT_GROUPS: Show a widget to change the groups
* the individual is in.
* @EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP: Make widgets more designed for a
- * tooltip. For example, make widgets not selectable.
+ * tooltip. For example, make labels not selectable.
+ * @EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION: Display the individual's current
+ * location textually or using a map (if compiled with libchamplain support).
+ * @EMPATHY_INDIVIDUAL_WIDGET_SHOW_DETAILS: Fetch and display extended vCard
+ * details of the individual, if the individual has them.
+ * @EMPATHY_INDIVIDUAL_WIDGET_SHOW_PERSONAS: List the #FolksPersona<!-- -->s
+ * contained in the individual.
*
* Flags used when creating an #EmpathyIndividualWidget to specify which
* features should be available.
*/
typedef enum
{
- EMPATHY_INDIVIDUAL_WIDGET_EDIT_NONE = 0,
+ EMPATHY_INDIVIDUAL_WIDGET_NONE = 0,
EMPATHY_INDIVIDUAL_WIDGET_EDIT_ALIAS = 1 << 0,
- EMPATHY_INDIVIDUAL_WIDGET_EDIT_AVATAR = 1 << 1,
- EMPATHY_INDIVIDUAL_WIDGET_EDIT_ACCOUNT = 1 << 2,
- EMPATHY_INDIVIDUAL_WIDGET_EDIT_ID = 1 << 3,
- EMPATHY_INDIVIDUAL_WIDGET_EDIT_GROUPS = 1 << 4,
- EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP = 1 << 5,
- EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION = 1 << 6,
- EMPATHY_INDIVIDUAL_WIDGET_NO_SET_ALIAS = 1 << 7,
- EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE = 1 << 8,
- EMPATHY_INDIVIDUAL_WIDGET_SHOW_DETAILS = 1 << 9,
- EMPATHY_INDIVIDUAL_WIDGET_EDIT_DETAILS = 1 << 10,
- EMPATHY_INDIVIDUAL_WIDGET_SHOW_PERSONAS = 1 << 11,
+ EMPATHY_INDIVIDUAL_WIDGET_EDIT_FAVOURITE = 1 << 1,
+ EMPATHY_INDIVIDUAL_WIDGET_EDIT_GROUPS = 1 << 2,
+ EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP = 1 << 3,
+ EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION = 1 << 4,
+ EMPATHY_INDIVIDUAL_WIDGET_SHOW_DETAILS = 1 << 5,
+ EMPATHY_INDIVIDUAL_WIDGET_SHOW_PERSONAS = 1 << 6,
} EmpathyIndividualWidgetFlags;
#define EMPATHY_TYPE_INDIVIDUAL_WIDGET (empathy_individual_widget_get_type ())
diff --git a/libempathy-gtk/empathy-individual-widget.ui b/libempathy-gtk/empathy-individual-widget.ui
new file mode 100644
index 000000000..5fb382363
--- /dev/null
+++ b/libempathy-gtk/empathy-individual-widget.ui
@@ -0,0 +1,188 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkVBox" id="vbox_individual_widget">
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window_individual">
+ <property name="hscrollbar-policy">never</property>
+ <property name="vscrollbar-policy">never</property><!-- Modified in code when necessary -->
+ <property name="shadow-type">none</property><!-- Modified in code when necessary -->
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport_individual">
+ <property name="shadow-type">none</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkVBox" id="vbox_individual">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox_location">
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label_location">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Location&lt;/b&gt; at (date)</property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="subvbox_location">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkFrame" id="viewport_map">
+ <property name="height_request">150</property>
+ <property name="resize_mode">queue</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="EmpathyGroupsWidget" id="groups_widget"/>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox_details">
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label649">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Contact Details</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment30">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox218">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkTable" id="table_details">
+ <property name="visible">True</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox_details_requested">
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkImage" id="image885">
+ <property name="visible">True</property>
+ <property name="stock">gtk-dialog-info</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label653">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Information requested…</property>
+ <property name="use_markup">True</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/libempathy-gtk/empathy-live-search.c b/libempathy-gtk/empathy-live-search.c
index 41325c34b..2b5f54f98 100644
--- a/libempathy-gtk/empathy-live-search.c
+++ b/libempathy-gtk/empathy-live-search.c
@@ -522,7 +522,7 @@ empathy_live_search_class_init (EmpathyLiveSearchClass *klass)
_empathy_gtk_marshal_BOOLEAN__BOXED,
G_TYPE_BOOLEAN, 1, GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
- param_spec = g_param_spec_object ("hook-widget", "Live Searchs Hook Widget",
+ param_spec = g_param_spec_object ("hook-widget", "Live Search Hook Widget",
"The live search catches key-press-events on this widget",
GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_HOOK_WIDGET,
diff --git a/libempathy-gtk/empathy-log-window.ui b/libempathy-gtk/empathy-log-window.ui
index d38618c24..3220c18f4 100644
--- a/libempathy-gtk/empathy-log-window.ui
+++ b/libempathy-gtk/empathy-log-window.ui
@@ -1,6 +1,7 @@
<?xml version="1.0"?>
-<!--*- mode: xml -*-->
<interface>
+ <!-- interface-requires gtk+ 2.12 -->
+ <!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="log_window">
<property name="title" translatable="yes">Previous Conversations</property>
<property name="role">log</property>
@@ -24,13 +25,14 @@
<child>
<object class="GtkLabel" id="label628">
<property name="visible">True</property>
- <property comments="Searching *for* something" name="label" translatable="yes">_For:</property>
+ <property name="label" translatable="yes" comments="Searching *for* something">_For:</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">entry_find</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
+ <property name="position">0</property>
</packing>
</child>
<child>
@@ -46,12 +48,13 @@
</child>
<child>
<object class="GtkButton" id="button_find">
+ <property name="label">gtk-find</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
- <property name="label">gtk-find</property>
+ <property name="receives_default">False</property>
<property name="use_stock">True</property>
<property name="focus_on_click">False</property>
</object>
@@ -65,6 +68,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
+ <property name="position">0</property>
</packing>
</child>
<child>
@@ -76,9 +80,9 @@
<object class="GtkScrolledWindow" id="scrolledwindow14">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
- <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
- <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="treeview_find">
<property name="visible">True</property>
@@ -100,12 +104,15 @@
<object class="GtkScrolledWindow" id="scrolledwindow_find">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
- <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
</child>
<child>
<object class="GtkHBox" id="hbox171">
@@ -115,34 +122,36 @@
<placeholder/>
</child>
<child>
- <object class="GtkButton" id="button_previous">
+ <object class="GtkButton" id="button_next">
+ <property name="label" translatable="yes">Find next</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
- <property name="label">gtk-go-back</property>
- <property name="use_stock">True</property>
+ <property name="receives_default">False</property>
+ <property name="image">image1</property>
<property name="focus_on_click">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
- <property name="pack_type">GTK_PACK_END</property>
+ <property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
- <object class="GtkButton" id="button_next">
+ <object class="GtkButton" id="button_previous">
+ <property name="label" translatable="yes">Find previous</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
- <property name="label">gtk-go-forward</property>
- <property name="use_stock">True</property>
+ <property name="receives_default">False</property>
+ <property name="image">image2</property>
<property name="focus_on_click">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
- <property name="pack_type">GTK_PACK_END</property>
+ <property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
@@ -169,7 +178,7 @@
<child type="tab">
<object class="GtkLabel" id="label595">
<property name="visible">True</property>
- <property comments="Tab Label" name="label" translatable="yes">Search</property>
+ <property name="label" translatable="yes" comments="Tab Label">Search</property>
</object>
<packing>
<property name="tab_fill">False</property>
@@ -200,8 +209,8 @@
<object class="GtkScrolledWindow" id="scrolledwindow_chats">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
- <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
@@ -222,9 +231,9 @@
<property name="width_request">150</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
- <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
- <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="treeview_chats">
<property name="visible">True</property>
@@ -233,6 +242,9 @@
</object>
</child>
</object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
</child>
<child>
<object class="GtkCalendar" id="calendar_chats">
@@ -264,6 +276,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
+ <property name="position">0</property>
</packing>
</child>
<child>
@@ -285,11 +298,14 @@
</packing>
</child>
</object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
</child>
<child type="tab">
<object class="GtkLabel" id="label596">
<property name="visible">True</property>
- <property comments="Tab Label" name="label" translatable="yes">Conversations</property>
+ <property name="label" translatable="yes" comments="Tab Label">Conversations</property>
</object>
<packing>
<property name="position">1</property>
@@ -299,4 +315,12 @@
</object>
</child>
</object>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-go-forward</property>
+ </object>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="stock">gtk-go-back</property>
+ </object>
</interface>
diff --git a/libempathy-gtk/empathy-new-call-dialog.c b/libempathy-gtk/empathy-new-call-dialog.c
index 958bc49f7..3d51283b1 100644
--- a/libempathy-gtk/empathy-new-call-dialog.c
+++ b/libempathy-gtk/empathy-new-call-dialog.c
@@ -210,7 +210,7 @@ empathy_new_call_dialog_init (EmpathyNewCallDialog *dialog)
gtk_widget_show (priv->check_video);
/* add chat button */
- parent->button_action = gtk_button_new_with_mnemonic (_("_Call"));
+ parent->button_action = gtk_button_new_with_mnemonic (_("C_all"));
image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VOIP,
GTK_ICON_SIZE_BUTTON);
gtk_button_set_image (GTK_BUTTON (parent->button_action), image);
diff --git a/libempathy-gtk/empathy-persona-store.c b/libempathy-gtk/empathy-persona-store.c
index beb71c743..7997150bc 100644
--- a/libempathy-gtk/empathy-persona-store.c
+++ b/libempathy-gtk/empathy-persona-store.c
@@ -279,6 +279,7 @@ add_persona (EmpathyPersonaStore *self,
EmpathyPersonaStorePriv *priv;
GtkTreeIter iter;
GtkTreePath *path;
+ FolksPersonaStore *store;
EmpathyContact *contact;
const gchar *alias;
@@ -293,9 +294,12 @@ add_persona (EmpathyPersonaStore *self,
contact = empathy_contact_dup_from_tp_contact (tpf_persona_get_contact (
TPF_PERSONA (persona)));
+ store = folks_persona_get_store (persona);
gtk_list_store_insert_with_values (GTK_LIST_STORE (self), &iter, 0,
EMPATHY_PERSONA_STORE_COL_NAME, alias,
+ EMPATHY_PERSONA_STORE_COL_ACCOUNT_NAME,
+ folks_persona_store_get_display_name (store),
EMPATHY_PERSONA_STORE_COL_DISPLAY_ID,
folks_persona_get_display_id (persona),
EMPATHY_PERSONA_STORE_COL_PERSONA, persona,
@@ -419,6 +423,7 @@ update_persona (EmpathyPersonaStore *self,
}
else
{
+ FolksPersonaStore *store;
EmpathyContact *contact;
GtkTreeIter iter;
GdkPixbuf *pixbuf_avatar;
@@ -462,6 +467,7 @@ update_persona (EmpathyPersonaStore *self,
/* We still need to use EmpathyContact for the capabilities stuff */
contact = empathy_contact_dup_from_tp_contact (tpf_persona_get_contact (
TPF_PERSONA (persona)));
+ store = folks_persona_get_store (persona);
pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
32, 32);
@@ -472,6 +478,8 @@ update_persona (EmpathyPersonaStore *self,
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_ACCOUNT_NAME,
+ folks_persona_store_get_display_name (store),
EMPATHY_PERSONA_STORE_COL_DISPLAY_ID,
folks_persona_get_display_id (persona),
EMPATHY_PERSONA_STORE_COL_PRESENCE_TYPE,
@@ -734,6 +742,7 @@ set_up (EmpathyPersonaStore *self)
GDK_TYPE_PIXBUF, /* Avatar pixbuf */
G_TYPE_BOOLEAN, /* Avatar pixbuf visible */
G_TYPE_STRING, /* Name */
+ G_TYPE_STRING, /* Account name */
G_TYPE_STRING, /* Display ID */
G_TYPE_UINT, /* Presence type */
G_TYPE_STRING, /* Status string */
diff --git a/libempathy-gtk/empathy-persona-store.h b/libempathy-gtk/empathy-persona-store.h
index 8c3759985..892f8fa72 100644
--- a/libempathy-gtk/empathy-persona-store.h
+++ b/libempathy-gtk/empathy-persona-store.h
@@ -58,6 +58,7 @@ typedef enum
EMPATHY_PERSONA_STORE_COL_PIXBUF_AVATAR,
EMPATHY_PERSONA_STORE_COL_PIXBUF_AVATAR_VISIBLE,
EMPATHY_PERSONA_STORE_COL_NAME,
+ EMPATHY_PERSONA_STORE_COL_ACCOUNT_NAME,
EMPATHY_PERSONA_STORE_COL_DISPLAY_ID,
EMPATHY_PERSONA_STORE_COL_PRESENCE_TYPE,
EMPATHY_PERSONA_STORE_COL_STATUS,
diff --git a/libempathy-gtk/empathy-persona-view.c b/libempathy-gtk/empathy-persona-view.c
index ee679ec74..04777b1b6 100644
--- a/libempathy-gtk/empathy-persona-view.c
+++ b/libempathy-gtk/empathy-persona-view.c
@@ -356,6 +356,8 @@ constructed (GObject *object)
gtk_tree_view_column_set_cell_data_func (col, cell,
(GtkTreeCellDataFunc) text_cell_data_func, self, NULL);
+ /* We (ab)use the name and status properties here to display display ID and
+ * account name, respectively. Harmless. */
gtk_tree_view_column_add_attribute (col, cell,
"name", EMPATHY_PERSONA_STORE_COL_DISPLAY_ID);
gtk_tree_view_column_add_attribute (col, cell,
@@ -363,7 +365,7 @@ constructed (GObject *object)
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);
+ "status", EMPATHY_PERSONA_STORE_COL_ACCOUNT_NAME);
/* Audio Call Icon */
cell = empathy_cell_renderer_activatable_new ();
diff --git a/libempathy-gtk/empathy-share-my-desktop.c b/libempathy-gtk/empathy-share-my-desktop.c
index a630fc2a5..01a885ada 100644
--- a/libempathy-gtk/empathy-share-my-desktop.c
+++ b/libempathy-gtk/empathy-share-my-desktop.c
@@ -77,7 +77,7 @@ empathy_share_my_desktop_share_with_contact (EmpathyContact *contact)
NULL);
req = tp_account_channel_request_new (empathy_contact_get_account (contact),
- request, EMPATHY_DISPATCHER_CURRENT_TIME);
+ request, TP_USER_ACTION_TIME_CURRENT_TIME);
tp_account_channel_request_create_channel_async (req, NULL, NULL,
create_tube_channel_cb, NULL);
diff --git a/libempathy-gtk/empathy-theme-boxes.c b/libempathy-gtk/empathy-theme-boxes.c
index 24b7fc5b4..bf97f3f97 100644
--- a/libempathy-gtk/empathy-theme-boxes.c
+++ b/libempathy-gtk/empathy-theme-boxes.c
@@ -106,7 +106,7 @@ theme_boxes_pad_to_size (GdkPixbuf *pixbuf,
typedef struct {
GdkPixbuf *pixbuf;
- gchar *token;
+ gchar *filename;
} AvatarData;
static void
@@ -115,7 +115,7 @@ theme_boxes_avatar_cache_data_free (gpointer ptr)
AvatarData *data = ptr;
g_object_unref (data->pixbuf);
- g_free (data->token);
+ g_free (data->filename);
g_slice_free (AvatarData, data);
}
@@ -131,7 +131,7 @@ theme_boxes_get_avatar_pixbuf_with_cache (EmpathyContact *contact)
avatar = empathy_contact_get_avatar (contact);
data = g_object_get_data (G_OBJECT (contact), "chat-view-avatar-cache");
if (data) {
- if (avatar && !tp_strdiff (avatar->token, data->token)) {
+ if (avatar && !tp_strdiff (avatar->filename, data->filename)) {
/* We have the avatar in cache */
return data->pixbuf;
}
@@ -147,9 +147,11 @@ theme_boxes_get_avatar_pixbuf_with_cache (EmpathyContact *contact)
return NULL;
}
- /* Insert new pixbuf in cache */
+ /* Insert new pixbuf in cache. We store the filename as it's unique
+ * for each version of an avatar, so we can use it to perform change
+ * detection (as above). */
data = g_slice_new0 (AvatarData);
- data->token = g_strdup (avatar->token);
+ data->filename = g_strdup (avatar->filename);
data->pixbuf = pixbuf;
g_object_set_data_full (G_OBJECT (contact), "chat-view-avatar-cache",
diff --git a/libempathy-gtk/empathy-ui-utils.c b/libempathy-gtk/empathy-ui-utils.c
index 46a26d8f7..0a2d4f4f4 100644
--- a/libempathy-gtk/empathy-ui-utils.c
+++ b/libempathy-gtk/empathy-ui-utils.c
@@ -564,7 +564,7 @@ avatar_file_load_contents_cb (GObject *object,
{
GFile *file = G_FILE (object);
PixbufAvatarFromIndividualClosure *closure = user_data;
- char *data;
+ char *data = NULL;
gsize data_size;
struct SizeData size_data;
GError *error = NULL;
@@ -617,9 +617,10 @@ out:
void
empathy_pixbuf_avatar_from_individual_scaled_async (
FolksIndividual *individual,
- GAsyncReadyCallback callback,
gint width,
gint height,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
gpointer user_data)
{
GFile *avatar_file;
@@ -639,7 +640,7 @@ empathy_pixbuf_avatar_from_individual_scaled_async (
if (closure == NULL)
goto out;
- g_file_load_contents_async (avatar_file, NULL,
+ g_file_load_contents_async (avatar_file, cancellable,
avatar_file_load_contents_cb, closure);
g_object_unref (result);
diff --git a/libempathy-gtk/empathy-ui-utils.h b/libempathy-gtk/empathy-ui-utils.h
index 9c3ec6517..0b76d09a3 100644
--- a/libempathy-gtk/empathy-ui-utils.h
+++ b/libempathy-gtk/empathy-ui-utils.h
@@ -77,9 +77,10 @@ GdkPixbuf * empathy_pixbuf_from_data_and_mime (gchar *data,
gsize data_size,
gchar **mime_type);
void empathy_pixbuf_avatar_from_individual_scaled_async (FolksIndividual *individual,
- GAsyncReadyCallback callback,
gint width,
gint height,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
gpointer user_data);
GdkPixbuf * empathy_pixbuf_avatar_from_individual_scaled_finish (
FolksIndividual *individual,