[gnome-chess/chess-telepathy-networking-support-664946-rebase: 14/64] [libgames-contacts] Add classes to manage contacts and initiate networking
- From: Chandni Verma <vchandni src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-chess/chess-telepathy-networking-support-664946-rebase: 14/64] [libgames-contacts] Add classes to manage contacts and initiate networking
- Date: Sun, 23 Dec 2012 03:39:48 +0000 (UTC)
commit 74f9ad9f4dedbd0b2f58d666155acc9ecebd9f14
Author: Chandni Verma <chandniverma2112 gmail com>
Date: Tue May 29 09:00:08 2012 +0530
[libgames-contacts] Add classes to manage contacts and initiate networking
-GamesIndividualManager: Manages folks individuals
-GamesIndividualStore: Is a GtkTreeStore to model individuals
-GamesIndivudualGroups: Cache individual group expanders
-GamesIndividualStoreManager: Manages entry and removal of individuals from
GamesIndividualStore
-GamesContact: Are 1:1 with TpContact (or TpfPersona).
They are here to keep a track of capabilities not yet
provided by folks
-helper class for common utilities
configure.ac | 2 +
libgames-contacts/Makefile.am | 86 +-
libgames-contacts/games-contact.c | 1383 +++++++++++++++
libgames-contacts/games-contact.h | 122 ++
libgames-contacts/games-individual-groups.c | 287 ++++
libgames-contacts/games-individual-groups.dtd | 16 +
libgames-contacts/games-individual-groups.h | 38 +
libgames-contacts/games-individual-manager.c | 436 +++++
libgames-contacts/games-individual-manager.h | 67 +
libgames-contacts/games-individual-store-manager.c | 321 ++++
libgames-contacts/games-individual-store-manager.h | 61 +
libgames-contacts/games-individual-store.c | 1756 ++++++++++++++++++++
libgames-contacts/games-individual-store.h | 165 ++
libgames-contacts/games-ui-utils.c | 1091 ++++++++++++
libgames-contacts/games-ui-utils.h | 143 ++
15 files changed, 5972 insertions(+), 2 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 06b4d0f..ba138a3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,8 @@
AC_INIT(gnome-chess, 3.7.2)
LT_INIT
+AC_CONFIG_HEADERS([config.h])
+
AM_INIT_AUTOMAKE([1.11 no-dist-gzip dist-xz foreign])
AM_SILENT_RULES([yes])
AM_MAINTAINER_MODE
diff --git a/libgames-contacts/Makefile.am b/libgames-contacts/Makefile.am
index c4d3364..b2e7788 100644
--- a/libgames-contacts/Makefile.am
+++ b/libgames-contacts/Makefile.am
@@ -1,11 +1,37 @@
noinst_LTLIBRARIES =
+BUILT_SOURCES =
if ENABLE_NETWORKING
+BUILT_SOURCES += \
+ games-enum-types.c \
+ games-enum-types.h
+
noinst_LTLIBRARIES += libgames-contacts.la
endif
-libgames_contacts_la_SOURCES =
-
+networking_headers = \
+ games-individual-manager.h \
+ games-individual-store.h \
+ games-individual-store-manager.h \
+ games-individual-groups.h \
+ games-ui-utils.h \
+ games-contact.h
+
+networking_sources = \
+ $(networking_headers) \
+ games-individual-manager.c \
+ games-individual-store.c \
+ games-individual-store-manager.c \
+ games-individual-groups.c \
+ games-ui-utils.c \
+ games-contact.c
+
+libgames_contacts_la_SOURCES = \
+ $(networking_sources)
+
+nodist_libgames_contacts_la_SOURCES = \
+ $(BUILT_SOURCES)
+
libgames_contacts_la_CPPFLAGS = \
-DPKGDATADIR="\"$(pkgdatadir)\"" \
-DPREFIX="\"$(prefix)\"" \
@@ -31,12 +57,68 @@ libgames_contacts_la_LIBADD = \
EXTRA_DIST = \
GamesContacts-1.0.vapi
+if ENABLE_NETWORKING
+dtddir = $(pkgdatadir)
+dtd_DATA = \
+ games-individual-groups.dtd
+
+EXTRA_DIST += \
+ $(dtd_DATA)
+endif #ENABLE_NETWORKING
+
+
CLEANFILES =
+if ENABLE_NETWORKING
+CLEANFILES += \
+ $(BUILT_SOURCES) \
+ stamp-games-enum-types.h
+endif #ENABLE_NETWORKING
DISTCLEANFILES =
+# Generation of Enum types
+games-enum-types.h: stamp-games-enum-types.h
+ $(AM_V_GEN)true
+stamp-games-enum-types.h: $(networking_headers) Makefile
+ $(AM_V_GEN)(cd $(srcdir) \
+ && glib-mkenums \
+ --fhead "#ifndef __GAMES_ENUM_TYPES_H__\n" \
+ --fhead "#define __GAMES_ENUM_TYPES_H__ 1\n\n" \
+ --fhead "#include <glib-object.h>\n\n" \
+ --fhead "G_BEGIN_DECLS\n\n" \
+ --ftail "G_END_DECLS\n\n" \
+ --ftail "#endif /* __GAMES_ENUM_TYPES_H__ */\n" \
+ --fprod "#include \"@filename \"\n" \
+ --eprod "#define GAMES_TYPE_ ENUMSHORT@ @enum_name _get_type()\n" \
+ --eprod "GType @enum_name _get_type (void);\n" \
+ $(networking_headers) ) > xgen-gth \
+ && (cmp -s xgen-gth games-enum-type.h || cp xgen-gth games-enum-types.h) \
+ && rm -f xgen-gth \
+ && echo timestamp > $(@F)
+
+games-enum-types.c: $(networking_headers) Makefile
+ $(AM_V_GEN)(cd $(srcdir) \
+ && glib-mkenums \
+ --fhead "#include <config.h>\n" \
+ --fhead "#include <glib-object.h>\n" \
+ --fhead "#include \"games-enum-types.h\"\n\n" \
+ --fprod "\n/* enumerations from \"@filename \" */" \
+ --vhead "static const G Type@Value _ enum_name@_values[] = {" \
+ --vprod " { @VALUENAME@, \"@VALUENAME \", \"@valuenick \" }," \
+ --vtail " { 0, NULL, NULL }\n};\n\n" \
+ --vtail "GType\n enum_name@_get_type (void)\n{\n" \
+ --vtail " static GType type = 0;\n\n" \
+ --vtail " if (!type)\n" \
+ --vtail " type = g_ type@_register_static (\"@EnumName \", _ enum_name@_values);\n\n" \
+ --vtail " return type;\n}\n\n" \
+ $(networking_headers) ) > xgen-gtc \
+ && cp xgen-gtc $(@F) \
+ && rm -f xgen-gtc
+
+
@INTLTOOL_XML_NOMERGE_RULE@
@GSETTINGS_RULES@
-include $(top_srcdir)/git.mk
+
diff --git a/libgames-contacts/games-contact.c b/libgames-contacts/games-contact.c
new file mode 100644
index 0000000..d7b7ad3
--- /dev/null
+++ b/libgames-contacts/games-contact.c
@@ -0,0 +1,1383 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/*
+ * games-contact.c: A wrapper for TpContact to keep a track of contacts'
+ * gaming capabilities since folks doesn't directly provide them for TpfPersona
+ * (bgo#626179). As TpContact, GamesContact is one for each persona for an
+ * individual, logically.
+ *
+ * Copyright (C) 2012 Chandni Verma
+ *
+ * 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
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <telepathy-glib/account-manager.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+
+#include <folks/folks.h>
+#include <folks/folks-telepathy.h>
+
+#include "games-contact.h"
+#include "games-individual-manager.h"
+#include "games-enum-types.h"
+
+/* Much of code here is a simplified adaptation of the way contacts are
+ * wrapped in Empathy, a chat application using Folks.
+ * (https://live.gnome.org/Empathy)
+ * Copyright (C) 2007-2009 Collabora Ltd.
+ */
+
+typedef struct {
+ TpContact *tp_contact;
+ TpAccount *account;
+ FolksPersona *persona;
+ gchar *id;
+ gchar *alias;
+ GamesAvatar *avatar;
+ TpConnectionPresenceType presence;
+ guint handle;
+ GamesCapabilities capabilities;
+ gboolean is_user;
+} GamesContactPriv;
+
+static void contact_finalize (GObject *object);
+static void contact_get_property (GObject *object, guint param_id,
+ GValue *value, GParamSpec *pspec);
+static void contact_set_property (GObject *object, guint param_id,
+ const GValue *value, GParamSpec *pspec);
+
+static void set_capabilities_from_tp_caps (GamesContact *self,
+ TpCapabilities *caps);
+
+static void contact_set_avatar (GamesContact *contact,
+ GamesAvatar *avatar);
+static void contact_set_avatar_from_tp_contact (GamesContact *contact);
+
+G_DEFINE_TYPE (GamesContact, games_contact, G_TYPE_OBJECT);
+
+enum
+{
+ PROP_0,
+ PROP_TP_CONTACT,
+ PROP_ACCOUNT,
+ PROP_PERSONA,
+ PROP_ID,
+ PROP_ALIAS,
+ PROP_LOGGED_ALIAS,
+ PROP_AVATAR,
+ PROP_PRESENCE,
+ PROP_PRESENCE_MESSAGE,
+ PROP_HANDLE,
+ PROP_CAPABILITIES,
+ PROP_IS_USER,
+};
+
+enum {
+ PRESENCE_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+/* TpContact* -> GamesContact*, both borrowed ref */
+static GHashTable *contacts_table = NULL;
+
+static void
+tp_contact_notify_cb (TpContact *tp_contact,
+ GParamSpec *param,
+ GObject *contact)
+{
+ GamesContactPriv *priv = (GAMES_CONTACT (contact))->priv;
+
+ /* Forward property notifications */
+ if (!tp_strdiff (param->name, "alias"))
+ g_object_notify (contact, "alias");
+ else if (!tp_strdiff (param->name, "presence-type")) {
+ TpConnectionPresenceType presence;
+
+ presence = games_contact_get_presence (GAMES_CONTACT (contact));
+ g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence,
+ priv->presence);
+ priv->presence = presence;
+ g_object_notify (contact, "presence");
+ }
+ else if (!tp_strdiff (param->name, "identifier"))
+ g_object_notify (contact, "id");
+ else if (!tp_strdiff (param->name, "handle"))
+ g_object_notify (contact, "handle");
+ else if (!tp_strdiff (param->name, "capabilities"))
+ {
+ set_capabilities_from_tp_caps (GAMES_CONTACT (contact),
+ tp_contact_get_capabilities (tp_contact));
+ }
+ else if (!tp_strdiff (param->name, "avatar-file"))
+ {
+ contact_set_avatar_from_tp_contact (GAMES_CONTACT (contact));
+ }
+}
+
+static void
+folks_persona_notify_cb (FolksPersona *folks_persona,
+ GParamSpec *param,
+ GObject *contact)
+{
+ if (!tp_strdiff (param->name, "presence-message"))
+ g_object_notify (contact, "presence-message");
+}
+
+static void
+contact_dispose (GObject *object)
+{
+ GamesContactPriv *priv = (GAMES_CONTACT (object))->priv;
+
+ if (priv->tp_contact != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (priv->tp_contact,
+ tp_contact_notify_cb, object);
+ }
+ tp_clear_object (&priv->tp_contact);
+
+ if (priv->account)
+ g_object_unref (priv->account);
+ priv->account = NULL;
+
+ if (priv->persona)
+ {
+ g_signal_handlers_disconnect_by_func (priv->persona,
+ folks_persona_notify_cb, object);
+ g_object_unref (priv->persona);
+ }
+ priv->persona = NULL;
+
+ if (priv->avatar != NULL)
+ {
+ games_avatar_unref (priv->avatar);
+ priv->avatar = NULL;
+ }
+
+ G_OBJECT_CLASS (games_contact_parent_class)->dispose (object);
+}
+
+static void
+contact_constructed (GObject *object)
+{
+ GamesContact *contact = (GamesContact *) object;
+ GamesContactPriv *priv = contact->priv;
+ TpContact *self_contact;
+
+ if (priv->tp_contact == NULL)
+ return;
+
+ priv->presence = games_contact_get_presence (contact);
+
+ set_capabilities_from_tp_caps (contact,
+ tp_contact_get_capabilities (priv->tp_contact));
+
+ contact_set_avatar_from_tp_contact (contact);
+
+ /* Set is-user property. We could use handles too */
+ self_contact = tp_connection_get_self_contact (
+ tp_contact_get_connection (priv->tp_contact));
+ games_contact_set_is_user (contact, self_contact == priv->tp_contact);
+
+ g_signal_connect (priv->tp_contact, "notify",
+ G_CALLBACK (tp_contact_notify_cb), contact);
+}
+
+static void
+games_contact_class_init (GamesContactClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = contact_finalize;
+ object_class->dispose = contact_dispose;
+ object_class->get_property = contact_get_property;
+ object_class->set_property = contact_set_property;
+ object_class->constructed = contact_constructed;
+
+ g_object_class_install_property (object_class,
+ PROP_TP_CONTACT,
+ g_param_spec_object ("tp-contact",
+ "TpContact",
+ "The TpContact associated with the contact",
+ TP_TYPE_CONTACT,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_ACCOUNT,
+ g_param_spec_object ("account",
+ "The account",
+ "The account associated with the contact",
+ TP_TYPE_ACCOUNT,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_PERSONA,
+ g_param_spec_object ("persona",
+ "Persona",
+ "The FolksPersona associated with the contact",
+ FOLKS_TYPE_PERSONA,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_ID,
+ g_param_spec_string ("id",
+ "Contact id",
+ "String identifying contact",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_ALIAS,
+ g_param_spec_string ("alias",
+ "Contact alias",
+ "An alias for the contact",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_AVATAR,
+ g_param_spec_boxed ("avatar",
+ "Avatar image",
+ "The avatar image",
+ GAMES_TYPE_AVATAR,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_PRESENCE,
+ g_param_spec_uint ("presence",
+ "Contact presence",
+ "Presence of contact",
+ TP_CONNECTION_PRESENCE_TYPE_UNSET,
+ NUM_TP_CONNECTION_PRESENCE_TYPES,
+ TP_CONNECTION_PRESENCE_TYPE_UNSET,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_PRESENCE_MESSAGE,
+ g_param_spec_string ("presence-message",
+ "Contact presence message",
+ "Presence message of contact",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_HANDLE,
+ g_param_spec_uint ("handle",
+ "Contact Handle",
+ "The handle of the contact",
+ 0,
+ G_MAXUINT,
+ 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_CAPABILITIES,
+ g_param_spec_flags ("capabilities",
+ "Contact Capabilities",
+ "Capabilities of the contact",
+ GAMES_TYPE_CAPABILITIES,
+ GAMES_CAPABILITIES_UNKNOWN,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_IS_USER,
+ g_param_spec_boolean ("is-user",
+ "Contact is-user",
+ "Is contact the user",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ signals[PRESENCE_CHANGED] =
+ g_signal_new ("presence-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 2, G_TYPE_UINT,
+ G_TYPE_UINT);
+
+ g_type_class_add_private (object_class, sizeof (GamesContactPriv));
+}
+
+static void
+games_contact_init (GamesContact *contact)
+{
+ GamesContactPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (contact,
+ GAMES_TYPE_CONTACT, GamesContactPriv);
+
+ contact->priv = priv;
+}
+
+static void
+contact_finalize (GObject *object)
+{
+ GamesContactPriv *priv;
+
+ priv = (GAMES_CONTACT (object))->priv;
+
+ g_debug ("finalize: %p", object);
+
+ g_free (priv->alias);
+ g_free (priv->id);
+
+ G_OBJECT_CLASS (games_contact_parent_class)->finalize (object);
+}
+
+static void
+games_contact_set_capabilities (GamesContact *contact,
+ GamesCapabilities capabilities)
+{
+ GamesContactPriv *priv;
+
+ g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+ priv = contact->priv;
+
+ if (priv->capabilities == capabilities)
+ return;
+
+ priv->capabilities = capabilities;
+
+ g_object_notify (G_OBJECT (contact), "capabilities");
+}
+
+static void
+games_contact_set_id (GamesContact *contact,
+ const gchar *id)
+{
+ GamesContactPriv *priv;
+
+ g_return_if_fail (GAMES_IS_CONTACT (contact));
+ g_return_if_fail (id != NULL);
+
+ priv = contact->priv;
+
+ /* We temporally ref the contact because it could be destroyed
+ * during the signal emition */
+ g_object_ref (contact);
+ if (tp_strdiff (id, priv->id))
+ {
+ g_free (priv->id);
+ priv->id = g_strdup (id);
+
+ g_object_notify (G_OBJECT (contact), "id");
+
+ /* And since alias defaults to ID */
+ if (priv->alias == NULL || g_strcmp0 (priv->alias, "") == 0)
+ g_object_notify (G_OBJECT (contact), "alias");
+ }
+
+ g_object_unref (contact);
+}
+
+static void
+games_contact_set_presence (GamesContact *contact,
+ TpConnectionPresenceType presence)
+{
+ GamesContactPriv *priv;
+ TpConnectionPresenceType old_presence;
+
+ g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+ priv = contact->priv;
+
+ if (presence == priv->presence)
+ return;
+
+ old_presence = priv->presence;
+ priv->presence = presence;
+
+ g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence, old_presence);
+
+ g_object_notify (G_OBJECT (contact), "presence");
+}
+
+static void
+games_contact_set_presence_message (GamesContact *contact,
+ const gchar *message)
+{
+ GamesContactPriv *priv = contact->priv;
+
+ g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+ if (priv->persona != NULL)
+ {
+ folks_presence_details_set_presence_message (
+ FOLKS_PRESENCE_DETAILS (priv->persona), message);
+ }
+}
+
+static void
+games_contact_set_handle (GamesContact *contact,
+ guint handle)
+{
+ GamesContactPriv *priv;
+
+ g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+ priv = contact->priv;
+
+ g_object_ref (contact);
+ if (handle != priv->handle)
+ {
+ priv->handle = handle;
+ g_object_notify (G_OBJECT (contact), "handle");
+ }
+ g_object_unref (contact);
+}
+
+static void
+contact_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GamesContact *contact = GAMES_CONTACT (object);
+
+ switch (param_id)
+ {
+ case PROP_TP_CONTACT:
+ g_value_set_object (value, games_contact_get_tp_contact (contact));
+ break;
+ case PROP_ACCOUNT:
+ g_value_set_object (value, games_contact_get_account (contact));
+ break;
+ case PROP_PERSONA:
+ g_value_set_object (value, games_contact_get_persona (contact));
+ break;
+ case PROP_ID:
+ g_value_set_string (value, games_contact_get_id (contact));
+ break;
+ case PROP_ALIAS:
+ g_value_set_string (value, games_contact_get_alias (contact));
+ break;
+ case PROP_AVATAR:
+ g_value_set_boxed (value, games_contact_get_avatar (contact));
+ break;
+ case PROP_PRESENCE:
+ g_value_set_uint (value, games_contact_get_presence (contact));
+ break;
+ case PROP_PRESENCE_MESSAGE:
+ g_value_set_string (value, games_contact_get_presence_message (contact));
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, games_contact_get_handle (contact));
+ break;
+ case PROP_CAPABILITIES:
+ g_value_set_flags (value, games_contact_get_capabilities (contact));
+ break;
+ case PROP_IS_USER:
+ g_value_set_boolean (value, games_contact_is_user (contact));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+contact_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GamesContact *contact = GAMES_CONTACT (object);
+ GamesContactPriv *priv = contact->priv;
+
+ switch (param_id)
+ {
+ case PROP_TP_CONTACT:
+ priv->tp_contact = g_value_dup_object (value);
+ break;
+ case PROP_ACCOUNT:
+ g_assert (priv->account == NULL);
+ priv->account = g_value_dup_object (value);
+ break;
+ case PROP_PERSONA:
+ games_contact_set_persona (contact, g_value_get_object (value));
+ break;
+ case PROP_ID:
+ games_contact_set_id (contact, g_value_get_string (value));
+ break;
+ case PROP_ALIAS:
+ games_contact_set_alias (contact, g_value_get_string (value));
+ break;
+ case PROP_PRESENCE:
+ games_contact_set_presence (contact, g_value_get_uint (value));
+ break;
+ case PROP_PRESENCE_MESSAGE:
+ games_contact_set_presence_message (contact, g_value_get_string (value));
+ break;
+ case PROP_HANDLE:
+ games_contact_set_handle (contact, g_value_get_uint (value));
+ break;
+ case PROP_CAPABILITIES:
+ games_contact_set_capabilities (contact, g_value_get_flags (value));
+ break;
+ case PROP_IS_USER:
+ games_contact_set_is_user (contact, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+remove_tp_contact (gpointer data,
+ GObject *object)
+{
+ g_hash_table_remove (contacts_table, data);
+}
+
+static GamesContact *
+games_contact_new (TpContact *tp_contact)
+{
+ GamesContact *retval;
+
+ g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
+
+ retval = g_object_new (GAMES_TYPE_CONTACT,
+ "tp-contact", tp_contact,
+ NULL);
+
+ g_object_weak_ref (G_OBJECT (retval), remove_tp_contact, tp_contact);
+
+ return retval;
+}
+
+TpContact *
+games_contact_get_tp_contact (GamesContact *contact)
+{
+ GamesContactPriv *priv;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+ priv = contact->priv;
+
+ return priv->tp_contact;
+}
+
+const gchar *
+games_contact_get_id (GamesContact *contact)
+{
+ GamesContactPriv *priv;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+ priv = contact->priv;
+
+ if (priv->tp_contact != NULL)
+ return tp_contact_get_identifier (priv->tp_contact);
+
+ return priv->id;
+}
+
+const gchar *
+games_contact_get_alias (GamesContact *contact)
+{
+ GamesContactPriv *priv;
+ const gchar *alias = NULL;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+ priv = contact->priv;
+
+ if (priv->alias != NULL && g_strcmp0 (priv->alias, "") != 0)
+ alias = priv->alias;
+ else if (priv->tp_contact != NULL)
+ alias = tp_contact_get_alias (priv->tp_contact);
+
+ if (alias != NULL && g_strcmp0 (alias, "") != 0)
+ return alias;
+ else
+ return games_contact_get_id (contact);
+}
+
+void
+games_contact_set_alias (GamesContact *contact,
+ const gchar *alias)
+{
+ GamesContactPriv *priv;
+ FolksPersona *persona;
+
+ g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+ priv = contact->priv;
+
+ g_object_ref (contact);
+
+ /* Set the alias on the persona */
+ persona = games_contact_get_persona (contact);
+ /* We are not creating contacts hence all our GamesContacts have to have
+ * personas beforehand */
+ g_assert (persona != NULL);
+
+ if (FOLKS_IS_ALIAS_DETAILS (persona))
+ {
+ g_debug ("Setting alias for contact %s to %s",
+ games_contact_get_id (contact), alias);
+
+ folks_alias_details_set_alias (FOLKS_ALIAS_DETAILS (persona), alias);
+ }
+
+ if (tp_strdiff (alias, priv->alias))
+ {
+ g_free (priv->alias);
+ priv->alias = g_strdup (alias);
+ g_object_notify (G_OBJECT (contact), "alias");
+ }
+
+ g_object_unref (contact);
+}
+
+GamesAvatar *
+games_contact_get_avatar (GamesContact *contact)
+{
+ GamesContactPriv *priv;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+ priv = contact->priv;
+
+ return priv->avatar;
+}
+
+static void
+contact_set_avatar (GamesContact *contact,
+ GamesAvatar *avatar)
+{
+ GamesContactPriv *priv;
+
+ g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+ priv = contact->priv;
+
+ if (priv->avatar == avatar)
+ return;
+
+ if (priv->avatar)
+ {
+ games_avatar_unref (priv->avatar);
+ priv->avatar = NULL;
+ }
+
+ if (avatar)
+ priv->avatar = games_avatar_ref (avatar);
+
+ g_object_notify (G_OBJECT (contact), "avatar");
+}
+
+TpAccount *
+games_contact_get_account (GamesContact *contact)
+{
+ GamesContactPriv *priv;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+ priv = contact->priv;
+
+ if (priv->account == NULL && priv->tp_contact != NULL)
+ {
+ TpConnection *connection;
+
+ /* FIXME: This assume the account manager already exists */
+ connection = tp_contact_get_connection (priv->tp_contact);
+ priv->account =
+ g_object_ref (tp_connection_get_account (connection));
+ }
+
+ return priv->account;
+}
+
+FolksPersona *
+games_contact_get_persona (GamesContact *contact)
+{
+ GamesContactPriv *priv;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+ priv = contact->priv;
+
+ if (priv->persona == NULL && priv->tp_contact != NULL)
+ {
+ TpfPersona *persona;
+
+ persona = tpf_persona_dup_for_contact (priv->tp_contact);
+ if (persona != NULL)
+ {
+ games_contact_set_persona (contact, (FolksPersona *) persona);
+ g_object_unref (persona);
+ }
+ }
+
+ return priv->persona;
+}
+
+void
+games_contact_set_persona (GamesContact *contact,
+ FolksPersona *persona)
+{
+ GamesContactPriv *priv;
+
+ g_return_if_fail (GAMES_IS_CONTACT (contact));
+ g_return_if_fail (TPF_IS_PERSONA (persona));
+
+ priv = contact->priv;
+
+ if (persona == priv->persona)
+ return;
+
+ if (priv->persona != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (priv->persona,
+ folks_persona_notify_cb, contact);
+ g_object_unref (priv->persona);
+ }
+ priv->persona = g_object_ref (persona);
+
+ g_signal_connect (priv->persona, "notify",
+ G_CALLBACK (folks_persona_notify_cb), contact);
+
+ g_object_notify (G_OBJECT (contact), "persona");
+
+ /* Set the persona's alias, since ours could've been set using
+ * games_contact_set_alias() before we had a persona; this happens when
+ * adding a contact. */
+ if (priv->alias != NULL)
+ games_contact_set_alias (contact, priv->alias);
+}
+
+TpConnection *
+games_contact_get_connection (GamesContact *contact)
+{
+ GamesContactPriv *priv;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+ priv = contact->priv;
+
+ if (priv->tp_contact != NULL)
+ return tp_contact_get_connection (priv->tp_contact);
+
+ return NULL;
+}
+
+TpConnectionPresenceType
+games_contact_get_presence (GamesContact *contact)
+{
+ GamesContactPriv *priv;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact),
+ TP_CONNECTION_PRESENCE_TYPE_UNSET);
+
+ priv = contact->priv;
+
+ if (priv->tp_contact != NULL)
+ return tp_contact_get_presence_type (priv->tp_contact);
+
+ return priv->presence;
+}
+
+const gchar *
+games_contact_get_presence_message (GamesContact *contact)
+{
+ GamesContactPriv *priv;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+ priv = contact->priv;
+
+ if (priv->persona != NULL)
+ return folks_presence_details_get_presence_message (
+ FOLKS_PRESENCE_DETAILS (priv->persona));
+
+ if (priv->tp_contact != NULL)
+ return tp_contact_get_presence_message (priv->tp_contact);
+
+ return NULL;
+}
+
+guint
+games_contact_get_handle (GamesContact *contact)
+{
+ GamesContactPriv *priv;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), 0);
+
+ priv = contact->priv;
+
+ if (priv->tp_contact != NULL)
+ return tp_contact_get_handle (priv->tp_contact);
+
+ return priv->handle;
+}
+
+GamesCapabilities
+games_contact_get_capabilities (GamesContact *contact)
+{
+ GamesContactPriv *priv;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), 0);
+
+ priv = contact->priv;
+
+ return priv->capabilities;
+}
+
+gboolean
+games_contact_is_user (GamesContact *contact)
+{
+ GamesContactPriv *priv;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), FALSE);
+
+ priv = contact->priv;
+
+ return priv->is_user;
+}
+
+void
+games_contact_set_is_user (GamesContact *contact,
+ gboolean is_user)
+{
+ GamesContactPriv *priv;
+
+ g_return_if_fail (GAMES_IS_CONTACT (contact));
+
+ priv = contact->priv;
+
+ if (priv->is_user == is_user)
+ return;
+
+ priv->is_user = is_user;
+
+ g_object_notify (G_OBJECT (contact), "is-user");
+}
+
+gboolean
+games_contact_is_online (GamesContact *contact)
+{
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), FALSE);
+
+ switch (games_contact_get_presence (contact))
+ {
+ case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
+ case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
+ case TP_CONNECTION_PRESENCE_TYPE_ERROR:
+ return FALSE;
+ /* Contacts without presence are considered online so we can display IRC
+ * contacts in rooms. */
+ case TP_CONNECTION_PRESENCE_TYPE_UNSET:
+ case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
+ case TP_CONNECTION_PRESENCE_TYPE_AWAY:
+ case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
+ case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
+ case TP_CONNECTION_PRESENCE_TYPE_BUSY:
+ default:
+ return TRUE;
+ }
+}
+
+const gchar *
+games_presence_get_default_message (TpConnectionPresenceType presence)
+{
+ switch (presence)
+ {
+ case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
+ return _("Available");
+ case TP_CONNECTION_PRESENCE_TYPE_BUSY:
+ return _("Busy");
+ case TP_CONNECTION_PRESENCE_TYPE_AWAY:
+ case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
+ return _("Away");
+ case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
+ return _("Invisible");
+ case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
+ return _("Offline");
+ case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
+ /* translators: presence type is unknown */
+ return C_("presence", "Unknown");
+ case TP_CONNECTION_PRESENCE_TYPE_UNSET:
+ case TP_CONNECTION_PRESENCE_TYPE_ERROR:
+ default:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+const gchar *
+games_contact_get_status (GamesContact *contact)
+{
+ const gchar *message;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), "");
+
+ message = games_contact_get_presence_message (contact);
+ if (message != NULL && g_strcmp0 (message, "") != 0)
+ return message;
+
+ return games_presence_get_default_message (
+ games_contact_get_presence (contact));
+}
+
+gboolean
+games_contact_can_play_glchess (GamesContact *contact)
+{
+ GamesContactPriv *priv;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), FALSE);
+
+ priv = contact->priv;
+
+ return priv->capabilities & GAMES_CAPABILITIES_TUBE_GLCHESS;
+}
+
+gboolean
+games_contact_can_do_action (GamesContact *self,
+ GamesActionType action_type)
+{
+ gboolean sensitivity = FALSE;
+
+ switch (action_type)
+ {
+ case GAMES_ACTION_CHAT:
+ sensitivity = TRUE;
+ break;
+ case GAMES_ACTION_PLAY_GLCHESS:
+ sensitivity = games_contact_can_play_glchess (self);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return (sensitivity ? TRUE : FALSE);
+}
+
+GType
+games_avatar_get_type (void)
+{
+ static GType type_id = 0;
+
+ if (!type_id)
+ {
+ type_id = g_boxed_type_register_static ("GamesAvatar",
+ (GBoxedCopyFunc) games_avatar_ref,
+ (GBoxedFreeFunc) games_avatar_unref);
+ }
+
+ return type_id;
+}
+
+/**
+ * games_avatar_new:
+ * @data: the avatar data
+ * @len: the size of avatar data
+ * @format: the mime type of the avatar image
+ * @filename: the filename where the avatar is stored in cache
+ *
+ * Create a #GamesAvatar from the provided data.
+ *
+ * Returns: a new #GamesAvatar
+ */
+GamesAvatar *
+games_avatar_new (const guchar *data,
+ gsize len,
+ const gchar *format,
+ const gchar *filename)
+{
+ GamesAvatar *avatar;
+
+ avatar = g_slice_new0 (GamesAvatar);
+ avatar->data = g_memdup (data, len);
+ avatar->len = len;
+ avatar->format = g_strdup (format);
+ avatar->filename = g_strdup (filename);
+ avatar->refcount = 1;
+
+ return avatar;
+}
+
+void
+games_avatar_unref (GamesAvatar *avatar)
+{
+ g_return_if_fail (avatar != NULL);
+
+ avatar->refcount--;
+ if (avatar->refcount == 0)
+ {
+ g_free (avatar->data);
+ g_free (avatar->format);
+ g_free (avatar->filename);
+ g_slice_free (GamesAvatar, avatar);
+ }
+}
+
+GamesAvatar *
+games_avatar_ref (GamesAvatar *avatar)
+{
+ g_return_val_if_fail (avatar != NULL, NULL);
+
+ avatar->refcount++;
+
+ return avatar;
+}
+
+/**
+ * games_avatar_save_to_file:
+ * @avatar: the avatar
+ * @filename: name of a file to write avatar to
+ * @error: return location for a GError, or NULL
+ *
+ * Save the avatar to a file named filename
+ *
+ * Returns: %TRUE on success, %FALSE if an error occurred
+ */
+gboolean
+games_avatar_save_to_file (GamesAvatar *self,
+ const gchar *filename,
+ GError **error)
+{
+ return g_file_set_contents (filename, (const gchar *) self->data, self->len,
+ error);
+}
+
+/**
+ * games_contact_equal:
+ * @contact1: an #GamesContact
+ * @contact2: an #GamesContact
+ *
+ * Returns FALSE if one of the contacts is NULL but the other is not.
+ * Otherwise returns TRUE if both pointer are equal or if they bith
+ * refer to the same id.
+ * It's only necessary to call this function if your contact objects
+ * come from logs where contacts are created dynamically and comparing
+ * pointers is not enough.
+ */
+gboolean
+games_contact_equal (gconstpointer contact1,
+ gconstpointer contact2)
+{
+ GamesContact *c1;
+ GamesContact *c2;
+ const gchar *id1;
+ const gchar *id2;
+
+ if ((contact1 == NULL) != (contact2 == NULL)) {
+ return FALSE;
+ }
+ if (contact1 == contact2) {
+ return TRUE;
+ }
+ c1 = GAMES_CONTACT (contact1);
+ c2 = GAMES_CONTACT (contact2);
+ id1 = games_contact_get_id (c1);
+ id2 = games_contact_get_id (c2);
+ if (!tp_strdiff (id1, id2)) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static GamesCapabilities
+tp_caps_to_capabilities (TpCapabilities *caps)
+{
+ GamesCapabilities capabilities = 0;
+
+ if (tp_capabilities_supports_dbus_tubes (caps, TP_HANDLE_TYPE_CONTACT,
+ "org.gnome.glchess"))
+ capabilities |= GAMES_CAPABILITIES_TUBE_GLCHESS;
+
+ return capabilities;
+}
+
+static void
+set_capabilities_from_tp_caps (GamesContact *self,
+ TpCapabilities *caps)
+{
+ GamesCapabilities capabilities;
+
+ if (caps == NULL)
+ return;
+
+ capabilities = tp_caps_to_capabilities (caps);
+ games_contact_set_capabilities (self, capabilities);
+}
+
+static void
+contact_set_avatar_from_tp_contact (GamesContact *contact)
+{
+ GamesContactPriv *priv = contact->priv;
+ const gchar *mime;
+ GFile *file;
+
+ mime = tp_contact_get_avatar_mime_type (priv->tp_contact);
+ file = tp_contact_get_avatar_file (priv->tp_contact);
+
+ if (file != NULL)
+ {
+ GamesAvatar *avatar;
+ gchar *data;
+ gsize len;
+ gchar *path;
+ GError *error = NULL;
+
+ if (!g_file_load_contents (file, NULL, &data, &len, NULL, &error))
+ {
+ g_debug ("Failed to load avatar: %s", error->message);
+
+ g_error_free (error);
+ contact_set_avatar (contact, NULL);
+ return;
+ }
+
+ path = g_file_get_path (file);
+
+ avatar = games_avatar_new ((guchar *) data, len, mime, path);
+
+ contact_set_avatar (contact, avatar);
+ games_avatar_unref (avatar);
+ g_free (path);
+ g_free (data);
+ }
+ else
+ {
+ contact_set_avatar (contact, NULL);
+ }
+}
+
+GamesContact *
+games_contact_dup_from_tp_contact (TpContact *tp_contact)
+{
+ GamesContact *contact = NULL;
+
+ g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
+
+ if (contacts_table == NULL)
+ contacts_table = g_hash_table_new (g_direct_hash, g_direct_equal);
+ else
+ contact = g_hash_table_lookup (contacts_table, tp_contact);
+
+ if (contact == NULL)
+ {
+ contact = games_contact_new (tp_contact);
+
+ /* The hash table does not keep any ref.
+ * contact keeps a ref to tp_contact, and is removed from the table in
+ * contact_dispose() */
+ g_hash_table_insert (contacts_table, tp_contact, contact);
+ }
+ else
+ {
+ g_object_ref (contact);
+ }
+
+ return contact;
+}
+
+static int
+presence_cmp_func (GamesContact *a,
+ GamesContact *b)
+{
+ FolksPresenceDetails *presence_a, *presence_b;
+
+ presence_a = FOLKS_PRESENCE_DETAILS (games_contact_get_persona (a));
+ presence_b = FOLKS_PRESENCE_DETAILS (games_contact_get_persona (b));
+
+ /* We negate the result because we're sorting in reverse order (i.e. such that
+ * the Personas with the highest presence are at the beginning of the list. */
+ return -folks_presence_details_typecmp (
+ folks_presence_details_get_presence_type (presence_a),
+ folks_presence_details_get_presence_type (presence_b));
+}
+
+static gint
+glchess_tube_cmp_func (GamesContact *a,
+ GamesContact *b)
+{
+ gboolean can_play_glchess_a, can_play_glchess_b;
+
+ can_play_glchess_a = games_contact_can_play_glchess (a);
+ can_play_glchess_b = games_contact_can_play_glchess (b);
+
+ if (can_play_glchess_a == can_play_glchess_b)
+ /* Fallback to sort by presence to break ties */
+ return presence_cmp_func (a, b);
+ else if (can_play_glchess_a)
+ return -1;
+ else
+ return 1;
+}
+
+static GCompareFunc
+get_sort_func_for_action (GamesActionType action_type)
+{
+ switch (action_type)
+ {
+ case GAMES_ACTION_CHAT:
+ return (GCompareFunc) presence_cmp_func;
+ case GAMES_ACTION_PLAY_GLCHESS:
+ return (GCompareFunc) glchess_tube_cmp_func;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/**
+ * games_contact_dup_best_for_action:
+ * @individual: a #FolksIndividual
+ * @action_type: the type of action to be performed on the contact
+ *
+ * Chooses a #FolksPersona from the given @individual which is best-suited for
+ * the given @action_type. "Best-suited" is determined by choosing the persona
+ * with the highest presence out of all the personas which can perform the
+ * given @action_type (e.g. are capable of playing glchess remotely).
+ *
+ * Return value: an #GamesContact for the best persona, or %NULL;
+ * unref with g_object_unref()
+ */
+GamesContact *
+games_contact_dup_best_for_action (FolksIndividual *individual,
+ GamesActionType action_type)
+{
+ GeeSet *personas;
+ GeeIterator *iter;
+ GList *contacts;
+ GamesContact *best_contact = NULL;
+
+ /* Build a list of GamesContacts that we can sort */
+ personas = folks_individual_get_personas (individual);
+ contacts = NULL;
+
+ iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+ while (gee_iterator_next (iter))
+ {
+ FolksPersona *persona = gee_iterator_get (iter);
+ TpContact *tp_contact;
+ GamesContact *contact = NULL;
+
+ if (!games_folks_persona_is_interesting (persona))
+ goto while_finish;
+
+ tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+ if (tp_contact == NULL)
+ goto while_finish;
+
+ contact = games_contact_dup_from_tp_contact (tp_contact);
+ games_contact_set_persona (contact, FOLKS_PERSONA (persona));
+
+ /* Only choose the contact if they're actually capable of the specified
+ * action. */
+ if (games_contact_can_do_action (contact, action_type))
+ contacts = g_list_prepend (contacts, g_object_ref (contact));
+
+while_finish:
+ g_clear_object (&contact);
+ g_clear_object (&persona);
+ }
+ g_clear_object (&iter);
+
+ /* Sort the contacts by some heuristic based on the action type, then take
+ * the top contact. */
+ if (contacts != NULL)
+ {
+ contacts = g_list_sort (contacts, get_sort_func_for_action (action_type));
+ best_contact = g_object_ref (contacts->data);
+ }
+
+ g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
+ g_list_free (contacts);
+
+ return best_contact;
+}
+
+/*
+ * Returns whether a given persona wraps a usable TpContact
+ */
+gboolean
+games_folks_persona_is_interesting (FolksPersona *persona)
+{
+ /* We're not interested in non-Telepathy personas */
+ if (!TPF_IS_PERSONA (persona))
+ return FALSE;
+
+ /* We're not interested in user personas which haven't been added to the
+ * contact list (see bgo#637151). */
+ if (folks_persona_get_is_user (persona) &&
+ !tpf_persona_get_is_in_contact_list (TPF_PERSONA (persona)))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Returns the GamesContact corresponding to the first TpContact contained
+ * within the given Individual.
+ * Note that this is a temporary convenience since actually,
+ * FolksIndividuals are not 1:1 to TpContacts.
+ *
+ * The returned GamesContact object must be unreffed when done with it.
+ */
+GamesContact *
+games_contact_dup_from_folks_individual (FolksIndividual *individual)
+{
+ GeeSet *personas;
+ GeeIterator *iter;
+ GamesContact *contact = NULL;
+
+ g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+
+ personas = folks_individual_get_personas (individual);
+ iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+ while (gee_iterator_next (iter) && (contact == NULL))
+ {
+ TpfPersona *persona = gee_iterator_get (iter);
+
+ if (games_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
+ {
+ TpContact *tp_contact;
+
+ tp_contact = tpf_persona_get_contact (persona);
+ if (tp_contact != NULL)
+ {
+ contact = games_contact_dup_from_tp_contact (tp_contact);
+ games_contact_set_persona (contact, FOLKS_PERSONA (persona));
+ }
+ }
+ g_clear_object (&persona);
+ }
+ g_clear_object (&iter);
+
+ if (contact == NULL)
+ {
+ g_debug ("Can't create an GamesContact for Individual %s",
+ folks_individual_get_id (individual));
+ }
+
+ return contact;
+}
diff --git a/libgames-contacts/games-contact.h b/libgames-contacts/games-contact.h
new file mode 100644
index 0000000..a91c073
--- /dev/null
+++ b/libgames-contacts/games-contact.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2012 Chandni Verma
+ *
+ * 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
+ *
+ */
+
+#ifndef __GAMES_CONTACT_H__
+#define __GAMES_CONTACT_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/contact.h>
+#include <telepathy-glib/account.h>
+#include <folks/folks.h>
+
+G_BEGIN_DECLS
+
+#define GAMES_TYPE_CONTACT (games_contact_get_type ())
+#define GAMES_CONTACT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GAMES_TYPE_CONTACT, GamesContact))
+#define GAMES_CONTACT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GAMES_TYPE_CONTACT, GamesContactClass))
+#define GAMES_IS_CONTACT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GAMES_TYPE_CONTACT))
+#define GAMES_IS_CONTACT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GAMES_TYPE_CONTACT))
+#define GAMES_CONTACT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GAMES_TYPE_CONTACT, GamesContactClass))
+
+typedef struct _GamesContact GamesContact;
+typedef struct _GamesContactClass GamesContactClass;
+
+struct _GamesContact
+{
+ GObject parent;
+ gpointer priv;
+};
+
+struct _GamesContactClass
+{
+ GObjectClass parent_class;
+};
+
+typedef struct {
+ guchar *data;
+ gsize len;
+ gchar *format;
+ gchar *token;
+ gchar *filename;
+ guint refcount;
+} GamesAvatar;
+
+typedef enum {
+ GAMES_CAPABILITIES_NONE = 0,
+ GAMES_CAPABILITIES_TUBE_GLCHESS = 1 << 0,
+ GAMES_CAPABILITIES_UNKNOWN = 1 << 1
+} GamesCapabilities;
+
+GType games_contact_get_type (void) G_GNUC_CONST;
+TpContact *games_contact_get_tp_contact (GamesContact *contact);
+const gchar *games_contact_get_id (GamesContact *contact);
+const gchar *games_contact_get_alias (GamesContact *contact);
+void games_contact_set_alias (GamesContact *contact,
+ const gchar *alias);
+GamesAvatar *games_contact_get_avatar (GamesContact *contact);
+TpAccount *games_contact_get_account (GamesContact *contact);
+FolksPersona *games_contact_get_persona (GamesContact *contact);
+void games_contact_set_persona (GamesContact *contact,
+ FolksPersona *persona);
+TpConnection *games_contact_get_connection (GamesContact *contact);
+TpConnectionPresenceType games_contact_get_presence (GamesContact *contact);
+const gchar *games_contact_get_presence_message (GamesContact *contact);
+guint games_contact_get_handle (GamesContact *contact);
+GamesCapabilities games_contact_get_capabilities (GamesContact *contact);
+gboolean games_contact_is_user (GamesContact *contact);
+void games_contact_set_is_user (GamesContact *contact,
+ gboolean is_user);
+gboolean games_contact_is_online (GamesContact *contact);
+const gchar *games_presence_get_default_message (TpConnectionPresenceType presence);
+const gchar *games_contact_get_status (GamesContact *contact);
+gboolean games_contact_can_play_glchess (GamesContact *contact);
+gboolean games_folks_persona_is_interesting (FolksPersona *persona);
+GamesContact *games_contact_dup_from_folks_individual (FolksIndividual *individual);
+
+typedef enum {
+ GAMES_ACTION_CHAT,
+ GAMES_ACTION_PLAY_GLCHESS,
+} GamesActionType;
+
+gboolean games_contact_can_do_action (GamesContact *self,
+ GamesActionType action_type);
+
+#define GAMES_TYPE_AVATAR (games_avatar_get_type ())
+GType games_avatar_get_type (void) G_GNUC_CONST;
+GamesAvatar *games_avatar_new (const guchar *data,
+ gsize len,
+ const gchar *format,
+ const gchar *filename);
+GamesAvatar *games_avatar_ref (GamesAvatar *avatar);
+void games_avatar_unref (GamesAvatar *avatar);
+
+gboolean games_avatar_save_to_file (GamesAvatar *avatar,
+ const gchar *filename, GError **error);
+
+gboolean games_contact_equal (gconstpointer contact1,
+ gconstpointer contact2);
+
+GamesContact *games_contact_dup_from_tp_contact (TpContact *tp_contact);
+GamesContact *games_contact_dup_best_for_action (FolksIndividual *individual,
+ GamesActionType action_type);
+
+G_END_DECLS
+
+#endif /* __GAMES_CONTACT_H__ */
diff --git a/libgames-contacts/games-individual-groups.c b/libgames-contacts/games-individual-groups.c
new file mode 100644
index 0000000..bb22a96
--- /dev/null
+++ b/libgames-contacts/games-individual-groups.c
@@ -0,0 +1,287 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* games-individual-groups.c: This class tracks server-saved and local
+ * user-defined groups of individuals as provided by folks. It makes the
+ * expanded state of groups persistent in a configuration file so the last
+ * state is retrievable by any game displaying groups.
+ *
+ * Copyright  2012 Chandni Verma
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ *
+ * Authors: Chandni Verma <chandniverma2112 gmail com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#include "games-ui-utils.h"
+#include "games-individual-groups.h"
+
+#define INDIVIDUAL_GROUPS_XML_FILENAME "individual-groups.xml"
+#define INDIVIDUAL_GROUPS_DTD_FILENAME "games-individual-groups.dtd"
+
+typedef struct {
+ gchar *name;
+ gboolean expanded;
+} IndividualGroup;
+
+static void individual_groups_file_parse (const gchar *filename);
+static gboolean individual_groups_file_save (void);
+static IndividualGroup *individual_group_new (const gchar *name,
+ gboolean expanded);
+static void individual_group_free (IndividualGroup *group);
+
+static GList *groups = NULL;
+
+void
+games_individual_groups_get_all (void)
+{
+ gchar *dir;
+ gchar *file_with_path;
+
+ /* If already set up clean up first */
+ if (groups) {
+ g_list_foreach (groups, (GFunc)individual_group_free, NULL);
+ g_list_free (groups);
+ groups = NULL;
+ }
+
+ dir = g_build_filename (g_get_user_config_dir (), PACKAGE, NULL);
+ file_with_path = g_build_filename (dir, INDIVIDUAL_GROUPS_XML_FILENAME, NULL);
+ g_free (dir);
+
+ if (g_file_test (file_with_path, G_FILE_TEST_EXISTS)) {
+ individual_groups_file_parse (file_with_path);
+ }
+
+ g_free (file_with_path);
+}
+
+static void
+individual_groups_file_parse (const gchar *filename)
+{
+ xmlParserCtxtPtr ctxt;
+ xmlDocPtr doc;
+ xmlNodePtr contacts;
+ xmlNodePtr account;
+ xmlNodePtr node;
+
+ g_debug ("Attempting to parse file:'%s'...", filename);
+
+ ctxt = xmlNewParserCtxt ();
+
+ /* Parse and validate the file. */
+ doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
+ if (!doc) {
+ g_warning ("Failed to parse file:'%s'", filename);
+ xmlFreeParserCtxt (ctxt);
+ return;
+ }
+
+ if (!games_xml_validate (doc, INDIVIDUAL_GROUPS_DTD_FILENAME)) {
+ g_warning ("Failed to validate file:'%s'", filename);
+ xmlFreeDoc (doc);
+ xmlFreeParserCtxt (ctxt);
+ return;
+ }
+
+ /* The root node, contacts. */
+ contacts = xmlDocGetRootElement (doc);
+
+ account = NULL;
+ node = contacts->children;
+ while (node) {
+ if (strcmp ((gchar *) node->name, "account") == 0) {
+ account = node;
+ break;
+ }
+ node = node->next;
+ }
+
+ node = NULL;
+ if (account) {
+ node = account->children;
+ }
+
+ while (node) {
+ if (strcmp ((gchar *) node->name, "group") == 0) {
+ gchar *name;
+ gchar *expanded_str;
+ gboolean expanded;
+ IndividualGroup *individual_group;
+
+ name = (gchar *) xmlGetProp (node, (const xmlChar *) "name");
+ expanded_str = (gchar *) xmlGetProp (node, (const xmlChar *) "expanded");
+
+ if (expanded_str && strcmp (expanded_str, "yes") == 0) {
+ expanded = TRUE;
+ } else {
+ expanded = FALSE;
+ }
+
+ individual_group = individual_group_new (name, expanded);
+ groups = g_list_append (groups, individual_group);
+
+ xmlFree (name);
+ xmlFree (expanded_str);
+ }
+
+ node = node->next;
+ }
+
+ g_debug ("Parsed %d contact groups", g_list_length (groups));
+
+ xmlFreeDoc (doc);
+ xmlFreeParserCtxt (ctxt);
+}
+
+static IndividualGroup *
+individual_group_new (const gchar *name,
+ gboolean expanded)
+{
+ IndividualGroup *group;
+
+ group = g_new0 (IndividualGroup, 1);
+
+ group->name = g_strdup (name);
+ group->expanded = expanded;
+
+ return group;
+}
+
+static void
+individual_group_free (IndividualGroup *group)
+{
+ g_return_if_fail (group != NULL);
+
+ g_free (group->name);
+
+ g_free (group);
+}
+
+static gboolean
+individual_groups_file_save (void)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root;
+ xmlNodePtr node;
+ GList *l;
+ gchar *dir;
+ gchar *file;
+
+ dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
+ g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
+ file = g_build_filename (dir, INDIVIDUAL_GROUPS_XML_FILENAME, NULL);
+ g_free (dir);
+
+ doc = xmlNewDoc ((const xmlChar *) "1.0");
+ root = xmlNewNode (NULL, (const xmlChar *) "individuals");
+ xmlDocSetRootElement (doc, root);
+
+ node = xmlNewChild (root, NULL, (const xmlChar *) "account", NULL);
+ xmlNewProp (node, (const xmlChar *) "name", (const xmlChar *) "Default");
+
+ for (l = groups; l; l = l->next) {
+ IndividualGroup *cg;
+ xmlNodePtr subnode;
+
+ cg = l->data;
+
+ subnode = xmlNewChild (node, NULL, (const xmlChar *) "group", NULL);
+ xmlNewProp (subnode, (const xmlChar *) "expanded", cg->expanded ?
+ (const xmlChar *) "yes" : (const xmlChar *) "no");
+ xmlNewProp (subnode, (const xmlChar *) "name", (const xmlChar *) cg->name);
+ }
+
+ /* Make sure the XML is indented properly */
+ xmlIndentTreeOutput = 1;
+
+ g_debug ("Saving file:'%s'", file);
+ xmlSaveFormatFileEnc (file, doc, "utf-8", 1);
+ xmlFreeDoc (doc);
+
+ xmlMemoryDump ();
+
+ g_free (file);
+
+ return TRUE;
+}
+
+gboolean
+games_individual_group_get_expanded (const gchar *group)
+{
+ GList *l;
+ gboolean default_val = TRUE;
+
+ g_return_val_if_fail (group != NULL, default_val);
+
+ for (l = groups; l; l = l->next) {
+ IndividualGroup *cg = l->data;
+
+ if (!cg || !cg->name) {
+ continue;
+ }
+
+ if (strcmp (cg->name, group) == 0) {
+ return cg->expanded;
+ }
+ }
+
+ return default_val;
+}
+
+void
+games_individual_group_set_expanded (const gchar *group,
+ gboolean expanded)
+{
+ GList *l;
+ IndividualGroup *cg;
+ gboolean changed = FALSE;
+
+ g_return_if_fail (group != NULL);
+
+ for (l = groups; l; l = l->next) {
+ cg = l->data;
+
+ if (!cg || !cg->name) {
+ continue;
+ }
+
+ if (strcmp (cg->name, group) == 0) {
+ cg->expanded = expanded;
+ changed = TRUE;
+ break;
+ }
+ }
+
+ /* if here... we don't have an IndividualGroup for the group. */
+ if (!changed) {
+ cg = individual_group_new (group, expanded);
+ groups = g_list_append (groups, cg);
+ }
+
+ individual_groups_file_save ();
+}
diff --git a/libgames-contacts/games-individual-groups.dtd b/libgames-contacts/games-individual-groups.dtd
new file mode 100644
index 0000000..ff8b1b8
--- /dev/null
+++ b/libgames-contacts/games-individual-groups.dtd
@@ -0,0 +1,16 @@
+<!--
+ DTD for groups of folks' individuals.
+-->
+
+<!-- Root element -->
+<!ELEMENT individuals (account)>
+
+<!ELEMENT account (group)+>
+<!ATTLIST account
+ name CDATA #REQUIRED>
+
+<!-- Groups in roster -->
+<!ELEMENT group EMPTY>
+<!ATTLIST group
+ name CDATA #REQUIRED
+ expanded CDATA #REQUIRED>
diff --git a/libgames-contacts/games-individual-groups.h b/libgames-contacts/games-individual-groups.h
new file mode 100644
index 0000000..7ec25d7
--- /dev/null
+++ b/libgames-contacts/games-individual-groups.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright  2012 Chandni Verma
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ *
+ * Authors: Chandni Verma <chandniverma2112 gmail com>
+ */
+
+#ifndef __GAMES_INDIVIDUAL_GROUPS_H__
+#define __GAMES_INDIVIDUAL_GROUPS_H__
+
+G_BEGIN_DECLS
+
+#include <glib.h>
+
+void games_individual_groups_get_all (void);
+
+gboolean games_individual_group_get_expanded (const gchar *group);
+void games_individual_group_set_expanded (const gchar *group,
+ gboolean expanded);
+
+G_END_DECLS
+
+#endif /* __GAMES_INDIVIDUAL_GROUPS_H__ */
diff --git a/libgames-contacts/games-individual-manager.c b/libgames-contacts/games-individual-manager.c
new file mode 100644
index 0000000..4ebbbab
--- /dev/null
+++ b/libgames-contacts/games-individual-manager.c
@@ -0,0 +1,436 @@
+/* games-individual-manager.c: Store, manage and ref Folks individuals
+ * who contain a GamesContact
+ *
+ * Copyright  2012 Chandni Verma
+ *
+ * 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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Chandni Verma <chandniverma2112 gmail com>
+ */
+
+#include <folks/folks-telepathy.h>
+
+#include "games-contact.h"
+#include "games-individual-manager.h"
+
+#define GAMES_INDIVIDUAL_MANAGER_GET_PRIV(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GAMES_TYPE_INDIVIDUAL_MANAGER, \
+ GamesIndividualManagerPriv))
+
+/* Much of code has been influenced by Empathy, a chat
+ * application based on Telepathy (https://live.gnome.org/Empathy)
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ * */
+
+struct _GamesIndividualManagerPriv{
+ FolksIndividualAggregator *aggregator;
+
+ /* individual.id -> individual map */
+ GHashTable *individuals;
+
+ gboolean contacts_loaded;
+};
+
+enum {
+ GROUPS_CHANGED,
+ FAVOURITES_CHANGED,
+ MEMBERS_CHANGED,
+ CONTACTS_LOADED,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GamesIndividualManager, games_individual_manager, G_TYPE_OBJECT);
+
+static GamesIndividualManager *manager_singleton = NULL;
+
+static GObject *
+individual_manager_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GObject *retval;
+
+ if (manager_singleton == NULL)
+ {
+ retval = G_OBJECT_CLASS (games_individual_manager_parent_class)->
+ constructor (type, n_construct_properties, construct_properties);
+
+ manager_singleton = GAMES_INDIVIDUAL_MANAGER (retval);
+ g_object_add_weak_pointer (retval, (gpointer) &manager_singleton);
+ }
+ else
+ {
+ retval = g_object_ref (manager_singleton);
+ }
+
+ return retval;
+}
+
+static void
+individual_group_changed_cb (FolksIndividual *individual,
+ gchar *group,
+ gboolean is_member,
+ GamesIndividualManager *self)
+{
+ g_signal_emit (self, signals[GROUPS_CHANGED], 0, individual, group,
+ is_member);
+}
+
+static void
+individual_notify_is_favourite_cb (FolksIndividual *individual,
+ GParamSpec *pspec,
+ GamesIndividualManager *self)
+{
+ gboolean is_favourite = folks_favourite_details_get_is_favourite (
+ FOLKS_FAVOURITE_DETAILS (individual));
+ g_signal_emit (self, signals[FAVOURITES_CHANGED], 0, individual,
+ is_favourite);
+}
+
+static void
+add_individual (GamesIndividualManager *self, FolksIndividual *individual)
+{
+ GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+ g_hash_table_insert (priv->individuals,
+ g_strdup (folks_individual_get_id (individual)),
+ g_object_ref (individual));
+
+ g_signal_connect (individual, "group-changed",
+ G_CALLBACK (individual_group_changed_cb), self);
+ g_signal_connect (individual, "notify::is-favourite",
+ G_CALLBACK (individual_notify_is_favourite_cb), self);
+}
+
+static void
+remove_individual (GamesIndividualManager *self, FolksIndividual *individual)
+{
+ GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+ g_signal_handlers_disconnect_by_func (individual,
+ individual_group_changed_cb, self);
+ g_signal_handlers_disconnect_by_func (individual,
+ individual_notify_is_favourite_cb, self);
+
+ g_hash_table_remove (priv->individuals, folks_individual_get_id (individual));
+}
+
+/* Returns TRUE if the given Individual contains a TpContact */
+static gboolean
+individual_contains_tp_contact (FolksIndividual *individual)
+{
+ GeeSet *personas;
+ GeeIterator *iter;
+ gboolean retval = FALSE;
+
+ g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), FALSE);
+
+ personas = folks_individual_get_personas (individual);
+ iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+ while (!retval && gee_iterator_next (iter))
+ {
+ FolksPersona *persona = gee_iterator_get (iter);
+ TpContact *contact = NULL;
+
+ if (games_folks_persona_is_interesting (persona))
+ contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+
+ g_clear_object (&persona);
+
+ if (contact != NULL)
+ retval = TRUE;
+ }
+ g_clear_object (&iter);
+
+ return retval;
+}
+
+/* This is emitted for *all* individuals in the individual aggregator (not
+ * just the ones we keep a reference to), to allow for the case where a new
+ * individual doesn't contain a GamesContact, but later has a persona added
+ * which does. */
+static void
+individual_notify_personas_cb (FolksIndividual *individual,
+ GParamSpec *pspec,
+ GamesIndividualManager *self)
+{
+ GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+ const gchar *id = folks_individual_get_id (individual);
+ gboolean has_contact = individual_contains_tp_contact (individual);
+ gboolean had_contact = (g_hash_table_lookup (priv->individuals,
+ id) != NULL) ? TRUE : FALSE;
+
+ if (had_contact == TRUE && has_contact == FALSE)
+ {
+ GList *removed = NULL;
+
+ /* The Individual has lost its GamesContact */
+ removed = g_list_prepend (removed, individual);
+ g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, NULL, removed,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
+ g_list_free (removed);
+
+ remove_individual (self, individual);
+ }
+ else if (had_contact == FALSE && has_contact == TRUE)
+ {
+ GList *added = NULL;
+
+ /* The Individual has gained its first GamesContact */
+ add_individual (self, individual);
+
+ added = g_list_prepend (added, individual);
+ g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL, added, NULL,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE /* FIXME */);
+ g_list_free (added);
+ }
+}
+
+static void
+aggregator_individuals_changed_cb (FolksIndividualAggregator *aggregator,
+ GeeMultiMap *changes,
+ GamesIndividualManager *self)
+{
+ GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+ GeeIterator *iter;
+ GeeSet *removed;
+ GeeCollection *added;
+ GList *added_set = NULL, *added_filtered = NULL, *removed_list = NULL;
+
+ /* We're not interested in the relationships between the added and removed
+ * individuals, so just extract collections of them. Note that the added
+ * collection may contain duplicates, being a collection, while the removed
+ * set won't. */
+ removed = gee_multi_map_get_keys (changes);
+ added = gee_multi_map_get_values (changes);
+
+ /* Handle the removals first, as one of the added Individuals might have the
+ * same ID as one of the removed Individuals (due to linking). */
+ iter = gee_iterable_iterator (GEE_ITERABLE (removed));
+ while (gee_iterator_next (iter))
+ {
+ FolksIndividual *ind = gee_iterator_get (iter);
+
+ if (ind == NULL)
+ continue;
+
+ g_signal_handlers_disconnect_by_func (ind,
+ individual_notify_personas_cb, self);
+
+ if (g_hash_table_lookup (priv->individuals,
+ folks_individual_get_id (ind)) != NULL)
+ {
+ remove_individual (self, ind);
+ removed_list = g_list_prepend (removed_list, ind);
+ }
+
+ g_clear_object (&ind);
+ }
+ g_clear_object (&iter);
+
+ /* Filter the individuals for ones which contain GamesContacts */
+ iter = gee_iterable_iterator (GEE_ITERABLE (added));
+ while (gee_iterator_next (iter))
+ {
+ FolksIndividual *ind = gee_iterator_get (iter);
+
+ /* Make sure we handle each added individual only once. */
+ if (ind == NULL || g_list_find (added_set, ind) != NULL)
+ continue;
+ added_set = g_list_prepend (added_set, ind);
+
+ g_signal_connect (ind, "notify::personas",
+ G_CALLBACK (individual_notify_personas_cb), self);
+
+ /* We add all individuals having TpContacts since this set of
+ * individuals and the manager singleton is common to all games.
+ * We leave filtering of individuals having a specific gaming
+ * capability upto respective GamesIndividualViews */
+ if (individual_contains_tp_contact (ind))
+ {
+ add_individual (self, ind);
+ added_filtered = g_list_prepend (added_filtered, ind);
+ }
+
+ g_clear_object (&ind);
+ }
+ g_clear_object (&iter);
+
+ g_list_free (added_set);
+
+ g_object_unref (added);
+ g_object_unref (removed);
+
+ /* Bail if we have no individuals left */
+ if (added_filtered == NULL && removed == NULL)
+ return;
+
+ added_filtered = g_list_reverse (added_filtered);
+
+ g_signal_emit (self, signals[MEMBERS_CHANGED], 0, NULL,
+ added_filtered, removed_list,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
+ TRUE);
+
+ g_list_free (added_filtered);
+ g_list_free (removed_list);
+}
+
+static void
+aggregator_is_quiescent_notify_cb (FolksIndividualAggregator *aggregator,
+ GParamSpec *spec,
+ GamesIndividualManager *self)
+{
+ GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+ gboolean is_quiescent;
+
+ if (priv->contacts_loaded)
+ return;
+
+ g_object_get (aggregator, "is-quiescent", &is_quiescent, NULL);
+
+ if (!is_quiescent)
+ return;
+
+ priv->contacts_loaded = TRUE;
+
+ g_signal_emit (self, signals[CONTACTS_LOADED], 0);
+}
+
+static void
+individual_manager_dispose (GObject *object)
+{
+ GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (object);
+
+ g_hash_table_unref (priv->individuals);
+
+ g_signal_handlers_disconnect_by_func (priv->aggregator,
+ aggregator_individuals_changed_cb, object);
+ g_signal_handlers_disconnect_by_func (priv->aggregator,
+ aggregator_is_quiescent_notify_cb, object);
+ tp_clear_object (&priv->aggregator);
+
+ G_OBJECT_CLASS (games_individual_manager_parent_class)->dispose (object);
+}
+
+static void
+games_individual_manager_class_init (GamesIndividualManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GamesIndividualManagerPriv));
+
+ object_class->dispose = individual_manager_dispose;
+ object_class->constructor = individual_manager_constructor;
+
+ signals[GROUPS_CHANGED] =
+ g_signal_new ("groups-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3, FOLKS_TYPE_INDIVIDUAL, G_TYPE_STRING, G_TYPE_BOOLEAN);
+
+ signals[FAVOURITES_CHANGED] =
+ g_signal_new ("favourites-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 2, FOLKS_TYPE_INDIVIDUAL, G_TYPE_BOOLEAN);
+
+ signals[MEMBERS_CHANGED] =
+ g_signal_new ("members-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 4, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_UINT);
+
+ signals[CONTACTS_LOADED] =
+ g_signal_new ("contacts-loaded",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+games_individual_manager_init (GamesIndividualManager *self)
+{
+ GamesIndividualManagerPriv *priv;
+
+ self->priv = priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+ priv->individuals = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+
+ priv->aggregator = folks_individual_aggregator_new ();
+ g_signal_connect (priv->aggregator, "individuals-changed-detailed",
+ G_CALLBACK (aggregator_individuals_changed_cb), self);
+ g_signal_connect (priv->aggregator, "notify::is-quiescent",
+ G_CALLBACK (aggregator_is_quiescent_notify_cb), self);
+
+ /* Prepare individual aggregator for use */
+ folks_individual_aggregator_prepare (priv->aggregator, NULL, NULL);
+}
+
+GamesIndividualManager *
+games_individual_manager_dup_singleton (void)
+{
+ return g_object_new (GAMES_TYPE_INDIVIDUAL_MANAGER, NULL);
+}
+
+GList *
+games_individual_manager_get_members (GamesIndividualManager *self)
+{
+ GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_MANAGER (self), NULL);
+
+ return g_hash_table_get_values (priv->individuals);
+}
+
+gboolean
+games_individual_manager_get_contacts_loaded (GamesIndividualManager *self)
+{
+ GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+ return priv->contacts_loaded;
+}
+
+FolksIndividual *
+games_individual_manager_lookup_member (GamesIndividualManager *self,
+ const gchar *id)
+{
+ GamesIndividualManagerPriv *priv = GAMES_INDIVIDUAL_MANAGER_GET_PRIV (self);
+
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_MANAGER (self), NULL);
+
+ return g_hash_table_lookup (priv->individuals, id);
+}
+
+
diff --git a/libgames-contacts/games-individual-manager.h b/libgames-contacts/games-individual-manager.h
new file mode 100644
index 0000000..cb786ba
--- /dev/null
+++ b/libgames-contacts/games-individual-manager.h
@@ -0,0 +1,67 @@
+/* games-individual-manager.h: Manage Folks individuals
+ *
+ * Copyright  2012 Chandni Verma
+ *
+ * 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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Chandni Verma <chandniverma2112 gmail com>
+ */
+
+#ifndef GAMES_INDIVIDUAL_MANAGER_H
+#define GAMES_INDIVIDUAL_MANAGER_H
+
+#include <glib.h>
+#include <folks/folks.h>
+
+G_BEGIN_DECLS
+
+#define GAMES_TYPE_INDIVIDUAL_MANAGER (games_individual_manager_get_type ())
+#define GAMES_INDIVIDUAL_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAMES_TYPE_INDIVIDUAL_MANAGER, GamesIndividualManager))
+#define GAMES_INDIVIDUAL_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAMES_TYPE_INDIVIDUAL_MANAGER, GamesIndividualManagerClass))
+#define GAMES_IS_INDIVIDUAL_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAMES_TYPE_INDIVIDUAL_MANAGER))
+#define GAMES_IS_INDIVIDUAL_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAMES_TYPE_INDIVIDUAL_MANAGER))
+#define GAMES_INDIVIDUAL_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GAMES_TYPE_INDIVIDUAL_MANAGER))
+
+typedef struct _GamesIndividualManager GamesIndividualManager;
+typedef struct _GamesIndividualManagerClass GamesIndividualManagerClass;
+typedef struct _GamesIndividualManagerPriv GamesIndividualManagerPriv;
+
+struct _GamesIndividualManager{
+ GObject parent;
+
+ /*< private >*/
+ GamesIndividualManagerPriv *priv;
+};
+
+struct _GamesIndividualManagerClass{
+ GObjectClass parent_class;
+};
+
+/* used by GAMES_TYPE_INDIVIDUAL_MANAGER */
+GType games_individual_manager_get_type (void);
+
+/* Function prototypes */
+GamesIndividualManager *games_individual_manager_dup_singleton (void);
+GList *games_individual_manager_get_members (GamesIndividualManager *self);
+FolksIndividual *games_individual_manager_lookup_member (GamesIndividualManager *manager,
+ const gchar *id);
+
+gboolean games_individual_manager_get_contacts_loaded (GamesIndividualManager *self);
+
+
+G_END_DECLS
+#endif /* GAMES_INDIVIDUAL_MANAGER_H */
+/* EOF */
diff --git a/libgames-contacts/games-individual-store-manager.c b/libgames-contacts/games-individual-store-manager.c
new file mode 100644
index 0000000..1c87fc9
--- /dev/null
+++ b/libgames-contacts/games-individual-store-manager.c
@@ -0,0 +1,321 @@
+/* games-individual-store-manager.c: Is a GamesIndividualStore which connects to
+ * GamesIndividualManager signals to update
+ * itself and manages initial loading of
+ * contacts.
+ *
+ * Copyright (C) 2012 Chandni Verma
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This code has been influenced by folks usage in Empathy,
+ * a chat application based on Telepathy (https://live.gnome.org/Empathy)
+ * Copyright (C) 2005-2007 Imendio ABdd
+ * Copyright (C) 2007-2011 Collabora Ltd.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include <folks/folks.h>
+#include <folks/folks-telepathy.h>
+#include <telepathy-glib/util.h>
+
+/*#include <libgames-support/games-utils.h>*/
+#include "games-enum-types.h"
+#include "games-individual-manager.h"
+
+#include "games-individual-store-manager.h"
+
+#include "games-ui-utils.h"
+#include "games-enum-types.h"
+
+struct _GamesIndividualStoreManagerPriv
+{
+ GamesIndividualManager *manager;
+ gboolean setup_idle_id;
+};
+
+enum
+{
+ PROP_0,
+ PROP_INDIVIDUAL_MANAGER,
+};
+
+
+G_DEFINE_TYPE (GamesIndividualStoreManager, games_individual_store_manager,
+ GAMES_TYPE_INDIVIDUAL_STORE);
+
+static void
+individual_store_manager_members_changed_cb (GamesIndividualManager *manager,
+ const gchar *message,
+ GList *added,
+ GList *removed,
+ guint reason,
+ GamesIndividualStoreManager *self)
+{
+ GList *l;
+ GamesIndividualStore *store = GAMES_INDIVIDUAL_STORE (self);
+
+ for (l = removed; l; l = l->next)
+ {
+ g_debug ("Individual %s (%s) %s",
+ folks_individual_get_id (l->data),
+ folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (l->data)),
+ "removed");
+
+ individual_store_remove_individual_and_disconnect (store, l->data);
+ }
+
+ for (l = added; l; l = l->next)
+ {
+ g_debug ("Individual %s (%s) %s", folks_individual_get_id (l->data),
+ folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (l->data)),
+ "added");
+
+ individual_store_add_individual_and_connect (store, l->data);
+ }
+}
+
+static void
+individual_store_manager_groups_changed_cb (GamesIndividualManager *manager,
+ FolksIndividual *individual,
+ gchar *group,
+ gboolean is_member,
+ GamesIndividualStoreManager *self)
+{
+ GamesIndividualStore *store = GAMES_INDIVIDUAL_STORE (self);
+
+ g_debug ("Updating groups for individual %s (%s)",
+ folks_individual_get_id (individual),
+ folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
+
+ /* We do this to make sure the groups are correct, if not, we
+ * would have to check the groups already set up for each
+ * contact and then see what has been updated.
+ */
+ games_individual_store_refresh_individual (store, individual);
+}
+
+static void
+individual_store_manager_add_existing_individuals (
+ GamesIndividualStoreManager *self,
+ const gchar *message)
+{
+ GList *individuals;
+
+ individuals = games_individual_manager_get_members (self->priv->manager);
+ if (individuals != NULL)
+ {
+ individual_store_manager_members_changed_cb (self->priv->manager,
+ message, individuals, NULL, 0, self);
+ g_list_free (individuals);
+ }
+}
+
+static gboolean
+individual_store_manager_manager_setup (gpointer user_data)
+{
+ GamesIndividualStoreManager *self = user_data;
+
+ /* Signal connection. */
+
+ g_debug ("handling individual renames unimplemented");
+
+ g_signal_connect (self->priv->manager,
+ "members-changed",
+ G_CALLBACK (individual_store_manager_members_changed_cb), self);
+
+ g_signal_connect (self->priv->manager,
+ "groups-changed",
+ G_CALLBACK (individual_store_manager_groups_changed_cb), self);
+
+ /* Add contacts already created. */
+ individual_store_manager_add_existing_individuals (self, "initial add");
+
+ self->priv->setup_idle_id = 0;
+ return FALSE;
+}
+
+static void
+individual_store_manager_set_individual_manager (
+ GamesIndividualStoreManager *self,
+ GamesIndividualManager *manager)
+{
+ g_assert (self->priv->manager == NULL); /* construct only */
+ self->priv->manager = g_object_ref (manager);
+
+ /* Let a chance to have all properties set before populating. They are all
+ * set after constructed () gets called and before this object is returned
+ * to the code creating it.
+ *
+ * Loading @manager is added to an idle source here to avoid having every
+ * code creating a GamesIndividualManagerStore object do it (since loading
+ * can take some time) after obtaining this object. */
+ self->priv->setup_idle_id = g_idle_add (
+ individual_store_manager_manager_setup, self);
+}
+
+static void
+individual_store_manager_dispose (GObject *object)
+{
+ GamesIndividualStoreManager *self = GAMES_INDIVIDUAL_STORE_MANAGER (
+ object);
+ GamesIndividualStore *store = GAMES_INDIVIDUAL_STORE (object);
+ GList *individuals, *l;
+
+ individuals = games_individual_manager_get_members (self->priv->manager);
+ for (l = individuals; l; l = l->next)
+ {
+ games_individual_store_disconnect_individual (store,
+ FOLKS_INDIVIDUAL (l->data));
+ }
+ tp_clear_pointer (&individuals, g_list_free);
+
+ if (self->priv->manager != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (self->priv->manager,
+ G_CALLBACK (individual_store_manager_members_changed_cb), object);
+ g_signal_handlers_disconnect_by_func (self->priv->manager,
+ G_CALLBACK (individual_store_manager_groups_changed_cb), object);
+ g_clear_object (&self->priv->manager);
+ }
+
+ if (self->priv->setup_idle_id != 0)
+ {
+ g_source_remove (self->priv->setup_idle_id);
+ self->priv->setup_idle_id = 0;
+ }
+
+ G_OBJECT_CLASS (games_individual_store_manager_parent_class)->dispose (
+ object);
+}
+
+static void
+individual_store_manager_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GamesIndividualStoreManager *self = GAMES_INDIVIDUAL_STORE_MANAGER (
+ object);
+
+ switch (param_id)
+ {
+ case PROP_INDIVIDUAL_MANAGER:
+ g_value_set_object (value, self->priv->manager);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+individual_store_manager_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (param_id)
+ {
+ case PROP_INDIVIDUAL_MANAGER:
+ individual_store_manager_set_individual_manager (
+ GAMES_INDIVIDUAL_STORE_MANAGER (object),
+ g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+individual_store_manager_reload_individuals (GamesIndividualStore *store)
+{
+ GamesIndividualStoreManager *self = GAMES_INDIVIDUAL_STORE_MANAGER (
+ store);
+
+ individual_store_manager_add_existing_individuals (self,
+ "re-adding members: toggled group visibility");
+}
+
+static gboolean
+individual_store_manager_initial_loading (GamesIndividualStore *store)
+{
+ GamesIndividualStoreManager *self = GAMES_INDIVIDUAL_STORE_MANAGER (
+ store);
+
+ return self->priv->setup_idle_id != 0;
+}
+
+static void
+games_individual_store_manager_class_init (
+ GamesIndividualStoreManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GamesIndividualStoreClass *store_class = GAMES_INDIVIDUAL_STORE_CLASS (
+ klass);
+
+ object_class->dispose = individual_store_manager_dispose;
+ object_class->get_property = individual_store_manager_get_property;
+ object_class->set_property = individual_store_manager_set_property;
+
+ store_class->reload_individuals = individual_store_manager_reload_individuals;
+ store_class->initial_loading = individual_store_manager_initial_loading;
+
+ g_object_class_install_property (object_class,
+ PROP_INDIVIDUAL_MANAGER,
+ g_param_spec_object ("individual-manager",
+ "Individual manager",
+ "Individual manager",
+ GAMES_TYPE_INDIVIDUAL_MANAGER,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class,
+ sizeof (GamesIndividualStoreManagerPriv));
+}
+
+static void
+games_individual_store_manager_init (GamesIndividualStoreManager *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ GAMES_TYPE_INDIVIDUAL_STORE_MANAGER, GamesIndividualStoreManagerPriv);
+}
+
+GamesIndividualStoreManager *
+games_individual_store_manager_new (GamesIndividualManager *manager)
+{
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_MANAGER (manager), NULL);
+
+ return g_object_new (GAMES_TYPE_INDIVIDUAL_STORE_MANAGER,
+ "individual-manager", manager, NULL);
+}
+
+GamesIndividualManager *
+games_individual_store_manager_get_manager (
+ GamesIndividualStoreManager *self)
+{
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_STORE_MANAGER (self), FALSE);
+
+ return self->priv->manager;
+}
diff --git a/libgames-contacts/games-individual-store-manager.h b/libgames-contacts/games-individual-store-manager.h
new file mode 100644
index 0000000..f330585
--- /dev/null
+++ b/libgames-contacts/games-individual-store-manager.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2012 Chandni Verma
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GAMES_INDIVIDUAL_STORE_MANAGER_H__
+#define __GAMES_INDIVIDUAL_STORE_MANAGER_H__
+
+#include <gtk/gtk.h>
+
+#include "games-individual-manager.h"
+
+#include "games-individual-store.h"
+
+G_BEGIN_DECLS
+#define GAMES_TYPE_INDIVIDUAL_STORE_MANAGER (games_individual_store_manager_get_type ())
+#define GAMES_INDIVIDUAL_STORE_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GAMES_TYPE_INDIVIDUAL_STORE_MANAGER, GamesIndividualStoreManager))
+#define GAMES_INDIVIDUAL_STORE_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GAMES_TYPE_INDIVIDUAL_STORE_MANAGER, GamesIndividualStoreManagerClass))
+#define GAMES_IS_INDIVIDUAL_STORE_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GAMES_TYPE_INDIVIDUAL_STORE_MANAGER))
+#define GAMES_IS_INDIVIDUAL_STORE_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GAMES_TYPE_INDIVIDUAL_STORE_MANAGER))
+#define GAMES_INDIVIDUAL_STORE_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GAMES_TYPE_INDIVIDUAL_STORE_MANAGER, GamesIndividualStoreManagerClass))
+
+typedef struct _GamesIndividualStoreManager GamesIndividualStoreManager;
+typedef struct _GamesIndividualStoreManagerClass GamesIndividualStoreManagerClass;
+typedef struct _GamesIndividualStoreManagerPriv GamesIndividualStoreManagerPriv;
+
+struct _GamesIndividualStoreManager
+{
+ GamesIndividualStore parent;
+ GamesIndividualStoreManagerPriv *priv;
+};
+
+struct _GamesIndividualStoreManagerClass
+{
+ GamesIndividualStoreClass parent_class;
+};
+
+GType games_individual_store_manager_get_type (void) G_GNUC_CONST;
+
+GamesIndividualStoreManager *games_individual_store_manager_new (
+ GamesIndividualManager *manager);
+
+GamesIndividualManager *games_individual_store_manager_get_manager (
+ GamesIndividualStoreManager *store);
+
+G_END_DECLS
+#endif /* __GAMES_INDIVIDUAL_STORE_MANAGER_H__ */
diff --git a/libgames-contacts/games-individual-store.c b/libgames-contacts/games-individual-store.c
new file mode 100644
index 0000000..e147908
--- /dev/null
+++ b/libgames-contacts/games-individual-store.c
@@ -0,0 +1,1756 @@
+/* games-individual-store.c: A GtkListStore to model Folks individuals
+ * obtained from GamesIndividualManager and their capabilities.
+ * Managing functions are provided by GamesIndividualStoreManager, an
+ * extension of GamesIndividualStore.
+ *
+ * Copyright  2012 Chandni Verma
+ *
+ * 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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Chandni Verma <chandniverma2112 gmail com>
+ */
+
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include <folks/folks.h>
+#include <folks/folks-telepathy.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include "games-individual-store.h"
+#include "games-individual-manager.h"
+#include "games-contact.h"
+#include "games-enum-types.h"
+#include "games-ui-utils.h"
+
+#define GAMES_INDIVIDUAL_STORE_GET_PRIV(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GAMES_TYPE_INDIVIDUAL_STORE, \
+ GamesIndividualStorePriv))
+
+/* Much of code here is a simplified version of Folks usage in Empathy,
+ * a chat application based on Telepathy (https://live.gnome.org/Empathy)
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ * */
+
+/* Active users are those which have recently changed state
+ * (e.g. online, offline or from normal to a busy state).
+ */
+
+/* Time in seconds user is shown as active */
+#define ACTIVE_USER_SHOW_TIME 7
+
+/* Time in seconds after connecting which we wait before active users are enabled */
+#define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5
+
+struct _GamesIndividualStorePriv{
+ gboolean show_avatars;
+ gboolean show_groups;
+ gboolean is_compact;
+ gboolean show_protocols;
+ GamesIndividualStoreSort sort_criterium;
+ guint inhibit_active;
+ gboolean dispose_has_run;
+ GHashTable *status_icons;
+ /* List of owned GCancellables for each pending avatar load operation */
+ GList *avatar_cancellables;
+ /* Hash: FolksIndividual* -> GQueue (GtkTreeIter *) */
+ GHashTable *folks_individual_cache;
+ /* Hash: char *groupname -> GtkTreeIter * */
+ GHashTable *contact_group_cache;
+ gboolean show_active;
+};
+
+typedef struct
+{
+ GamesIndividualStore *self;
+ FolksIndividual *individual;
+ gboolean remove;
+ guint timeout;
+} ShowActiveData;
+
+enum
+{
+ PROP_0,
+ PROP_SHOW_AVATARS,
+ PROP_SHOW_PROTOCOLS,
+ PROP_SHOW_GROUPS,
+ PROP_IS_COMPACT,
+ PROP_SORT_CRITERIUM
+};
+
+/* Forward declarations */
+static void individual_store_contact_update (GamesIndividualStore *self,
+ FolksIndividual *individual);
+
+G_DEFINE_TYPE (GamesIndividualStore, games_individual_store, GTK_TYPE_TREE_STORE);
+
+/**
+ * individual_store_get_individual_gaming_capabilities:
+ * @individual: the individual whose gaming capabilities are to be gauged
+ *
+ * Returns: All gaming capabilities in a single pass through an individual's
+ * personas as ORed GamesCapabilities flags.
+ */
+static GamesCapabilities
+individual_store_get_individual_gaming_capabilities (
+ FolksIndividual *individual)
+{
+ GeeSet *personas;
+ GeeIterator *iter;
+ GamesCapabilities individual_caps = GAMES_CAPABILITIES_NONE;
+
+ personas = folks_individual_get_personas (individual);
+ iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+
+ while (gee_iterator_next (iter))
+ {
+ FolksPersona *persona;
+ TpContact *tp_contact;
+ GamesContact *contact;
+ GamesCapabilities contact_caps;
+
+ persona = gee_iterator_get (iter);
+ /* Ignore non-telepathy personas */
+ if (!games_folks_persona_is_interesting (persona))
+ goto while_finally;
+
+ tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+ if (tp_contact == NULL)
+ /* Possibly network disconnected */
+ goto while_finally;
+
+ contact = games_contact_dup_from_tp_contact (tp_contact);
+
+ contact_caps = games_contact_get_capabilities (contact);
+ individual_caps |= contact_caps;
+
+ g_object_unref (contact);
+ while_finally:
+ g_clear_object (&persona);
+ }
+
+ g_clear_object (&iter);
+ return individual_caps;
+}
+
+static void
+add_individual_to_store (GtkTreeStore *store,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ FolksIndividual *individual)
+{
+ GamesIndividualStore *self = GAMES_INDIVIDUAL_STORE (store);
+ GamesCapabilities individual_caps =
+ individual_store_get_individual_gaming_capabilities (individual);
+ GQueue *queue;
+
+ gtk_tree_store_insert_with_values (store, iter, parent, 0,
+ GAMES_INDIVIDUAL_STORE_COL_NAME,
+ folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, individual,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, FALSE,
+ GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL_CAPABILITIES, individual_caps,
+ -1);
+
+ queue = g_hash_table_lookup (self->priv->folks_individual_cache, individual);
+ if (queue)
+ {
+ g_queue_push_tail (queue, gtk_tree_iter_copy (iter));
+ }
+ else
+ {
+ queue = g_queue_new ();
+ g_queue_push_tail (queue, gtk_tree_iter_copy (iter));
+ g_hash_table_insert (self->priv->folks_individual_cache, individual,
+ queue);
+ }
+}
+
+static void
+individual_store_get_group (GamesIndividualStore *self,
+ const gchar *name,
+ GtkTreeIter *iter_group_to_set,
+ GtkTreeIter *iter_separator_to_set,
+ gboolean *created,
+ gboolean is_fake_group)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter_group;
+ GtkTreeIter iter_separator;
+ GtkTreeIter *iter;
+
+ model = GTK_TREE_MODEL (self);
+ iter = g_hash_table_lookup (self->priv->contact_group_cache, name);
+
+ if (iter == NULL)
+ {
+ if (created)
+ *created = TRUE;
+
+ gtk_tree_store_insert_with_values (GTK_TREE_STORE (self), &iter_group,
+ NULL, 0,
+ GAMES_INDIVIDUAL_STORE_COL_ICON_STATUS, NULL,
+ GAMES_INDIVIDUAL_STORE_COL_NAME, name,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, TRUE,
+ GAMES_INDIVIDUAL_STORE_COL_IS_ACTIVE, FALSE,
+ GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
+ GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake_group,
+ -1);
+
+ g_hash_table_insert (self->priv->contact_group_cache, g_strdup (name),
+ gtk_tree_iter_copy (&iter_group));
+
+ if (iter_group_to_set)
+ *iter_group_to_set = iter_group;
+
+ gtk_tree_store_insert_with_values (GTK_TREE_STORE (self), &iter_separator,
+ &iter_group, 0,
+ GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, TRUE,
+ -1);
+
+ if (iter_separator_to_set)
+ *iter_separator_to_set = iter_separator;
+ }
+ else
+ {
+ if (created)
+ *created = FALSE;
+
+ if (iter_group_to_set)
+ *iter_group_to_set = *iter;
+
+ iter_separator = *iter;
+
+ if (gtk_tree_model_iter_next (model, &iter_separator))
+ {
+ gboolean is_separator;
+
+ gtk_tree_model_get (model, &iter_separator,
+ GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator, -1);
+
+ if (is_separator && iter_separator_to_set)
+ *iter_separator_to_set = iter_separator;
+ }
+ }
+}
+
+static GList *
+individual_store_find_contact (GamesIndividualStore *self,
+ FolksIndividual *individual)
+{
+ GQueue *row_refs_queue;
+ GList *i;
+ GList *iters_list = NULL;
+
+ row_refs_queue = g_hash_table_lookup (self->priv->folks_individual_cache,
+ individual);
+ if (!row_refs_queue)
+ return NULL;
+
+ for (i = g_queue_peek_head_link (row_refs_queue) ; i != NULL ; i = i->next)
+ {
+ GtkTreeIter *iter = i->data;
+
+ iters_list = g_list_prepend (iters_list, gtk_tree_iter_copy (iter));
+ }
+
+ return iters_list;
+}
+
+static void
+free_iters (GList *iters)
+{
+ g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
+ g_list_free (iters);
+}
+
+void
+games_individual_store_remove_individual (GamesIndividualStore *self,
+ FolksIndividual *individual)
+{
+ GtkTreeModel *model;
+ GQueue *row_refs;
+ GList *l;
+
+ row_refs = g_hash_table_lookup (self->priv->folks_individual_cache,
+ individual);
+ if (!row_refs)
+ return;
+
+ /* Clean up model */
+ model = GTK_TREE_MODEL (self);
+
+ for (l = g_queue_peek_head_link (row_refs); l; l = l->next)
+ {
+ GtkTreeIter *iter = l->data;
+ GtkTreeIter parent;
+
+ /* NOTE: it is only <= 2 here because we have
+ * separators after the group name, otherwise it
+ * should be 1.
+ */
+ if (gtk_tree_model_iter_parent (model, &parent, iter) &&
+ gtk_tree_model_iter_n_children (model, &parent) <= 2)
+ {
+ gchar *group_name;
+ gtk_tree_model_get (model, &parent,
+ GAMES_INDIVIDUAL_STORE_COL_NAME, &group_name,
+ -1);
+ g_hash_table_remove (self->priv->contact_group_cache,
+ group_name);
+ gtk_tree_store_remove (GTK_TREE_STORE (self), &parent);
+ }
+ else
+ {
+ gtk_tree_store_remove (GTK_TREE_STORE (self), iter);
+ }
+ }
+
+ g_hash_table_remove (self->priv->folks_individual_cache, individual);
+}
+
+gboolean
+games_individual_store_get_show_avatars (GamesIndividualStore *self)
+{
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_STORE (self), TRUE);
+
+ return self->priv->show_avatars;
+}
+
+static gboolean
+individual_store_update_list_mode_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GamesIndividualStore *self)
+{
+ gboolean show_avatar = FALSE;
+ FolksIndividual *individual;
+ GdkPixbuf *pixbuf_status;
+
+ if (self->priv->show_avatars && !self->priv->is_compact)
+ {
+ show_avatar = TRUE;
+ }
+
+ gtk_tree_model_get (model, iter,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
+
+ if (individual == NULL)
+ {
+ return FALSE;
+ }
+ /* get icon from hash_table */
+ pixbuf_status =
+ games_individual_store_get_individual_status_icon (self, individual);
+
+ gtk_tree_store_set (GTK_TREE_STORE (self), iter,
+ GAMES_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf_status,
+ GAMES_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
+ GAMES_INDIVIDUAL_STORE_COL_COMPACT, self->priv->is_compact, -1);
+
+ g_object_unref (individual);
+
+ return FALSE;
+}
+
+void
+games_individual_store_set_show_avatars (GamesIndividualStore *self,
+ gboolean show_avatars)
+{
+ GtkTreeModel *model;
+
+ g_return_if_fail (GAMES_IS_INDIVIDUAL_STORE (self));
+
+ self->priv->show_avatars = show_avatars;
+
+ model = GTK_TREE_MODEL (self);
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc)
+ individual_store_update_list_mode_foreach, self);
+
+ g_object_notify (G_OBJECT (self), "show-avatars");
+}
+
+gboolean
+games_individual_store_get_show_protocols (GamesIndividualStore *self)
+{
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_STORE (self), TRUE);
+
+ return self->priv->show_protocols;
+}
+
+void
+games_individual_store_set_show_protocols (GamesIndividualStore *self,
+ gboolean show_protocols)
+{
+ GtkTreeModel *model;
+
+ g_return_if_fail (GAMES_IS_INDIVIDUAL_STORE (self));
+
+ self->priv->show_protocols = show_protocols;
+
+ model = GTK_TREE_MODEL (self);
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc)
+ individual_store_update_list_mode_foreach, self);
+
+ g_object_notify (G_OBJECT (self), "show-protocols");
+}
+
+gboolean
+games_individual_store_get_show_groups (GamesIndividualStore *self)
+{
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_STORE (self), TRUE);
+
+ return self->priv->show_groups;
+}
+
+void
+games_individual_store_set_show_groups (GamesIndividualStore *self,
+ gboolean show_groups)
+{
+ GamesIndividualStoreClass *klass;
+
+ g_return_if_fail (GAMES_IS_INDIVIDUAL_STORE (self));
+
+ klass = GAMES_INDIVIDUAL_STORE_GET_CLASS ( self);
+
+ if (self->priv->show_groups == show_groups)
+ {
+ return;
+ }
+
+ self->priv->show_groups = show_groups;
+
+ if (!klass->initial_loading (self))
+ {
+ /* Remove all contacts and add them back, not optimized but
+ * that's the easy way :)
+ *
+ * This is only done if there's not a pending setup idle
+ * callback, otherwise it will race and the contacts will get
+ * added twice */
+
+ gtk_tree_store_clear (GTK_TREE_STORE (self));
+ /* Also clear the cache */
+ g_hash_table_remove_all (self->priv->folks_individual_cache);
+ g_hash_table_remove_all (self->priv->contact_group_cache);
+
+ klass->reload_individuals (self);
+ }
+
+ g_object_notify (G_OBJECT (self), "show-groups");
+}
+
+gboolean
+games_individual_store_get_is_compact (GamesIndividualStore *self)
+{
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_STORE (self), TRUE);
+
+ return self->priv->is_compact;
+}
+
+void
+games_individual_store_set_is_compact (GamesIndividualStore *self,
+ gboolean is_compact)
+{
+ GtkTreeModel *model;
+
+ g_return_if_fail (GAMES_IS_INDIVIDUAL_STORE (self));
+
+ self->priv->is_compact = is_compact;
+
+ model = GTK_TREE_MODEL (self);
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc)
+ individual_store_update_list_mode_foreach, self);
+
+ g_object_notify (G_OBJECT (self), "is-compact");
+}
+
+GamesIndividualStoreSort
+games_individual_store_get_sort_criterium (GamesIndividualStore *self)
+{
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_STORE (self), 0);
+
+ return self->priv->sort_criterium;
+}
+
+void
+games_individual_store_set_sort_criterium (GamesIndividualStore *self,
+ GamesIndividualStoreSort sort_criterium)
+{
+ g_return_if_fail (GAMES_IS_INDIVIDUAL_STORE (self));
+
+ self->priv->sort_criterium = sort_criterium;
+
+ switch (sort_criterium)
+ {
+ case GAMES_INDIVIDUAL_STORE_SORT_STATE:
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self),
+ GAMES_INDIVIDUAL_STORE_COL_STATUS, GTK_SORT_ASCENDING);
+ break;
+
+ case GAMES_INDIVIDUAL_STORE_SORT_NAME:
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self),
+ GAMES_INDIVIDUAL_STORE_COL_NAME, GTK_SORT_ASCENDING);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_object_notify (G_OBJECT (self), "sort-criterium");
+}
+
+static gint
+get_position (const char **strv,
+ const char *str)
+{
+ int i;
+
+ for (i = 0; strv[i] != NULL; i++)
+ {
+ if (!tp_strdiff (strv[i], str))
+ return i;
+ }
+
+ return -1;
+}
+
+static gint
+compare_separator_and_groups (gboolean is_separator_a,
+ gboolean is_separator_b,
+ const gchar *name_a,
+ const gchar *name_b,
+ FolksIndividual *individual_a,
+ FolksIndividual *individual_b,
+ gboolean fake_group_a,
+ gboolean fake_group_b)
+{
+ /* these two lists are the sorted list of fake groups to include at the
+ * top and bottom of the roster */
+ const char *top_groups[] = {
+ GAMES_INDIVIDUAL_STORE_FAVORITE,
+ NULL
+ };
+
+ const char *bottom_groups[] = {
+ GAMES_INDIVIDUAL_STORE_UNGROUPED,
+ NULL
+ };
+
+ if (is_separator_a || is_separator_b)
+ {
+ /* We have at least one separator */
+ if (is_separator_a)
+ {
+ return -1;
+ }
+ else if (is_separator_b)
+ {
+ return 1;
+ }
+ }
+
+ /* One group and one contact */
+ if (!individual_a && individual_b)
+ {
+ return 1;
+ }
+ else if (individual_a && !individual_b)
+ {
+ return -1;
+ }
+ else if (!individual_a && !individual_b)
+ {
+ gboolean a_in_top, b_in_top, a_in_bottom, b_in_bottom;
+
+ a_in_top = fake_group_a && tp_strv_contains (top_groups, name_a);
+ b_in_top = fake_group_b && tp_strv_contains (top_groups, name_b);
+ a_in_bottom = fake_group_a && tp_strv_contains (bottom_groups, name_a);
+ b_in_bottom = fake_group_b && tp_strv_contains (bottom_groups, name_b);
+
+ if (a_in_top && b_in_top)
+ {
+ /* compare positions */
+ return CLAMP (get_position (top_groups, name_a) -
+ get_position (top_groups, name_b), -1, 1);
+ }
+ else if (a_in_bottom && b_in_bottom)
+ {
+ /* compare positions */
+ return CLAMP (get_position (bottom_groups, name_a) -
+ get_position (bottom_groups, name_b), -1, 1);
+ }
+ else if (a_in_top || b_in_bottom)
+ {
+ return -1;
+ }
+ else if (b_in_top || a_in_bottom)
+ {
+ return 1;
+ }
+ else
+ {
+ return g_utf8_collate (name_a, name_b);
+ }
+ }
+
+ /* Two contacts, ordering depends of the sorting policy */
+ return 0;
+}
+
+static gint
+individual_store_contact_sort (FolksIndividual *individual_a,
+ FolksIndividual *individual_b)
+{
+ gint ret_val;
+ GamesContact *contact_a = NULL, *contact_b = NULL;
+ TpAccount *account_a, *account_b;
+
+ g_return_val_if_fail (individual_a != NULL || individual_b != NULL, 0);
+
+ /* alias */
+ ret_val = g_utf8_collate (
+ folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual_a)),
+ folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual_b)));
+
+ if (ret_val != 0)
+ goto out;
+
+ contact_a = games_contact_dup_from_folks_individual (individual_a);
+ contact_b = games_contact_dup_from_folks_individual (individual_b);
+ if (contact_a != NULL && contact_b != NULL)
+ {
+ account_a = games_contact_get_account (contact_a);
+ account_b = games_contact_get_account (contact_b);
+
+ g_assert (account_a != NULL);
+ g_assert (account_b != NULL);
+
+ /* protocol */
+ ret_val = g_strcmp0 (tp_account_get_protocol_name (account_a),
+ tp_account_get_protocol_name (account_b));
+
+ if (ret_val != 0)
+ goto out;
+
+ /* account ID */
+ ret_val = g_strcmp0 (tp_proxy_get_object_path (account_a),
+ tp_proxy_get_object_path (account_b));
+
+ if (ret_val != 0)
+ goto out;
+ }
+
+ /* identifier */
+ ret_val = g_utf8_collate (folks_individual_get_id (individual_a),
+ folks_individual_get_id (individual_b));
+
+out:
+ tp_clear_object (&contact_a);
+ tp_clear_object (&contact_b);
+
+ return ret_val;
+}
+
+static gint
+individual_store_state_sort_func (GtkTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b,
+ gpointer user_data)
+{
+ gint ret_val;
+ FolksIndividual *individual_a, *individual_b;
+ gchar *name_a, *name_b;
+ gboolean is_separator_a, is_separator_b;
+ gboolean fake_group_a, fake_group_b;
+ FolksPresenceType folks_presence_type_a, folks_presence_type_b;
+ TpConnectionPresenceType tp_presence_a, tp_presence_b;
+
+ gtk_tree_model_get (model, iter_a,
+ GAMES_INDIVIDUAL_STORE_COL_NAME, &name_a,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_a,
+ GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_a,
+ GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_a, -1);
+ gtk_tree_model_get (model, iter_b,
+ GAMES_INDIVIDUAL_STORE_COL_NAME, &name_b,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_b,
+ GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_b,
+ GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_b, -1);
+
+ if (individual_a == NULL || individual_b == NULL)
+ {
+ ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
+ name_a, name_b, individual_a, individual_b, fake_group_a,
+ fake_group_b);
+ goto free_and_out;
+ }
+
+ /* If we managed to get this far, we can start looking at
+ * the presences.
+ */
+ folks_presence_type_a =
+ folks_presence_details_get_presence_type (
+ FOLKS_PRESENCE_DETAILS (individual_a));
+ folks_presence_type_b =
+ folks_presence_details_get_presence_type (
+ FOLKS_PRESENCE_DETAILS (individual_b));
+ tp_presence_a = games_folks_presence_type_to_tp (folks_presence_type_a);
+ tp_presence_b = games_folks_presence_type_to_tp (folks_presence_type_b);
+
+ ret_val = -tp_connection_presence_type_cmp_availability (tp_presence_a,
+ tp_presence_b);
+
+ if (ret_val == 0)
+ {
+ /* Fallback: compare by name et al. */
+ ret_val = individual_store_contact_sort (individual_a, individual_b);
+ }
+
+free_and_out:
+ g_free (name_a);
+ g_free (name_b);
+ tp_clear_object (&individual_a);
+ tp_clear_object (&individual_b);
+
+ return ret_val;
+}
+
+static gint
+individual_store_name_sort_func (GtkTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b,
+ gpointer user_data)
+{
+ gchar *name_a, *name_b;
+ FolksIndividual *individual_a, *individual_b;
+ gboolean is_separator_a = FALSE, is_separator_b = FALSE;
+ gint ret_val;
+ gboolean fake_group_a, fake_group_b;
+
+ gtk_tree_model_get (model, iter_a,
+ GAMES_INDIVIDUAL_STORE_COL_NAME, &name_a,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_a,
+ GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_a,
+ GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_a, -1);
+ gtk_tree_model_get (model, iter_b,
+ GAMES_INDIVIDUAL_STORE_COL_NAME, &name_b,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_b,
+ GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_b,
+ GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_b, -1);
+
+ if (individual_a == NULL || individual_b == NULL)
+ ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
+ name_a, name_b, individual_a, individual_b, fake_group_a, fake_group_b);
+ else
+ ret_val = individual_store_contact_sort (individual_a, individual_b);
+
+ tp_clear_object (&individual_a);
+ tp_clear_object (&individual_b);
+ g_free (name_a);
+ g_free (name_b);
+
+ return ret_val;
+}
+
+static void
+individual_store_dispose (GObject *object)
+{
+ GamesIndividualStore *self = GAMES_INDIVIDUAL_STORE (object);
+ GList *l;
+
+ if (self->priv->dispose_has_run)
+ return;
+ self->priv->dispose_has_run = TRUE;
+
+ /* Cancel any pending avatar load operations */
+ for (l = self->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 (self->priv->avatar_cancellables);
+
+ if (self->priv->inhibit_active)
+ {
+ g_source_remove (self->priv->inhibit_active);
+ }
+
+ g_hash_table_unref (self->priv->status_icons);
+ g_hash_table_unref (self->priv->folks_individual_cache);
+ g_hash_table_unref (self->priv->contact_group_cache);
+
+ G_OBJECT_CLASS (games_individual_store_parent_class)->dispose (object);
+}
+
+static void
+individual_store_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GamesIndividualStore *self = GAMES_INDIVIDUAL_STORE (object);
+
+ switch (param_id)
+ {
+ case PROP_SHOW_AVATARS:
+ g_value_set_boolean (value, self->priv->show_avatars);
+ break;
+ case PROP_SHOW_PROTOCOLS:
+ g_value_set_boolean (value, self->priv->show_protocols);
+ break;
+ case PROP_SHOW_GROUPS:
+ g_value_set_boolean (value, self->priv->show_groups);
+ break;
+ case PROP_IS_COMPACT:
+ g_value_set_boolean (value, self->priv->is_compact);
+ break;
+ case PROP_SORT_CRITERIUM:
+ g_value_set_enum (value, self->priv->sort_criterium);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+individual_store_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (param_id)
+ {
+ case PROP_SHOW_AVATARS:
+ games_individual_store_set_show_avatars (GAMES_INDIVIDUAL_STORE
+ (object), g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_PROTOCOLS:
+ games_individual_store_set_show_protocols (GAMES_INDIVIDUAL_STORE
+ (object), g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_GROUPS:
+ games_individual_store_set_show_groups (GAMES_INDIVIDUAL_STORE
+ (object), g_value_get_boolean (value));
+ break;
+ case PROP_IS_COMPACT:
+ games_individual_store_set_is_compact (GAMES_INDIVIDUAL_STORE
+ (object), g_value_get_boolean (value));
+ break;
+ case PROP_SORT_CRITERIUM:
+ games_individual_store_set_sort_criterium (GAMES_INDIVIDUAL_STORE
+ (object), g_value_get_enum (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+games_individual_store_class_init (GamesIndividualStoreClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GamesIndividualStorePriv));
+
+ object_class->dispose = individual_store_dispose;
+ object_class->get_property = individual_store_get_property;
+ object_class->set_property = individual_store_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_SHOW_AVATARS,
+ g_param_spec_boolean ("show-avatars",
+ "Show Avatars",
+ "Whether contact list should display "
+ "avatars for contacts", TRUE, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SHOW_PROTOCOLS,
+ g_param_spec_boolean ("show-protocols",
+ "Show Protocols",
+ "Whether contact list should display "
+ "protocols for contacts", FALSE, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SHOW_GROUPS,
+ g_param_spec_boolean ("show-groups",
+ "Show Groups",
+ "Whether contact list should display "
+ "contact groups", TRUE, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_IS_COMPACT,
+ g_param_spec_boolean ("is-compact",
+ "Is Compact",
+ "Whether the contact list is in compact mode or not",
+ FALSE, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SORT_CRITERIUM,
+ g_param_spec_enum ("sort-criterium",
+ "Sort criterium",
+ "The sort criterium to use for sorting the contact list",
+ GAMES_TYPE_INDIVIDUAL_STORE_SORT,
+ GAMES_INDIVIDUAL_STORE_SORT_STATE, G_PARAM_READWRITE));
+}
+
+static gboolean
+individual_store_inhibit_active_cb (GamesIndividualStore *self)
+{
+ self->priv->show_active = TRUE;
+ self->priv->inhibit_active = 0;
+
+ return FALSE;
+}
+
+static void
+g_queue_free_full_iter (gpointer data)
+{
+ GQueue *queue = (GQueue *) data;
+ g_queue_foreach (queue, (GFunc) gtk_tree_iter_free, NULL);
+ g_queue_free (queue);
+}
+
+static void
+games_individual_store_init (GamesIndividualStore *self)
+{
+ GamesIndividualStorePriv *priv;
+
+ self->priv = priv = GAMES_INDIVIDUAL_STORE_GET_PRIV (self);
+
+ self->priv->show_avatars = TRUE;
+ self->priv->show_groups = TRUE;
+ self->priv->show_protocols = FALSE;
+ self->priv->inhibit_active =
+ g_timeout_add_seconds (ACTIVE_USER_WAIT_TO_ENABLE_TIME,
+ (GSourceFunc) individual_store_inhibit_active_cb, self);
+ self->priv->status_icons =
+ g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+ self->priv->folks_individual_cache = g_hash_table_new_full (NULL, NULL, NULL,
+ g_queue_free_full_iter);
+ self->priv->contact_group_cache = g_hash_table_new_full (g_str_hash,
+ g_str_equal, g_free, (GDestroyNotify) gtk_tree_iter_free);
+
+ GType types[] = {
+ GDK_TYPE_PIXBUF, /* Status pixbuf */
+ GDK_TYPE_PIXBUF, /* Avatar pixbuf */
+ G_TYPE_BOOLEAN, /* Avatar pixbuf visible */
+ G_TYPE_STRING, /* Name */
+ G_TYPE_UINT, /* Presence type */
+ G_TYPE_STRING, /* Status string */
+ G_TYPE_BOOLEAN, /* Compact view */
+ FOLKS_TYPE_INDIVIDUAL, /* Individual type */
+ G_TYPE_BOOLEAN, /* Is group */
+ G_TYPE_BOOLEAN, /* Is active */
+ G_TYPE_BOOLEAN, /* Is online */
+ G_TYPE_BOOLEAN, /* Is separator */
+ G_TYPE_BOOLEAN, /* Can play glchess */
+ G_TYPE_BOOLEAN, /* Is a fake group */
+ };
+
+ gtk_tree_store_set_column_types (GTK_TREE_STORE (self),
+ GAMES_INDIVIDUAL_STORE_COL_COUNT, types);
+
+ /* Set up sorting */
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (self),
+ GAMES_INDIVIDUAL_STORE_COL_NAME,
+ individual_store_name_sort_func, self, NULL);
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (self),
+ GAMES_INDIVIDUAL_STORE_COL_STATUS,
+ individual_store_state_sort_func, self, NULL);
+
+ /* Sort by state with fallback on name (alias et al.) */
+ self->priv->sort_criterium = GAMES_INDIVIDUAL_STORE_SORT_STATE;
+
+ games_individual_store_set_sort_criterium (self,
+ self->priv->sort_criterium);
+}
+
+gboolean
+games_individual_store_row_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gboolean is_separator = FALSE;
+
+ g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
+
+ gtk_tree_model_get (model, iter,
+ GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator, -1);
+
+ return is_separator;
+}
+
+gchar *
+games_individual_store_get_parent_group (GtkTreeModel *model,
+ GtkTreePath *path,
+ gboolean *path_is_group,
+ gboolean *is_fake_group)
+{
+ GtkTreeIter parent_iter, iter;
+ gchar *name = NULL;
+ gboolean is_group;
+ gboolean fake = FALSE;
+
+ g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
+
+ if (path_is_group)
+ {
+ *path_is_group = FALSE;
+ }
+
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ {
+ return NULL;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+ GAMES_INDIVIDUAL_STORE_COL_NAME, &name, -1);
+
+ if (!is_group)
+ {
+ g_free (name);
+ name = NULL;
+
+ if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter))
+ {
+ return NULL;
+ }
+
+ iter = parent_iter;
+
+ gtk_tree_model_get (model, &iter,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+ GAMES_INDIVIDUAL_STORE_COL_NAME, &name,
+ GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
+ if (!is_group)
+ {
+ g_free (name);
+ return NULL;
+ }
+ }
+
+ if (path_is_group)
+ {
+ *path_is_group = TRUE;
+ }
+
+ if (is_fake_group != NULL)
+ *is_fake_group = fake;
+
+ return name;
+}
+
+static GdkPixbuf *
+individual_store_get_individual_status_icon_with_icon_name (
+ GamesIndividualStore *self,
+ FolksIndividual *individual,
+ const gchar *status_icon_name)
+{
+ GdkPixbuf *pixbuf_status;
+ const gchar *protocol_name = NULL;
+ gchar *icon_name = NULL;
+ GeeSet *personas;
+ GeeIterator *iter;
+ guint contact_count = 0;
+ GamesContact *contact = NULL;
+ gboolean show_protocols_here;
+
+ personas = folks_individual_get_personas (individual);
+ iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+ while (gee_iterator_next (iter))
+ {
+ FolksPersona *persona = gee_iterator_get (iter);
+ if (games_folks_persona_is_interesting (persona))
+ contact_count++;
+
+ g_clear_object (&persona);
+
+ if (contact_count > 1)
+ break;
+ }
+ g_clear_object (&iter);
+
+ show_protocols_here = (self->priv->show_protocols && (contact_count == 1));
+ if (show_protocols_here)
+ {
+ contact = games_contact_dup_from_folks_individual (individual);
+ if (contact != NULL)
+ {
+ protocol_name = games_protocol_name_for_contact (contact);
+ icon_name = g_strdup_printf ("%s-%s", status_icon_name,
+ protocol_name);
+ }
+ else
+ {
+ g_warning ("Cannot retrieve contact from individual '%s'",
+ folks_alias_details_get_alias (
+ FOLKS_ALIAS_DETAILS (individual)));
+
+ return NULL;
+ }
+ }
+ else
+ {
+ icon_name = g_strdup_printf ("%s", status_icon_name);
+ }
+
+ pixbuf_status = g_hash_table_lookup (self->priv->status_icons, icon_name);
+
+ if (pixbuf_status == NULL)
+ {
+ pixbuf_status =
+ games_pixbuf_contact_status_icon_with_icon_name (contact,
+ status_icon_name, show_protocols_here);
+
+ if (pixbuf_status != NULL)
+ {
+ /* pass the reference to the hash table */
+ g_hash_table_insert (self->priv->status_icons,
+ g_strdup (icon_name), pixbuf_status);
+ }
+ }
+
+ g_free (icon_name);
+ tp_clear_object (&contact);
+
+ return pixbuf_status;
+}
+
+GdkPixbuf *
+games_individual_store_get_individual_status_icon (
+ GamesIndividualStore *self,
+ FolksIndividual *individual)
+{
+ GdkPixbuf *pixbuf_status = NULL;
+ const gchar *status_icon_name = NULL;
+
+ status_icon_name = games_icon_name_for_individual (individual);
+ if (status_icon_name == NULL)
+ return NULL;
+
+ pixbuf_status =
+ individual_store_get_individual_status_icon_with_icon_name (self,
+ individual, status_icon_name);
+
+ return pixbuf_status;
+}
+
+void
+games_individual_store_refresh_individual (GamesIndividualStore *self,
+ FolksIndividual *individual)
+{
+ gboolean show_active;
+
+ show_active = self->priv->show_active;
+ self->priv->show_active = FALSE;
+ games_individual_store_remove_individual (self, individual);
+ games_individual_store_add_individual (self, individual);
+ self->priv->show_active = show_active;
+}
+
+void
+games_individual_store_add_individual (GamesIndividualStore *self,
+ FolksIndividual *individual)
+{
+ GtkTreeIter iter, iter_group;
+ GeeSet *group_set = NULL;
+ gboolean grouped = FALSE;
+ const gchar *alias =
+ folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
+
+ if (alias == NULL)
+ return;
+
+ if (!self->priv->show_groups)
+ {
+ /* add our individual to the toplevel of the store */
+ add_individual_to_store (GTK_TREE_STORE (self), &iter, NULL,
+ individual);
+
+ goto finally;
+ }
+
+ group_set = folks_group_details_get_groups (
+ FOLKS_GROUP_DETAILS (individual));
+
+ if (gee_collection_get_size (GEE_COLLECTION (group_set)) > 0)
+ {
+ /* add the contact to its groups */
+ GeeIterator *group_iter =
+ gee_iterable_iterator (GEE_ITERABLE (group_set));
+
+ while (group_iter != NULL && gee_iterator_next (group_iter))
+ {
+ gchar *group_name = gee_iterator_get (group_iter);
+
+ individual_store_get_group (self, group_name, &iter_group,
+ NULL, NULL, FALSE);
+
+ add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
+ individual);
+ grouped = TRUE;
+
+ g_free (group_name);
+ }
+
+ g_clear_object (&group_iter);
+ }
+ else
+ {
+ /* fall-back groups, in case there are no named groups */
+ GamesContact *contact;
+ TpConnection *connection;
+ const gchar *protocol_name = NULL;
+
+ /* Link local-xmpp contacts cannot be linked with other personas so if
+ * the individual is visible (interesting, with the desired gaming
+ * capabilities) it has to have a single telepathy persona on just this
+ * protocol. We don't need to specify which contact we want.
+ * Just use the first persona (It will be a TpContact) */
+ contact = games_contact_dup_from_folks_individual (individual);
+ if (contact != NULL)
+ {
+ connection = games_contact_get_connection (contact);
+ protocol_name = tp_connection_get_protocol_name (connection);
+ }
+
+ if (!tp_strdiff (protocol_name, "local-xmpp"))
+ {
+ /* these are People Nearby */
+ individual_store_get_group (self,
+ GAMES_INDIVIDUAL_STORE_PEOPLE_NEARBY, &iter_group, NULL, NULL,
+ TRUE);
+ add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
+ individual);
+ grouped = TRUE;
+ }
+
+ g_clear_object (&contact);
+ }
+
+ if (folks_favourite_details_get_is_favourite (
+ FOLKS_FAVOURITE_DETAILS (individual)))
+ {
+ /* Add contact to the fake 'Favorites' group */
+ individual_store_get_group (self, GAMES_INDIVIDUAL_STORE_FAVORITE,
+ &iter_group, NULL, NULL, TRUE);
+
+ add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
+ individual);
+ grouped = TRUE;
+ }
+
+ if (!grouped)
+ {
+ /* Else add the contact to 'Ungrouped' */
+ individual_store_get_group (self,
+ GAMES_INDIVIDUAL_STORE_UNGROUPED,
+ &iter_group, NULL, NULL, TRUE);
+ add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
+ individual);
+ }
+
+
+finally:
+ individual_store_contact_update (self, individual);
+}
+
+static void
+individual_store_contact_set_active (GamesIndividualStore *self,
+ FolksIndividual *individual,
+ gboolean active,
+ gboolean set_changed)
+{
+ GtkTreeModel *model;
+ GList *iters, *l;
+
+ model = GTK_TREE_MODEL (self);
+
+ iters = individual_store_find_contact (self, individual);
+ for (l = iters; l; l = l->next)
+ {
+ GtkTreePath *path;
+
+ gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
+ GAMES_INDIVIDUAL_STORE_COL_IS_ACTIVE, active,
+ -1);
+
+ if (set_changed)
+ {
+ path = gtk_tree_model_get_path (model, l->data);
+ gtk_tree_model_row_changed (model, path, l->data);
+ gtk_tree_path_free (path);
+ }
+ }
+
+ free_iters (iters);
+}
+
+static void individual_store_contact_active_free (ShowActiveData *data);
+
+static void
+individual_store_contact_active_invalidated (ShowActiveData *data,
+ GObject *old_object)
+{
+ /* Remove the timeout and free the struct, since the individual or individual
+ * store has disappeared. */
+ g_source_remove (data->timeout);
+
+ if (old_object == (GObject *) data->self)
+ data->self = NULL;
+ else if (old_object == (GObject *) data->individual)
+ data->individual = NULL;
+ else
+ g_assert_not_reached ();
+
+ individual_store_contact_active_free (data);
+}
+
+static ShowActiveData *
+individual_store_contact_active_new (GamesIndividualStore *self,
+ FolksIndividual *individual,
+ gboolean remove_)
+{
+ ShowActiveData *data;
+
+ data = g_slice_new0 (ShowActiveData);
+
+ /* We don't actually want to force either the IndividualStore or the
+ * Individual to stay alive, since the user could quit Games or disable
+ * the account before the contact_active timeout is fired. */
+ g_object_weak_ref (G_OBJECT (self),
+ (GWeakNotify) individual_store_contact_active_invalidated, data);
+ g_object_weak_ref (G_OBJECT (individual),
+ (GWeakNotify) individual_store_contact_active_invalidated, data);
+
+ data->self = self;
+ data->individual = individual;
+ data->remove = remove_;
+ data->timeout = 0;
+
+ return data;
+}
+
+static void
+individual_store_contact_active_free (ShowActiveData *data)
+{
+ if (data->self != NULL)
+ {
+ g_object_weak_unref (G_OBJECT (data->self),
+ (GWeakNotify) individual_store_contact_active_invalidated, data);
+ }
+
+ if (data->individual != NULL)
+ {
+ g_object_weak_unref (G_OBJECT (data->individual),
+ (GWeakNotify) individual_store_contact_active_invalidated, data);
+ }
+
+ g_slice_free (ShowActiveData, data);
+}
+
+static gboolean
+individual_store_contact_active_cb (ShowActiveData *data)
+{
+ if (data->remove)
+ {
+ g_debug ("Individual'%s' active timeout, removing item",
+ folks_alias_details_get_alias (
+ FOLKS_ALIAS_DETAILS (data->individual)));
+ games_individual_store_remove_individual (data->self, data->individual);
+ }
+
+ individual_store_contact_set_active (data->self,
+ data->individual, FALSE, TRUE);
+
+ individual_store_contact_active_free (data);
+
+ return FALSE;
+}
+
+typedef struct {
+ GamesIndividualStore *store; /* weak */
+ GCancellable *cancellable; /* owned */
+} LoadAvatarData;
+
+static void
+individual_avatar_pixbuf_received_cb (FolksIndividual *individual,
+ GAsyncResult *result,
+ LoadAvatarData *data)
+{
+ GError *error = NULL;
+ GdkPixbuf *pixbuf;
+
+ pixbuf = games_pixbuf_avatar_from_individual_scaled_finish (individual,
+ result, &error);
+
+ if (error != NULL)
+ {
+ /* No need to display an error if the individal just doesn't have an
+ * avatar */
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ {
+ g_debug ("failed to retrieve pixbuf for individual %s: %s",
+ folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
+ error->message);
+ }
+
+ g_clear_error (&error);
+ }
+ else if (data->store != NULL)
+ {
+ GList *iters, *l;
+
+ iters = individual_store_find_contact (data->store, individual);
+ for (l = iters; l; l = l->next)
+ {
+ gtk_tree_store_set (GTK_TREE_STORE (data->store), l->data,
+ GAMES_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, pixbuf,
+ -1);
+ }
+
+ free_iters (iters);
+ }
+
+ /* Free things */
+ if (data->store != NULL)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (data->store),
+ (gpointer *) &data->store);
+ data->store->priv->avatar_cancellables = g_list_remove (
+ data->store->priv->avatar_cancellables, data->cancellable);
+ }
+
+ tp_clear_object (&pixbuf);
+ g_object_unref (data->cancellable);
+ g_slice_free (LoadAvatarData, data);
+}
+
+
+static void
+individual_store_contact_update (GamesIndividualStore *self,
+ FolksIndividual *individual)
+{
+ ShowActiveData *data;
+ GtkTreeModel *model;
+ GList *iters, *l;
+ gboolean in_list;
+ gboolean was_online = TRUE;
+ gboolean now_online = FALSE;
+ gboolean set_model = FALSE;
+ gboolean do_remove = FALSE;
+ gboolean do_set_active = FALSE;
+ gboolean do_set_refresh = FALSE;
+ gboolean show_avatar = FALSE;
+ GdkPixbuf *pixbuf_status;
+ LoadAvatarData *load_avatar_data;
+
+ model = GTK_TREE_MODEL (self);
+
+ iters = individual_store_find_contact (self, individual);
+ if (!iters)
+ {
+ in_list = FALSE;
+ }
+ else
+ {
+ in_list = TRUE;
+ }
+
+ /* Get online state now. */
+ now_online = folks_presence_details_is_online (
+ FOLKS_PRESENCE_DETAILS (individual));
+
+ if (!in_list)
+ {
+ g_debug ("Individual'%s' in list:NO, should be:YES",
+ folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
+
+ games_individual_store_add_individual (self, individual);
+
+ if (self->priv->show_active)
+ {
+ do_set_active = TRUE;
+ }
+ }
+ else
+ {
+ /* Get online state before. */
+ if (iters && g_list_length (iters) > 0)
+ {
+ gtk_tree_model_get (model, iters->data,
+ GAMES_INDIVIDUAL_STORE_COL_IS_ONLINE, &was_online, -1);
+ }
+
+ /* Is this really an update or an online/offline. */
+ if (self->priv->show_active)
+ {
+ if (was_online != now_online)
+ {
+ do_set_active = TRUE;
+ do_set_refresh = TRUE;
+ }
+ else
+ {
+ /* Was TRUE for presence updates. */
+ /* do_set_active = FALSE; */
+ do_set_refresh = TRUE;
+ }
+ }
+
+ set_model = TRUE;
+ }
+
+ if (self->priv->show_avatars && !self->priv->is_compact)
+ {
+ show_avatar = TRUE;
+ }
+
+ /* 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 ();
+
+ self->priv->avatar_cancellables = g_list_prepend (
+ self->priv->avatar_cancellables, load_avatar_data->cancellable);
+
+ games_pixbuf_avatar_from_individual_scaled_async (individual, 32, 32,
+ load_avatar_data->cancellable,
+ (GAsyncReadyCallback) individual_avatar_pixbuf_received_cb,
+ load_avatar_data);
+
+ pixbuf_status =
+ games_individual_store_get_individual_status_icon (self, individual);
+
+ for (l = iters; l && set_model; l = l->next)
+ {
+ GamesCapabilities individual_caps =
+ individual_store_get_individual_gaming_capabilities (individual);
+
+ gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
+ GAMES_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf_status,
+ GAMES_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
+ GAMES_INDIVIDUAL_STORE_COL_NAME,
+ folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)),
+ GAMES_INDIVIDUAL_STORE_COL_PRESENCE_TYPE,
+ folks_presence_details_get_presence_type (
+ FOLKS_PRESENCE_DETAILS (individual)),
+ GAMES_INDIVIDUAL_STORE_COL_STATUS,
+ folks_presence_details_get_presence_message (
+ FOLKS_PRESENCE_DETAILS (individual)),
+ GAMES_INDIVIDUAL_STORE_COL_COMPACT, self->priv->is_compact,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, FALSE,
+ GAMES_INDIVIDUAL_STORE_COL_IS_ONLINE, now_online,
+ GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL_CAPABILITIES, individual_caps,
+ -1);
+ }
+
+ if (self->priv->show_active && do_set_active)
+ {
+ individual_store_contact_set_active (self, individual, do_set_active,
+ do_set_refresh);
+
+ if (do_set_active)
+ {
+ data =
+ individual_store_contact_active_new (self, individual,
+ do_remove);
+ data->timeout = g_timeout_add_seconds (ACTIVE_USER_SHOW_TIME,
+ (GSourceFunc) individual_store_contact_active_cb, data);
+ }
+ }
+
+ /* FIXME: when someone goes online then offline quickly, the
+ * first timeout sets the user to be inactive and the second
+ * timeout removes the user from the contact list, really we
+ * should remove the first timeout.
+ */
+ free_iters (iters);
+}
+
+static void
+individual_store_individual_updated_cb (FolksIndividual *individual,
+ GParamSpec *param,
+ GamesIndividualStore *self)
+{
+ individual_store_contact_update (self, individual);
+}
+
+static void
+individual_store_contact_updated_cb (GamesContact *contact,
+ GParamSpec *pspec,
+ GamesIndividualStore *self)
+{
+ FolksIndividual *individual;
+
+ individual = g_object_get_data (G_OBJECT (contact), "individual");
+ if (individual == NULL)
+ return;
+
+ individual_store_contact_update (self, individual);
+}
+
+static void
+individual_personas_changed_cb (FolksIndividual *individual,
+ GeeSet *added,
+ GeeSet *removed,
+ GamesIndividualStore *self)
+{
+ GeeIterator *iter;
+
+ iter = gee_iterable_iterator (GEE_ITERABLE (removed));
+ /* FIXME: libfolks hasn't grown capabilities support yet, so we have to go
+ * through the GamesContacts for them. */
+ while (gee_iterator_next (iter))
+ {
+ TpfPersona *persona = gee_iterator_get (iter);
+ TpContact *tp_contact;
+ GamesContact *contact;
+
+ if (TPF_IS_PERSONA (persona))
+ {
+ tp_contact = tpf_persona_get_contact (persona);
+ if (tp_contact != NULL)
+ {
+ contact = games_contact_dup_from_tp_contact (tp_contact);
+ games_contact_set_persona (contact, FOLKS_PERSONA (persona));
+
+ g_object_set_data (G_OBJECT (contact), "individual", NULL);
+ g_signal_handlers_disconnect_by_func (contact,
+ (GCallback) individual_store_contact_updated_cb, self);
+
+ g_object_unref (contact);
+ }
+ }
+
+ g_clear_object (&persona);
+ }
+ g_clear_object (&iter);
+
+ iter = gee_iterable_iterator (GEE_ITERABLE (added));
+ while (gee_iterator_next (iter))
+ {
+ TpfPersona *persona = gee_iterator_get (iter);
+ TpContact *tp_contact;
+ GamesContact *contact;
+
+ if (TPF_IS_PERSONA (persona))
+ {
+ tp_contact = tpf_persona_get_contact (persona);
+ if (tp_contact != NULL)
+ {
+ contact = games_contact_dup_from_tp_contact (tp_contact);
+ games_contact_set_persona (contact, FOLKS_PERSONA (persona));
+
+ g_object_set_data (G_OBJECT (contact), "individual", individual);
+ g_signal_connect (contact, "notify::capabilities",
+ (GCallback) individual_store_contact_updated_cb, self);
+
+ g_object_unref (contact);
+ }
+ }
+
+ g_clear_object (&persona);
+ }
+ g_clear_object (&iter);
+}
+
+static void
+individual_store_favourites_changed_cb (FolksIndividual *individual,
+ GParamSpec *param,
+ GamesIndividualStore *self)
+{
+ g_debug ("Individual %s is %s a favourite",
+ folks_individual_get_id (individual),
+ folks_favourite_details_get_is_favourite (
+ FOLKS_FAVOURITE_DETAILS (individual)) ? "now" : "no longer");
+
+ games_individual_store_remove_individual (self, individual);
+ games_individual_store_add_individual (self, individual);
+}
+
+void
+individual_store_add_individual_and_connect (GamesIndividualStore *self,
+ FolksIndividual *individual)
+{
+ GeeSet *empty_set = gee_set_empty (G_TYPE_NONE, NULL, NULL);
+
+ games_individual_store_add_individual (self, individual);
+
+ g_signal_connect (individual, "notify::avatar",
+ (GCallback) individual_store_individual_updated_cb, self);
+ g_signal_connect (individual, "notify::presence-type",
+ (GCallback) individual_store_individual_updated_cb, self);
+ g_signal_connect (individual, "notify::presence-message",
+ (GCallback) individual_store_individual_updated_cb, self);
+ g_signal_connect (individual, "notify::alias",
+ (GCallback) individual_store_individual_updated_cb, self);
+ g_signal_connect (individual, "personas-changed",
+ (GCallback) individual_personas_changed_cb, self);
+ g_signal_connect (individual, "notify::is-favourite",
+ (GCallback) individual_store_favourites_changed_cb, self);
+
+ /* provide an empty set so the callback can assume non-NULL sets */
+ individual_personas_changed_cb (individual,
+ folks_individual_get_personas (individual), empty_set, self);
+ g_clear_object (&empty_set);
+}
+
+void
+games_individual_store_disconnect_individual (GamesIndividualStore *self,
+ FolksIndividual *individual)
+{
+ GeeSet *empty_set = gee_set_empty (G_TYPE_NONE, NULL, NULL);
+
+ /* provide an empty set so the callback can assume non-NULL sets */
+ individual_personas_changed_cb (individual, empty_set,
+ folks_individual_get_personas (individual), self);
+ g_clear_object (&empty_set);
+
+ g_signal_handlers_disconnect_by_func (individual,
+ (GCallback) individual_store_individual_updated_cb, self);
+ g_signal_handlers_disconnect_by_func (individual,
+ (GCallback) individual_personas_changed_cb, self);
+ g_signal_handlers_disconnect_by_func (individual,
+ (GCallback) individual_store_favourites_changed_cb, self);
+}
+
+void
+individual_store_remove_individual_and_disconnect (
+ GamesIndividualStore *self,
+ FolksIndividual *individual)
+{
+ games_individual_store_disconnect_individual (self, individual);
+ games_individual_store_remove_individual (self, individual);
+}
+
+
diff --git a/libgames-contacts/games-individual-store.h b/libgames-contacts/games-individual-store.h
new file mode 100644
index 0000000..7e75a1a
--- /dev/null
+++ b/libgames-contacts/games-individual-store.h
@@ -0,0 +1,165 @@
+/* games-individual-store.h: Model Folks individuals
+ *
+ * Copyright  2012 Chandni Verma
+ *
+ * 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 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Chandni Verma <chandniverma2112 gmail com>
+ */
+
+#ifndef GAMES_INDIVIDUAL_STORE_H
+#define GAMES_INDIVIDUAL_STORE_H
+
+#include <gtk/gtk.h>
+#include <folks/folks.h>
+
+G_BEGIN_DECLS
+
+#define GAMES_TYPE_INDIVIDUAL_STORE (games_individual_store_get_type ())
+#define GAMES_INDIVIDUAL_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAMES_TYPE_INDIVIDUAL_STORE, GamesIndividualStore))
+#define GAMES_INDIVIDUAL_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAMES_TYPE_INDIVIDUAL_STORE, GamesIndividualStoreClass))
+#define GAMES_IS_INDIVIDUAL_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAMES_TYPE_INDIVIDUAL_STORE))
+#define GAMES_IS_INDIVIDUAL_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAMES_TYPE_INDIVIDUAL_STORE))
+#define GAMES_INDIVIDUAL_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GAMES_TYPE_INDIVIDUAL_STORE, GamesIndividualStoreClass))
+
+typedef struct _GamesIndividualStore GamesIndividualStore;
+typedef struct _GamesIndividualStoreClass GamesIndividualStoreClass;
+typedef struct _GamesIndividualStorePriv GamesIndividualStorePriv;
+
+typedef enum
+{
+ GAMES_INDIVIDUAL_STORE_SORT_STATE,
+ GAMES_INDIVIDUAL_STORE_SORT_NAME
+} GamesIndividualStoreSort;
+
+typedef enum
+{
+ GAMES_INDIVIDUAL_STORE_COL_ICON_STATUS,
+ GAMES_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR,
+ GAMES_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE,
+ GAMES_INDIVIDUAL_STORE_COL_NAME,
+ GAMES_INDIVIDUAL_STORE_COL_PRESENCE_TYPE,
+ GAMES_INDIVIDUAL_STORE_COL_STATUS,
+ GAMES_INDIVIDUAL_STORE_COL_COMPACT,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP,
+ GAMES_INDIVIDUAL_STORE_COL_IS_ACTIVE,
+ GAMES_INDIVIDUAL_STORE_COL_IS_ONLINE,
+ GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL_CAPABILITIES,
+ GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP,
+ GAMES_INDIVIDUAL_STORE_COL_COUNT,
+} GamesIndividualStoreCol;
+
+#define GAMES_INDIVIDUAL_STORE_UNGROUPED _("Ungrouped")
+#define GAMES_INDIVIDUAL_STORE_FAVORITE _("Favorite People")
+#define GAMES_INDIVIDUAL_STORE_PEOPLE_NEARBY _("People Nearby")
+
+struct _GamesIndividualStore{
+ GtkListStore parent;
+
+ /*< private >*/
+ GamesIndividualStorePriv *priv;
+};
+
+struct _GamesIndividualStoreClass{
+ GtkListStoreClass parent_class;
+
+ /* class members */
+ void (*reload_individuals) (GamesIndividualStore *self);
+ gboolean (*initial_loading) (GamesIndividualStore *self);
+};
+
+/* used by GAMES_TYPE_INDIVIDUAL_STORE */
+GType games_individual_store_get_type (void) G_GNUC_CONST;
+
+/* Function prototypes */
+
+gboolean games_individual_store_get_show_avatars (
+ GamesIndividualStore *store);
+
+void games_individual_store_set_show_avatars (GamesIndividualStore *store,
+ gboolean show_avatars);
+
+gboolean games_individual_store_get_show_groups (
+ GamesIndividualStore *store);
+
+void games_individual_store_set_show_groups (GamesIndividualStore *store,
+ gboolean show_groups);
+
+gboolean games_individual_store_get_is_compact (
+ GamesIndividualStore *store);
+
+void games_individual_store_set_is_compact (GamesIndividualStore *store,
+ gboolean is_compact);
+
+gboolean games_individual_store_get_show_protocols (
+ GamesIndividualStore *store);
+
+void games_individual_store_set_show_protocols (
+ GamesIndividualStore *store,
+ gboolean show_protocols);
+
+GamesIndividualStoreSort games_individual_store_get_sort_criterium (
+ GamesIndividualStore *store);
+
+void games_individual_store_set_sort_criterium (
+ GamesIndividualStore *store,
+ GamesIndividualStoreSort sort_criterium);
+
+gboolean games_individual_store_row_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data);
+
+gchar *games_individual_store_get_parent_group (GtkTreeModel *model,
+ GtkTreePath *path,
+ gboolean *path_is_group,
+ gboolean *is_fake_group);
+
+GdkPixbuf *games_individual_store_get_individual_status_icon (
+ GamesIndividualStore *store,
+ FolksIndividual *individual);
+
+void individual_store_add_individual_and_connect (
+ GamesIndividualStore *self,
+ FolksIndividual *individual);
+
+void individual_store_remove_individual_and_disconnect (
+ GamesIndividualStore *self,
+ FolksIndividual *individual);
+
+/* protected */
+
+void games_individual_store_disconnect_individual (
+ GamesIndividualStore *self,
+ FolksIndividual *individual);
+
+void games_individual_store_remove_individual (
+ GamesIndividualStore *self,
+ FolksIndividual *individual);
+
+void games_individual_store_add_individual (
+ GamesIndividualStore *self,
+ FolksIndividual *individual);
+
+void games_individual_store_refresh_individual (
+ GamesIndividualStore *self,
+ FolksIndividual *individual);
+
+
+G_END_DECLS
+#endif /* GAMES_INDIVIDUAL_STORE_H */
+/* EOF */
diff --git a/libgames-contacts/games-ui-utils.c b/libgames-contacts/games-ui-utils.c
new file mode 100644
index 0000000..668cae7
--- /dev/null
+++ b/libgames-contacts/games-ui-utils.c
@@ -0,0 +1,1091 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* games-ui-utils.c - This file contains generic UI specific code for use in
+ * displaying games and contacts to players.
+ *
+ * Copyright (C) 2002-2007 Imendio AB
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ * Copyright (C) 2012 Chandni Verma
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ *
+ * This file has been prepared from utilities found in Empathy
+ * (https://live.gnome.org/Empathy)
+ */
+
+#include <config.h>
+
+#include <X11/Xatom.h>
+#include <gdk/gdkx.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include <libxml/uri.h>
+
+#include <telepathy-glib/util.h>
+#include <folks/folks.h>
+
+#include "games-ui-utils.h"
+
+/* Contact presence image names */
+
+#define GAMES_IMAGE_OFFLINE "user-offline"
+/* user-invisible is not (yet?) in the naming spec but already implemented by
+ * some theme */
+#define GAMES_IMAGE_HIDDEN "user-invisible"
+#define GAMES_IMAGE_AVAILABLE "user-available"
+#define GAMES_IMAGE_BUSY "user-busy"
+#define GAMES_IMAGE_AWAY "user-away"
+#define GAMES_IMAGE_EXT_AWAY "user-extended-away"
+#define GAMES_IMAGE_IDLE "user-idle"
+#define GAMES_IMAGE_PENDING "games-pending"
+
+
+gboolean
+games_xml_validate (xmlDoc *doc,
+ const gchar *dtd_filename)
+{
+ gchar *path;
+ xmlChar *escaped;
+ xmlValidCtxt cvp;
+ xmlDtd *dtd;
+ gboolean ret;
+
+ path = g_build_filename (PKGDATADIR, PACKAGE, dtd_filename, NULL);
+
+ g_debug ("Loading dtd file %s", path);
+
+ /* The list of valid chars is taken from libxml. */
+ escaped = xmlURIEscapeStr ((const xmlChar *) path,
+ (const xmlChar *)":@&=+$,/?;");
+ g_free (path);
+
+ memset (&cvp, 0, sizeof (cvp));
+ dtd = xmlParseDTD (NULL, escaped);
+ ret = xmlValidateDtd (&cvp, doc, dtd);
+
+ xmlFree (escaped);
+ xmlFreeDtd (dtd);
+
+ return ret;
+}
+
+void
+games_builder_connect (GtkBuilder *gui,
+ gpointer user_data,
+ const gchar *first_object,
+ ...)
+{
+ va_list args;
+ const gchar *name;
+ const gchar *sig;
+ GObject *object;
+ GCallback callback;
+
+ va_start (args, first_object);
+ for (name = first_object; name; name = va_arg (args, const gchar *)) {
+ sig = va_arg (args, const gchar *);
+ callback = va_arg (args, GCallback);
+
+ object = gtk_builder_get_object (gui, name);
+ if (!object) {
+ g_warning ("File is missing object '%s'.", name);
+ continue;
+ }
+
+ g_signal_connect (object, sig, callback, user_data);
+ }
+
+ va_end (args);
+}
+
+GtkWidget *
+games_builder_unref_and_keep_widget (GtkBuilder *gui,
+ GtkWidget *widget)
+{
+ /* On construction gui sinks the initial reference to widget. When gui
+ * is finalized it will drop its ref to widget. We take our own ref to
+ * prevent widget being finalised. The widget is forced to have a
+ * floating reference, like when it was initially unowned so that it can
+ * be used like any other GtkWidget. */
+
+ g_object_ref (widget);
+ g_object_force_floating (G_OBJECT (widget));
+ g_object_unref (gui);
+
+ return widget;
+}
+
+const gchar *
+games_icon_name_for_presence (TpConnectionPresenceType presence)
+{
+ switch (presence) {
+ case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
+ return GAMES_IMAGE_AVAILABLE;
+ case TP_CONNECTION_PRESENCE_TYPE_BUSY:
+ return GAMES_IMAGE_BUSY;
+ case TP_CONNECTION_PRESENCE_TYPE_AWAY:
+ return GAMES_IMAGE_AWAY;
+ case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
+ if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
+ GAMES_IMAGE_EXT_AWAY))
+ return GAMES_IMAGE_EXT_AWAY;
+
+ /* The 'extended-away' icon is not an official one so we fallback to idle if
+ * it's not implemented */
+ return GAMES_IMAGE_IDLE;
+ case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
+ if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
+ GAMES_IMAGE_HIDDEN))
+ return GAMES_IMAGE_HIDDEN;
+
+ /* The 'hidden' icon is not an official one so we fallback to offline if
+ * it's not implemented */
+ return GAMES_IMAGE_OFFLINE;
+ case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
+ case TP_CONNECTION_PRESENCE_TYPE_ERROR:
+ return GAMES_IMAGE_OFFLINE;
+ case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
+ return GAMES_IMAGE_PENDING;
+ case TP_CONNECTION_PRESENCE_TYPE_UNSET:
+ default:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+const gchar *
+games_icon_name_for_contact (GamesContact *contact)
+{
+ TpConnectionPresenceType presence;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact),
+ GAMES_IMAGE_OFFLINE);
+
+ presence = games_contact_get_presence (contact);
+ return games_icon_name_for_presence (presence);
+}
+
+/* Translate Folks' general presence type to the Tp presence type */
+TpConnectionPresenceType
+games_folks_presence_type_to_tp (FolksPresenceType type)
+{
+ return (TpConnectionPresenceType) type;
+}
+
+const gchar *
+games_icon_name_for_individual (FolksIndividual *individual)
+{
+ FolksPresenceType folks_presence;
+ TpConnectionPresenceType presence;
+
+ folks_presence =
+ folks_presence_details_get_presence_type (
+ FOLKS_PRESENCE_DETAILS (individual));
+ presence = games_folks_presence_type_to_tp (folks_presence);
+
+ return games_icon_name_for_presence (presence);
+}
+
+const gchar *
+games_protocol_name_for_contact (GamesContact *contact)
+{
+ TpAccount *account;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+ account = games_contact_get_account (contact);
+ if (account == NULL) {
+ return NULL;
+ }
+
+ return tp_account_get_icon_name (account);
+}
+
+GdkPixbuf *
+games_pixbuf_from_data (gchar *data,
+ gsize data_size)
+{
+ return games_pixbuf_from_data_and_mime (data, data_size, NULL);
+}
+
+GdkPixbuf *
+games_pixbuf_from_data_and_mime (gchar *data,
+ gsize data_size,
+ gchar **mime_type)
+{
+ GdkPixbufLoader *loader;
+ GdkPixbufFormat *format;
+ GdkPixbuf *pixbuf = NULL;
+ gchar **mime_types;
+ GError *error = NULL;
+
+ if (!data) {
+ return NULL;
+ }
+
+ loader = gdk_pixbuf_loader_new ();
+ if (!gdk_pixbuf_loader_write (loader, (guchar *) data, data_size, &error)) {
+ g_debug ("Failed to write to pixbuf loader: %s",
+ error ? error->message : "No error given");
+ goto out;
+ }
+ if (!gdk_pixbuf_loader_close (loader, &error)) {
+ g_debug ("Failed to close pixbuf loader: %s",
+ error ? error->message : "No error given");
+ goto out;
+ }
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+ if (pixbuf) {
+ g_object_ref (pixbuf);
+
+ if (mime_type != NULL) {
+ format = gdk_pixbuf_loader_get_format (loader);
+ mime_types = gdk_pixbuf_format_get_mime_types (format);
+
+ *mime_type = g_strdup (*mime_types);
+ if (mime_types[1] != NULL) {
+ g_debug ("Loader supports more than one mime "
+ "type! Picking the first one, %s",
+ *mime_type);
+ }
+ g_strfreev (mime_types);
+ }
+ }
+
+out:
+ g_clear_error (&error);
+ g_object_unref (loader);
+
+ return pixbuf;
+}
+
+struct SizeData {
+ gint width;
+ gint height;
+ gboolean preserve_aspect_ratio;
+};
+
+static void
+pixbuf_from_avatar_size_prepared_cb (GdkPixbufLoader *loader,
+ int width,
+ int height,
+ struct SizeData *data)
+{
+ g_return_if_fail (width > 0 && height > 0);
+
+ if (data->preserve_aspect_ratio && (data->width > 0 || data->height > 0)) {
+ if (width < data->width && height < data->height) {
+ width = width;
+ height = height;
+ }
+
+ if (data->width < 0) {
+ width = width * (double) data->height / (gdouble) height;
+ height = data->height;
+ } else if (data->height < 0) {
+ height = height * (double) data->width / (double) width;
+ width = data->width;
+ } else if ((double) height * (double) data->width >
+ (double) width * (double) data->height) {
+ width = 0.5 + (double) width * (double) data->height / (double) height;
+ height = data->height;
+ } else {
+ height = 0.5 + (double) height * (double) data->width / (double) width;
+ width = data->width;
+ }
+ } else {
+ if (data->width > 0) {
+ width = data->width;
+ }
+
+ if (data->height > 0) {
+ height = data->height;
+ }
+ }
+
+ gdk_pixbuf_loader_set_size (loader, width, height);
+}
+
+static void
+games_avatar_pixbuf_roundify (GdkPixbuf *pixbuf)
+{
+ gint width, height, rowstride;
+ guchar *pixels;
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+ if (width < 6 || height < 6) {
+ return;
+ }
+
+ /* Top left */
+ pixels[3] = 0;
+ pixels[7] = 0x80;
+ pixels[11] = 0xC0;
+ pixels[rowstride + 3] = 0x80;
+ pixels[rowstride * 2 + 3] = 0xC0;
+
+ /* Top right */
+ pixels[width * 4 - 1] = 0;
+ pixels[width * 4 - 5] = 0x80;
+ pixels[width * 4 - 9] = 0xC0;
+ pixels[rowstride + (width * 4) - 1] = 0x80;
+ pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;
+
+ /* Bottom left */
+ pixels[(height - 1) * rowstride + 3] = 0;
+ pixels[(height - 1) * rowstride + 7] = 0x80;
+ pixels[(height - 1) * rowstride + 11] = 0xC0;
+ pixels[(height - 2) * rowstride + 3] = 0x80;
+ pixels[(height - 3) * rowstride + 3] = 0xC0;
+
+ /* Bottom right */
+ pixels[height * rowstride - 1] = 0;
+ pixels[(height - 1) * rowstride - 1] = 0x80;
+ pixels[(height - 2) * rowstride - 1] = 0xC0;
+ pixels[height * rowstride - 5] = 0x80;
+ pixels[height * rowstride - 9] = 0xC0;
+}
+
+static gboolean
+games_gdk_pixbuf_is_opaque (GdkPixbuf *pixbuf)
+{
+ gint height, rowstride, i;
+ guchar *pixels;
+ guchar *row;
+
+ height = gdk_pixbuf_get_height (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+ row = pixels;
+ for (i = 3; i < rowstride; i+=4) {
+ if (row[i] < 0xfe) {
+ return FALSE;
+ }
+ }
+
+ for (i = 1; i < height - 1; i++) {
+ row = pixels + (i*rowstride);
+ if (row[3] < 0xfe || row[rowstride-1] < 0xfe) {
+ return FALSE;
+ }
+ }
+
+ row = pixels + ((height-1) * rowstride);
+ for (i = 3; i < rowstride; i+=4) {
+ if (row[i] < 0xfe) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static GdkPixbuf *
+avatar_pixbuf_from_loader (GdkPixbufLoader *loader)
+{
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+ if (!gdk_pixbuf_get_has_alpha (pixbuf)) {
+ GdkPixbuf *rounded_pixbuf;
+
+ rounded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf));
+ gdk_pixbuf_copy_area (pixbuf, 0, 0,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ rounded_pixbuf,
+ 0, 0);
+ pixbuf = rounded_pixbuf;
+ } else {
+ g_object_ref (pixbuf);
+ }
+
+ if (games_gdk_pixbuf_is_opaque (pixbuf)) {
+ games_avatar_pixbuf_roundify (pixbuf);
+ }
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+games_pixbuf_from_avatar_scaled (GamesAvatar *avatar,
+ gint width,
+ gint height)
+{
+ GdkPixbuf *pixbuf;
+ GdkPixbufLoader *loader;
+ struct SizeData data;
+ GError *error = NULL;
+
+ if (!avatar) {
+ return NULL;
+ }
+
+ data.width = width;
+ data.height = height;
+ data.preserve_aspect_ratio = TRUE;
+
+ loader = gdk_pixbuf_loader_new ();
+
+ g_signal_connect (loader, "size-prepared",
+ G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
+ &data);
+
+ if (avatar->len == 0) {
+ g_warning ("Avatar has 0 length");
+ return NULL;
+ } else if (!gdk_pixbuf_loader_write (loader, avatar->data, avatar->len, &error)) {
+ g_warning ("Couldn't write avatar image:%p with "
+ "length:%" G_GSIZE_FORMAT " to pixbuf loader: %s",
+ avatar->data, avatar->len, error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ gdk_pixbuf_loader_close (loader, NULL);
+ pixbuf = avatar_pixbuf_from_loader (loader);
+
+ g_object_unref (loader);
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+games_pixbuf_avatar_from_contact_scaled (GamesContact *contact,
+ gint width,
+ gint height)
+{
+ GamesAvatar *avatar;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+ avatar = games_contact_get_avatar (contact);
+
+ return games_pixbuf_from_avatar_scaled (avatar, width, height);
+}
+
+typedef struct {
+ FolksIndividual *individual;
+ GSimpleAsyncResult *result;
+ guint width;
+ guint height;
+ struct SizeData size_data;
+ GdkPixbufLoader *loader;
+ GCancellable *cancellable;
+ guint8 data[512];
+} PixbufAvatarFromIndividualClosure;
+
+static PixbufAvatarFromIndividualClosure *
+pixbuf_avatar_from_individual_closure_new (FolksIndividual *individual,
+ GSimpleAsyncResult *result,
+ gint width,
+ gint height,
+ GCancellable *cancellable)
+{
+ PixbufAvatarFromIndividualClosure *closure;
+
+ g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+
+ closure = g_new0 (PixbufAvatarFromIndividualClosure, 1);
+ closure->individual = g_object_ref (individual);
+ closure->result = g_object_ref (result);
+ closure->width = width;
+ closure->height = height;
+ if (cancellable != NULL)
+ closure->cancellable = g_object_ref (cancellable);
+
+ return closure;
+}
+
+static void
+pixbuf_avatar_from_individual_closure_free (
+ PixbufAvatarFromIndividualClosure *closure)
+{
+ g_clear_object (&closure->cancellable);
+ tp_clear_object (&closure->loader);
+ g_object_unref (closure->individual);
+ g_object_unref (closure->result);
+ g_free (closure);
+}
+
+static void
+avatar_icon_load_close_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GError *error = NULL;
+
+ g_input_stream_close_finish (G_INPUT_STREAM (object), result, &error);
+
+ if (error != NULL) {
+ g_debug ("Failed to close pixbuf stream: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+avatar_icon_load_read_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GInputStream *stream = G_INPUT_STREAM (object);
+ PixbufAvatarFromIndividualClosure *closure = user_data;
+ gssize n_read;
+ GError *error = NULL;
+
+ /* Finish reading this chunk from the stream */
+ n_read = g_input_stream_read_finish (stream, result, &error);
+ if (error != NULL) {
+ g_debug ("Failed to finish read from pixbuf stream: %s",
+ error->message);
+ g_simple_async_result_set_from_error (closure->result, error);
+ goto out_close;
+ }
+
+ /* Write the chunk to the pixbuf loader */
+ if (!gdk_pixbuf_loader_write (closure->loader, (guchar *) closure->data,
+ n_read, &error)) {
+ g_debug ("Failed to write to pixbuf loader: %s",
+ error ? error->message : "No error given");
+ g_simple_async_result_set_from_error (closure->result, error);
+ goto out_close;
+ }
+
+ if (n_read == 0) {
+ /* EOF? */
+ if (!gdk_pixbuf_loader_close (closure->loader, &error)) {
+ g_debug ("Failed to close pixbuf loader: %s",
+ error ? error->message : "No error given");
+ g_simple_async_result_set_from_error (closure->result, error);
+ goto out;
+ }
+
+ /* We're done. */
+ g_simple_async_result_set_op_res_gpointer (closure->result,
+ avatar_pixbuf_from_loader (closure->loader),
+ g_object_unref);
+
+ goto out;
+ } else {
+ /* Loop round and read another chunk. */
+ g_input_stream_read_async (stream, closure->data,
+ G_N_ELEMENTS (closure->data),
+ G_PRIORITY_DEFAULT, closure->cancellable,
+ avatar_icon_load_read_cb, closure);
+
+ return;
+ }
+
+out_close:
+ /* We must close the pixbuf loader before unreffing it. */
+ gdk_pixbuf_loader_close (closure->loader, NULL);
+
+out:
+ /* Close the file for safety (even though it should be
+ * automatically closed when the stream is finalised). */
+ g_input_stream_close_async (stream, G_PRIORITY_DEFAULT, NULL,
+ (GAsyncReadyCallback) avatar_icon_load_close_cb, NULL);
+
+ g_simple_async_result_complete (closure->result);
+
+ g_clear_error (&error);
+ pixbuf_avatar_from_individual_closure_free (closure);
+}
+
+static void
+avatar_icon_load_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GLoadableIcon *icon = G_LOADABLE_ICON (object);
+ PixbufAvatarFromIndividualClosure *closure = user_data;
+ GInputStream *stream;
+ GError *error = NULL;
+
+ stream = g_loadable_icon_load_finish (icon, result, NULL, &error);
+ if (error != NULL) {
+ g_debug ("Failed to open avatar stream: %s", error->message);
+ g_simple_async_result_set_from_error (closure->result, error);
+ goto out;
+ }
+
+ closure->size_data.width = closure->width;
+ closure->size_data.height = closure->height;
+ closure->size_data.preserve_aspect_ratio = TRUE;
+
+ /* Load the data into a pixbuf loader in chunks. */
+ closure->loader = gdk_pixbuf_loader_new ();
+
+ g_signal_connect (closure->loader, "size-prepared",
+ G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
+ &(closure->size_data));
+
+ /* Begin to read the first chunk. */
+ g_input_stream_read_async (stream, closure->data,
+ G_N_ELEMENTS (closure->data),
+ G_PRIORITY_DEFAULT, closure->cancellable,
+ avatar_icon_load_read_cb, closure);
+
+ g_object_unref (stream);
+
+ return;
+
+out:
+ g_simple_async_result_complete (closure->result);
+
+ g_clear_error (&error);
+ tp_clear_object (&stream);
+ pixbuf_avatar_from_individual_closure_free (closure);
+}
+
+void
+games_pixbuf_avatar_from_individual_scaled_async (
+ FolksIndividual *individual,
+ gint width,
+ gint height,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GLoadableIcon *avatar_icon;
+ GSimpleAsyncResult *result;
+ PixbufAvatarFromIndividualClosure *closure;
+
+ result = g_simple_async_result_new (G_OBJECT (individual),
+ callback, user_data,
+ games_pixbuf_avatar_from_individual_scaled_async);
+
+ avatar_icon =
+ folks_avatar_details_get_avatar (FOLKS_AVATAR_DETAILS (individual));
+ if (avatar_icon == NULL) {
+ g_simple_async_result_set_error (result, G_IO_ERROR,
+ G_IO_ERROR_NOT_FOUND, "no avatar found");
+
+ g_simple_async_result_complete (result);
+ g_object_unref (result);
+ return;
+ }
+
+ closure = pixbuf_avatar_from_individual_closure_new (individual, result,
+ width, height,
+ cancellable);
+
+ g_return_if_fail (closure != NULL);
+
+ g_loadable_icon_load_async (avatar_icon, width, cancellable,
+ avatar_icon_load_cb, closure);
+
+ g_object_unref (result);
+}
+
+/* Return a ref on the GdkPixbuf */
+GdkPixbuf *
+games_pixbuf_avatar_from_individual_scaled_finish (
+ FolksIndividual *individual,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+ gboolean result_valid;
+ GdkPixbuf *pixbuf;
+
+ g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), NULL);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+
+ result_valid = g_simple_async_result_is_valid (result,
+ G_OBJECT (individual),
+ games_pixbuf_avatar_from_individual_scaled_async);
+ g_return_val_if_fail (result_valid, NULL);
+
+ pixbuf = g_simple_async_result_get_op_res_gpointer (simple);
+ return pixbuf != NULL ? g_object_ref (pixbuf) : NULL;
+}
+
+GdkPixbuf *
+games_pixbuf_contact_status_icon (GamesContact *contact,
+ gboolean show_protocol)
+{
+ const gchar *icon_name;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+ icon_name = games_icon_name_for_contact (contact);
+
+ if (icon_name == NULL) {
+ return NULL;
+ }
+ return games_pixbuf_contact_status_icon_with_icon_name (contact,
+ icon_name,
+ show_protocol);
+}
+
+GdkPixbuf *
+games_pixbuf_contact_status_icon_with_icon_name (GamesContact *contact,
+ const gchar *icon_name,
+ gboolean show_protocol)
+{
+ GdkPixbuf *pix_status;
+ GdkPixbuf *pix_protocol;
+ gchar *icon_filename;
+ gint height, width;
+ gint numerator, denominator;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact) ||
+ (show_protocol == FALSE), NULL);
+ g_return_val_if_fail (icon_name != NULL, NULL);
+
+ numerator = 3;
+ denominator = 4;
+
+ icon_filename = games_filename_from_icon_name (icon_name,
+ GTK_ICON_SIZE_MENU);
+ if (icon_filename == NULL) {
+ g_debug ("icon name: %s could not be found\n", icon_name);
+ return NULL;
+ }
+
+ pix_status = gdk_pixbuf_new_from_file (icon_filename, NULL);
+
+ if (pix_status == NULL) {
+ g_debug ("Could not open icon %s\n", icon_filename);
+ g_free (icon_filename);
+ return NULL;
+ }
+
+ g_free (icon_filename);
+
+ if (!show_protocol)
+ return pix_status;
+
+ height = gdk_pixbuf_get_height (pix_status);
+ width = gdk_pixbuf_get_width (pix_status);
+
+ pix_protocol = games_pixbuf_protocol_from_contact_scaled (contact,
+ width * numerator / denominator,
+ height * numerator / denominator);
+
+ if (pix_protocol == NULL) {
+ return pix_status;
+ }
+ gdk_pixbuf_composite (pix_protocol, pix_status,
+ 0, height - height * numerator / denominator,
+ width * numerator / denominator, height * numerator / denominator,
+ 0, height - height * numerator / denominator,
+ 1, 1,
+ GDK_INTERP_BILINEAR, 255);
+
+ g_object_unref (pix_protocol);
+
+ return pix_status;
+}
+
+GdkPixbuf *
+games_pixbuf_protocol_from_contact_scaled (GamesContact *contact,
+ gint width,
+ gint height)
+{
+ TpAccount *account;
+ gchar *filename;
+ GdkPixbuf *pixbuf = NULL;
+
+ g_return_val_if_fail (GAMES_IS_CONTACT (contact), NULL);
+
+ account = games_contact_get_account (contact);
+ filename = games_filename_from_icon_name (tp_account_get_icon_name (account),
+ GTK_ICON_SIZE_MENU);
+ if (filename != NULL) {
+ pixbuf = gdk_pixbuf_new_from_file_at_size (filename, width, height, NULL);
+ g_free (filename);
+ }
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+games_pixbuf_scale_down_if_necessary (GdkPixbuf *pixbuf, gint max_size)
+{
+ gint width, height;
+ gdouble factor;
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ if (width > 0 && (width > max_size || height > max_size)) {
+ factor = (gdouble) max_size / MAX (width, height);
+
+ width = width * factor;
+ height = height * factor;
+
+ return gdk_pixbuf_scale_simple (pixbuf,
+ width, height,
+ GDK_INTERP_HYPER);
+ }
+
+ return g_object_ref (pixbuf);
+}
+
+GdkPixbuf *
+games_pixbuf_from_icon_name_sized (const gchar *icon_name,
+ gint size)
+{
+ GtkIconTheme *theme;
+ GdkPixbuf *pixbuf;
+ GError *error = NULL;
+
+ if (!icon_name) {
+ return NULL;
+ }
+
+ theme = gtk_icon_theme_get_default ();
+
+ pixbuf = gtk_icon_theme_load_icon (theme,
+ icon_name,
+ size,
+ 0,
+ &error);
+ if (error) {
+ g_debug ("Error loading icon: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+games_pixbuf_from_icon_name (const gchar *icon_name,
+ GtkIconSize icon_size)
+{
+ gint w, h;
+ gint size = 48;
+
+ if (!icon_name) {
+ return NULL;
+ }
+
+ if (gtk_icon_size_lookup (icon_size, &w, &h)) {
+ size = (w + h) / 2;
+ }
+
+ return games_pixbuf_from_icon_name_sized (icon_name, size);
+}
+
+gchar *
+games_filename_from_icon_name (const gchar *icon_name,
+ GtkIconSize icon_size)
+{
+ GtkIconTheme *icon_theme;
+ GtkIconInfo *icon_info;
+ gint w, h;
+ gint size = 48;
+ gchar *ret;
+
+ icon_theme = gtk_icon_theme_get_default ();
+
+ if (gtk_icon_size_lookup (icon_size, &w, &h)) {
+ size = (w + h) / 2;
+ }
+
+ icon_info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, size, 0);
+ if (icon_info == NULL)
+ return NULL;
+
+ ret = g_strdup (gtk_icon_info_get_filename (icon_info));
+ gtk_icon_info_free (icon_info);
+
+ return ret;
+}
+
+gint64
+games_get_current_action_time (void)
+{
+ return (tp_user_action_time_from_x11 (gtk_get_current_event_time ()));
+}
+
+void
+games_launch_program (const gchar *dir,
+ const gchar *name,
+ const gchar *args)
+{
+ GdkDisplay *display;
+ GError *error = NULL;
+ gchar *path, *cmd;
+ GAppInfo *app_info;
+ GdkAppLaunchContext *context = NULL;
+
+ path = g_build_filename (dir, name, NULL);
+
+ if (args != NULL)
+ cmd = g_strconcat (path, " ", args, NULL);
+ else
+ cmd = g_strdup (path);
+
+ app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &error);
+ if (app_info == NULL)
+ {
+ g_debug ("Failed to create app info: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ display = gdk_display_get_default ();
+ context = gdk_display_get_app_launch_context (display);
+
+ if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
+ &error))
+ {
+ g_warning ("Failed to launch %s: %s", name, error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+out:
+ tp_clear_object (&app_info);
+ tp_clear_object (&context);
+ g_free (path);
+ g_free (cmd);
+}
+
+/* Most of the workspace manipulation code has been copied from libwnck
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2005-2007 Vincent Untz
+ */
+static void
+_wnck_activate_workspace (Screen *screen,
+ int new_active_space,
+ Time timestamp)
+{
+ Display *display;
+ Window root;
+ XEvent xev;
+
+ display = DisplayOfScreen (screen);
+ root = RootWindowOfScreen (screen);
+
+ xev.xclient.type = ClientMessage;
+ xev.xclient.serial = 0;
+ xev.xclient.send_event = True;
+ xev.xclient.display = display;
+ xev.xclient.window = root;
+ xev.xclient.message_type = gdk_x11_get_xatom_by_name ("_NET_CURRENT_DESKTOP");
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = new_active_space;
+ xev.xclient.data.l[1] = timestamp;
+ xev.xclient.data.l[2] = 0;
+ xev.xclient.data.l[3] = 0;
+ xev.xclient.data.l[4] = 0;
+
+ gdk_error_trap_push ();
+ XSendEvent (display, root, False,
+ SubstructureRedirectMask | SubstructureNotifyMask,
+ &xev);
+ XSync (display, False);
+ gdk_error_trap_pop_ignored ();
+}
+
+static gboolean
+_wnck_get_cardinal (Screen *screen,
+ Window xwindow,
+ Atom atom,
+ int *val)
+{
+ Display *display;
+ Atom type;
+ int format;
+ gulong nitems;
+ gulong bytes_after;
+ gulong *num;
+ int err, result;
+
+ display = DisplayOfScreen (screen);
+
+ *val = 0;
+
+ gdk_error_trap_push ();
+ type = None;
+ result = XGetWindowProperty (display, xwindow, atom,
+ 0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
+ &bytes_after, (void *) &num);
+ err = gdk_error_trap_pop ();
+ if (err != Success ||
+ result != Success)
+ return FALSE;
+
+ if (type != XA_CARDINAL)
+ {
+ XFree (num);
+ return FALSE;
+ }
+
+ *val = *num;
+
+ XFree (num);
+
+ return TRUE;
+}
+
+static int
+window_get_workspace (Screen *xscreen,
+ Window win)
+{
+ int number;
+
+ if (!_wnck_get_cardinal (xscreen, win,
+ gdk_x11_get_xatom_by_name ("_NET_WM_DESKTOP"), &number))
+ return -1;
+
+ return number;
+}
+
+/* Ask X to move to the desktop on which @window currently is
+ * and the present @window. */
+void
+games_move_to_window_desktop (GtkWindow *window,
+ guint32 timestamp)
+{
+ GdkScreen *screen;
+ Screen *xscreen;
+ GdkWindow *gdk_window;
+ int workspace;
+
+ screen = gtk_window_get_screen (window);
+ xscreen = gdk_x11_screen_get_xscreen (screen);
+ gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
+
+ workspace = window_get_workspace (xscreen,
+ gdk_x11_window_get_xid (gdk_window));
+ if (workspace == -1)
+ goto out;
+
+ _wnck_activate_workspace (xscreen, workspace, timestamp);
+
+out:
+ gtk_window_present_with_time (window, timestamp);
+}
+
diff --git a/libgames-contacts/games-ui-utils.h b/libgames-contacts/games-ui-utils.h
new file mode 100644
index 0000000..9fc7f54
--- /dev/null
+++ b/libgames-contacts/games-ui-utils.h
@@ -0,0 +1,143 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* games-ui-utils.h - This file contains generic UI specific code for use in
+ * displaying games and contacts to players.
+ *
+ * Copyright (C) 2002-2007 Imendio AB
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ * Copyright (C) 2012 Chandni Verma
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ *
+ * This file has been prepared from utilities provided in
+ * Empathy (https://live.gnome.org/Empathy).
+ */
+
+#ifndef __GAMES_UI_UTILS_H__
+#define __GAMES_UI_UTILS_H__
+
+#include <gtk/gtk.h>
+#include <libxml/uri.h>
+#include <libxml/tree.h>
+
+#include <folks/folks.h>
+
+#include "games-contact.h"
+
+G_BEGIN_DECLS
+
+#define STR_EMPTY(x) ((x) == NULL || (x)[0] == '\0')
+
+#define GAMES_RECT_IS_ON_SCREEN(x,y,w,h) ((x) + (w) > 0 && \
+ (y) + (h) > 0 && \
+ (x) < gdk_screen_width () && \
+ (y) < gdk_screen_height ())
+
+typedef void (*GamesPixbufAvatarFromIndividualCb) (FolksIndividual *individual,
+ GdkPixbuf *pixbuf,
+ gpointer user_data);
+
+void games_gtk_init (void);
+
+/* Glade */
+GtkBuilder * games_builder_get_file (const gchar *filename,
+ const gchar *first_object,
+ ...);
+void games_builder_connect (GtkBuilder *gui,
+ gpointer user_data,
+ const gchar *first_object,
+ ...);
+GtkWidget *games_builder_unref_and_keep_widget (GtkBuilder *gui,
+ GtkWidget *root);
+
+/* Pixbufs */
+const gchar * games_icon_name_for_presence (TpConnectionPresenceType presence);
+const gchar * games_icon_name_for_contact (GamesContact *contact);
+TpConnectionPresenceType games_folks_presence_type_to_tp (FolksPresenceType type);
+const gchar * games_icon_name_for_individual (FolksIndividual *individual);
+const gchar * games_protocol_name_for_contact (GamesContact *contact);
+GdkPixbuf * games_pixbuf_from_data (gchar *data,
+ gsize data_size);
+GdkPixbuf * games_pixbuf_from_data_and_mime (gchar *data,
+ gsize data_size,
+ gchar **mime_type);
+void games_pixbuf_avatar_from_individual_scaled_async (FolksIndividual *individual,
+ gint width,
+ gint height,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GdkPixbuf * games_pixbuf_avatar_from_individual_scaled_finish (
+ FolksIndividual *individual,
+ GAsyncResult *result,
+ GError **error);
+GdkPixbuf * games_pixbuf_from_avatar_scaled (GamesAvatar *avatar,
+ gint width,
+ gint height);
+GdkPixbuf * games_pixbuf_avatar_from_contact_scaled (GamesContact *contact,
+ gint width,
+ gint height);
+GdkPixbuf * games_pixbuf_protocol_from_contact_scaled (GamesContact *contact,
+ gint width,
+ gint height);
+GdkPixbuf * games_pixbuf_contact_status_icon (GamesContact *contact,
+ gboolean show_protocol);
+GdkPixbuf * games_pixbuf_contact_status_icon_with_icon_name (GamesContact *contact,
+ const gchar *icon_name,
+ gboolean show_protocol);
+GdkPixbuf * games_pixbuf_scale_down_if_necessary (GdkPixbuf *pixbuf,
+ gint max_size);
+GdkPixbuf * games_pixbuf_from_icon_name (const gchar *icon_name,
+ GtkIconSize icon_size);
+GdkPixbuf * games_pixbuf_from_icon_name_sized (const gchar *icon_name,
+ gint size);
+gchar * games_filename_from_icon_name (const gchar *icon_name,
+ GtkIconSize icon_size);
+
+/* Windows */
+void games_window_present (GtkWindow *window);
+void games_window_present_with_time (GtkWindow *window,
+ guint32 timestamp);
+GtkWindow * games_get_toplevel_window (GtkWidget *widget);
+
+void games_move_to_window_desktop (GtkWindow *window,
+ guint32 timestamp);
+
+/* URL */
+gchar * games_make_absolute_url (const gchar *url);
+
+gchar * games_make_absolute_url_len (const gchar *url,
+ guint len);
+void games_url_show (GtkWidget *parent,
+ const char *url);
+
+/* Misc */
+gint64 games_get_current_action_time (void);
+
+gboolean games_individual_match_string (
+ FolksIndividual *individual,
+ const gchar *text,
+ GPtrArray *words);
+
+void games_launch_program (const gchar *dir,
+ const gchar *name,
+ const gchar *args);
+
+gboolean games_xml_validate (xmlDoc *doc,
+ const gchar *dtd_filename);
+
+G_END_DECLS
+
+#endif /* __GAMES_UI_UTILS_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]