[gnome-chess/chess-telepathy-networking-support-664946-rebase: 15/64] [libgames-contacts] Add view to display folks individuals
- From: Chandni Verma <vchandni src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-chess/chess-telepathy-networking-support-664946-rebase: 15/64] [libgames-contacts] Add view to display folks individuals
- Date: Sun, 23 Dec 2012 03:39:53 +0000 (UTC)
commit 0db172fff39fdbe807339bbe6b6a1f3db015c59c
Author: Chandni Verma <chandniverma2112 gmail com>
Date: Wed Jul 11 07:53:58 2012 +0530
[libgames-contacts] Add view to display folks individuals
Supports:
- Avatars
- Presence
- Individual Groups
- Individual filteration based of gaming capabilities
- Contact search
libgames-contacts/Makefile.am | 15 +-
libgames-contacts/games-cell-renderer-expander.c | 344 ++++++
libgames-contacts/games-cell-renderer-expander.h | 65 ++
libgames-contacts/games-cell-renderer-text.c | 376 +++++++
libgames-contacts/games-cell-renderer-text.h | 59 +
libgames-contacts/games-contact.c | 13 +-
libgames-contacts/games-individual-view.c | 1238 ++++++++++++++++++++++
libgames-contacts/games-individual-view.h | 127 +++
libgames-contacts/games-live-search.c | 704 ++++++++++++
libgames-contacts/games-live-search.h | 81 ++
libgames-contacts/games-ui-utils.c | 65 ++
libgames-contacts/games-ui-utils.h | 7 +-
12 files changed, 3085 insertions(+), 9 deletions(-)
---
diff --git a/libgames-contacts/Makefile.am b/libgames-contacts/Makefile.am
index b2e7788..9b3f6f7 100644
--- a/libgames-contacts/Makefile.am
+++ b/libgames-contacts/Makefile.am
@@ -15,7 +15,12 @@ networking_headers = \
games-individual-store-manager.h \
games-individual-groups.h \
games-ui-utils.h \
- games-contact.h
+ games-contact.h \
+ games-individual-view.h \
+ games-cell-renderer-text.h \
+ games-cell-renderer-expander.h \
+ games-live-search.h
+
networking_sources = \
$(networking_headers) \
@@ -24,7 +29,12 @@ networking_sources = \
games-individual-store-manager.c \
games-individual-groups.c \
games-ui-utils.c \
- games-contact.c
+ games-contact.c \
+ games-individual-view.c \
+ games-cell-renderer-text.c \
+ games-cell-renderer-expander.c \
+ games-live-search.c
+
libgames_contacts_la_SOURCES = \
$(networking_sources)
@@ -39,6 +49,7 @@ libgames_contacts_la_CPPFLAGS = \
-DICON_THEME_DIRECTORY="\"$(datadir)/icons\"" \
-DLOCALE_DIRECTORY="\"$(datadir)/locale\"" \
-DGETTEXT_PACKAGE="\"gnome-chess\"" \
+ -DG_LOG_DOMAIN="\"Games\""
$(AM_CPPFLAGS)
libgames_contacts_la_CFLAGS = \
diff --git a/libgames-contacts/games-cell-renderer-expander.c b/libgames-contacts/games-cell-renderer-expander.c
new file mode 100644
index 0000000..ec96570
--- /dev/null
+++ b/libgames-contacts/games-cell-renderer-expander.c
@@ -0,0 +1,344 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * 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 contacts 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 <gtk/gtk.h>
+
+#include <libgames-contacts/games-ui-utils.h>
+#include "games-cell-renderer-expander.h"
+
+struct _GamesCellRendererExpanderPriv{
+ GtkExpanderStyle expander_style;
+ gint expander_size;
+
+ guint activatable : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_EXPANDER_STYLE,
+ PROP_EXPANDER_SIZE,
+ PROP_ACTIVATABLE
+};
+
+static void games_cell_renderer_expander_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void games_cell_renderer_expander_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void games_cell_renderer_expander_finalize (GObject *object);
+static void games_cell_renderer_expander_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height);
+static void games_cell_renderer_expander_render (GtkCellRenderer *cell,
+ cairo_t *cr,
+ GtkWidget *widget,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+static gboolean games_cell_renderer_expander_activate (GtkCellRenderer *cell,
+ GdkEvent *event,
+ GtkWidget *widget,
+ const gchar *path,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+
+G_DEFINE_TYPE (GamesCellRendererExpander, games_cell_renderer_expander, GTK_TYPE_CELL_RENDERER)
+
+static void
+games_cell_renderer_expander_init (GamesCellRendererExpander *expander)
+{
+ GamesCellRendererExpanderPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (expander,
+ GAMES_TYPE_CELL_RENDERER_EXPANDER, GamesCellRendererExpanderPriv);
+
+ expander->priv = priv;
+ priv->expander_style = GTK_EXPANDER_COLLAPSED;
+ priv->expander_size = 12;
+ priv->activatable = TRUE;
+
+ g_object_set (expander,
+ "xpad", 2,
+ "ypad", 2,
+ "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
+ NULL);
+}
+
+static void
+games_cell_renderer_expander_class_init (GamesCellRendererExpanderClass *klass)
+{
+ GObjectClass *object_class;
+ GtkCellRendererClass *cell_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ cell_class = GTK_CELL_RENDERER_CLASS (klass);
+
+ object_class->finalize = games_cell_renderer_expander_finalize;
+
+ object_class->get_property = games_cell_renderer_expander_get_property;
+ object_class->set_property = games_cell_renderer_expander_set_property;
+
+ cell_class->get_size = games_cell_renderer_expander_get_size;
+ cell_class->render = games_cell_renderer_expander_render;
+ cell_class->activate = games_cell_renderer_expander_activate;
+
+ g_object_class_install_property (object_class,
+ PROP_EXPANDER_STYLE,
+ g_param_spec_enum ("expander-style",
+ "Expander Style",
+ "Style to use when painting the expander",
+ GTK_TYPE_EXPANDER_STYLE,
+ GTK_EXPANDER_COLLAPSED,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_EXPANDER_SIZE,
+ g_param_spec_int ("expander-size",
+ "Expander Size",
+ "The size of the expander",
+ 0,
+ G_MAXINT,
+ 12,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_ACTIVATABLE,
+ g_param_spec_boolean ("activatable",
+ "Activatable",
+ "The expander can be activated",
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (GamesCellRendererExpanderPriv));
+}
+
+static void
+games_cell_renderer_expander_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GamesCellRendererExpander *expander;
+
+ expander = GAMES_CELL_RENDERER_EXPANDER (object);
+
+ switch (param_id) {
+ case PROP_EXPANDER_STYLE:
+ g_value_set_enum (value, expander->priv->expander_style);
+ break;
+
+ case PROP_EXPANDER_SIZE:
+ g_value_set_int (value, expander->priv->expander_size);
+ break;
+
+ case PROP_ACTIVATABLE:
+ g_value_set_boolean (value, expander->priv->activatable);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+games_cell_renderer_expander_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GamesCellRendererExpander *expander;
+
+ expander = GAMES_CELL_RENDERER_EXPANDER (object);
+
+ switch (param_id) {
+ case PROP_EXPANDER_STYLE:
+ expander->priv->expander_style = g_value_get_enum (value);
+ break;
+
+ case PROP_EXPANDER_SIZE:
+ expander->priv->expander_size = g_value_get_int (value);
+ break;
+
+ case PROP_ACTIVATABLE:
+ expander->priv->activatable = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+games_cell_renderer_expander_finalize (GObject *object)
+{
+ (* G_OBJECT_CLASS (games_cell_renderer_expander_parent_class)->finalize) (object);
+}
+
+GtkCellRenderer *
+games_cell_renderer_expander_new (void)
+{
+ return g_object_new (GAMES_TYPE_CELL_RENDERER_EXPANDER, NULL);
+}
+
+static void
+games_cell_renderer_expander_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height)
+{
+ GamesCellRendererExpander *expander;
+ gfloat xalign, yalign;
+ guint xpad, ypad;
+
+ expander = (GamesCellRendererExpander *) cell;
+
+ g_object_get (cell,
+ "xalign", &xalign,
+ "yalign", &yalign,
+ "xpad", &xpad,
+ "ypad", &ypad,
+ NULL);
+
+ if (cell_area) {
+ if (x_offset) {
+ *x_offset = xalign * (cell_area->width - (expander->priv->expander_size + (2 * xpad)));
+ *x_offset = MAX (*x_offset, 0);
+ }
+
+ if (y_offset) {
+ *y_offset = yalign * (cell_area->height - (expander->priv->expander_size + (2 * ypad)));
+ *y_offset = MAX (*y_offset, 0);
+ }
+ } else {
+ if (x_offset)
+ *x_offset = 0;
+
+ if (y_offset)
+ *y_offset = 0;
+ }
+
+ if (width)
+ *width = xpad * 2 + expander->priv->expander_size;
+
+ if (height)
+ *height = ypad * 2 + expander->priv->expander_size;
+}
+
+static void
+games_cell_renderer_expander_render (GtkCellRenderer *cell,
+ cairo_t *cr,
+ GtkWidget *widget,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ GamesCellRendererExpander *expander;
+ gint x_offset, y_offset;
+ guint xpad, ypad;
+ GtkStyleContext *style;
+ GtkStateFlags state;
+
+ expander = (GamesCellRendererExpander *) cell;
+
+ games_cell_renderer_expander_get_size (cell, widget,
+ (GdkRectangle *) cell_area,
+ &x_offset, &y_offset,
+ NULL, NULL);
+
+ g_object_get (cell,
+ "xpad", &xpad,
+ "ypad", &ypad,
+ NULL);
+
+ style = gtk_widget_get_style_context (widget);
+
+ gtk_style_context_save (style);
+ gtk_style_context_add_class (style, GTK_STYLE_CLASS_EXPANDER);
+
+ state = gtk_cell_renderer_get_state (cell, widget, flags);
+
+ if (expander->priv->expander_style == GTK_EXPANDER_COLLAPSED)
+ state |= GTK_STATE_FLAG_NORMAL;
+ else
+ state |= GTK_STATE_FLAG_ACTIVE;
+
+ gtk_style_context_set_state (style, state);
+
+ gtk_render_expander (style,
+ cr,
+ cell_area->x + x_offset + xpad,
+ cell_area->y + y_offset + ypad,
+ expander->priv->expander_size,
+ expander->priv->expander_size);
+
+ gtk_style_context_restore (style);
+}
+
+static gboolean
+games_cell_renderer_expander_activate (GtkCellRenderer *cell,
+ GdkEvent *event,
+ GtkWidget *widget,
+ const gchar *path_string,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ GamesCellRendererExpander *expander = GAMES_CELL_RENDERER_EXPANDER (cell);
+ GtkTreePath *path;
+
+ if (!GTK_IS_TREE_VIEW (widget) || !expander->priv->activatable)
+ return FALSE;
+
+ path = gtk_tree_path_new_from_string (path_string);
+
+ if (gtk_tree_path_get_depth (path) > 1) {
+ gtk_tree_path_free (path);
+ return TRUE;
+ }
+
+ if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
+ gtk_tree_view_collapse_row (GTK_TREE_VIEW (widget), path);
+ } else {
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (widget), path, FALSE);
+ }
+
+ gtk_tree_path_free (path);
+
+ return TRUE;
+}
diff --git a/libgames-contacts/games-cell-renderer-expander.h b/libgames-contacts/games-cell-renderer-expander.h
new file mode 100644
index 0000000..4a86722
--- /dev/null
+++ b/libgames-contacts/games-cell-renderer-expander.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * 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 contacts in Empathy, a chat
+ * application based on Telepathy (https://live.gnome.org/Empathy)
+ * Copyright (C) 2006-2007 Imendio AB
+ */
+
+#ifndef __GAMES_CELL_RENDERER_EXPANDER_H__
+#define __GAMES_CELL_RENDERER_EXPANDER_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GAMES_TYPE_CELL_RENDERER_EXPANDER (games_cell_renderer_expander_get_type ())
+#define GAMES_CELL_RENDERER_EXPANDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAMES_TYPE_CELL_RENDERER_EXPANDER, GamesCellRendererExpander))
+#define GAMES_CELL_RENDERER_EXPANDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAMES_TYPE_CELL_RENDERER_EXPANDER, GamesCellRendererExpanderClass))
+#define GAMES_IS_CELL_RENDERER_EXPANDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAMES_TYPE_CELL_RENDERER_EXPANDER))
+#define GAMES_IS_CELL_RENDERER_EXPANDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAMES_TYPE_CELL_RENDERER_EXPANDER))
+#define GAMES_CELL_RENDERER_EXPANDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GAMES_TYPE_CELL_RENDERER_EXPANDER, GamesCellRendererExpanderClass))
+
+typedef struct _GamesCellRendererExpander GamesCellRendererExpander;
+typedef struct _GamesCellRendererExpanderClass GamesCellRendererExpanderClass;
+typedef struct _GamesCellRendererExpanderPriv GamesCellRendererExpanderPriv;
+
+struct _GamesCellRendererExpander {
+ GtkCellRenderer parent;
+ GamesCellRendererExpanderPriv *priv;
+};
+
+struct _GamesCellRendererExpanderClass {
+ GtkCellRendererClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+GType games_cell_renderer_expander_get_type (void) G_GNUC_CONST;
+GtkCellRenderer *games_cell_renderer_expander_new (void);
+
+G_END_DECLS
+
+#endif /* __GAMES_CELL_RENDERER_EXPANDER_H__ */
diff --git a/libgames-contacts/games-cell-renderer-text.c b/libgames-contacts/games-cell-renderer-text.c
new file mode 100644
index 0000000..4308e14
--- /dev/null
+++ b/libgames-contacts/games-cell-renderer-text.c
@@ -0,0 +1,376 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * 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 contacts in Empathy, a chat
+ * application based on Telepathy (https://live.gnome.org/Empathy)
+ * Copyright (C) 2004-2007 Imendio ABdd
+ * Copyright (C) 2010 Collabora Ltd.
+ */
+
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgames-contacts/games-ui-utils.h>
+#include "games-cell-renderer-text.h"
+
+struct _GamesCellRendererTextPriv{
+ gchar *name;
+ TpConnectionPresenceType presence_type;
+ gchar *status;
+ gboolean is_group;
+
+ gboolean is_valid;
+ gboolean is_selected;
+
+ gboolean compact;
+};
+
+static void cell_renderer_text_dispose (GObject *object);
+static void cell_renderer_text_finalize (GObject *object);
+static void cell_renderer_text_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void cell_renderer_text_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void cell_renderer_text_render (GtkCellRenderer *cell,
+ cairo_t *cr,
+ GtkWidget *widget,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+static void cell_renderer_text_update_text (GamesCellRendererText *cell,
+ GtkWidget *widget,
+ gboolean selected);
+
+/* Properties */
+enum {
+ PROP_0,
+ PROP_NAME,
+ PROP_PRESENCE_TYPE,
+ PROP_STATUS,
+ PROP_IS_GROUP,
+ PROP_COMPACT,
+};
+
+G_DEFINE_TYPE (GamesCellRendererText, games_cell_renderer_text, GTK_TYPE_CELL_RENDERER_TEXT);
+
+static void
+cell_renderer_text_get_preferred_height_for_width (GtkCellRenderer *renderer,
+ GtkWidget *widget,
+ gint width,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GamesCellRendererText *self = GAMES_CELL_RENDERER_TEXT (renderer);
+
+ /* Only update if not already valid so we get the right size. */
+ cell_renderer_text_update_text (self, widget, self->priv->is_selected);
+
+ GTK_CELL_RENDERER_CLASS (games_cell_renderer_text_parent_class)->
+ get_preferred_height_for_width (renderer, widget, width,
+ minimum_size, natural_size);
+}
+
+
+static void
+games_cell_renderer_text_class_init (GamesCellRendererTextClass *klass)
+{
+ GObjectClass *object_class;
+ GtkCellRendererClass *cell_class;
+ GParamSpec *spec;
+
+ object_class = G_OBJECT_CLASS (klass);
+ cell_class = GTK_CELL_RENDERER_CLASS (klass);
+
+ object_class->dispose = cell_renderer_text_dispose;
+ object_class->finalize = cell_renderer_text_finalize;
+
+ object_class->get_property = cell_renderer_text_get_property;
+ object_class->set_property = cell_renderer_text_set_property;
+
+ cell_class->get_preferred_height_for_width = cell_renderer_text_get_preferred_height_for_width;
+ cell_class->render = cell_renderer_text_render;
+
+ spec = g_param_spec_string ("name", "Name", "Contact name", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_NAME, spec);
+
+ spec = g_param_spec_uint ("presence-type", "TpConnectionPresenceType",
+ "The contact's presence type",
+ 0, G_MAXUINT, /* Telepathy enum, can be extended */
+ TP_CONNECTION_PRESENCE_TYPE_UNKNOWN,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PRESENCE_TYPE,
+ spec);
+
+ spec = g_param_spec_string ("status", "Status message",
+ "Contact's custom status message", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STATUS, spec);
+
+ spec = g_param_spec_boolean ("is-group", "Is group",
+ "Whether this cell is a group", FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_IS_GROUP, spec);
+
+ spec = g_param_spec_boolean ("compact", "Compact",
+ "TRUE to show the status alongside the contact name;"
+ "FALSE to show it on its own line",
+ FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_COMPACT, spec);
+
+ g_type_class_add_private (object_class, sizeof (GamesCellRendererTextPriv));
+}
+
+static void
+games_cell_renderer_text_init (GamesCellRendererText *cell)
+{
+ GamesCellRendererTextPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (cell,
+ GAMES_TYPE_CELL_RENDERER_TEXT, GamesCellRendererTextPriv);
+
+ cell->priv = priv;
+ g_object_set (cell,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ NULL);
+
+ priv->name = g_strdup ("");
+ priv->status = g_strdup ("");
+ priv->compact = FALSE;
+}
+
+static void
+cell_renderer_text_dispose (GObject *object)
+{
+ GamesCellRendererText *cell = GAMES_CELL_RENDERER_TEXT (object);
+
+ g_free (cell->priv->name);
+ g_free (cell->priv->status);
+
+ (G_OBJECT_CLASS (games_cell_renderer_text_parent_class)->dispose) (object);
+}
+
+static void
+cell_renderer_text_finalize (GObject *object)
+{
+ (G_OBJECT_CLASS (games_cell_renderer_text_parent_class)->finalize) (object);
+}
+
+static void
+cell_renderer_text_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GamesCellRendererText *cell;
+
+ cell = GAMES_CELL_RENDERER_TEXT (object);
+
+ switch (param_id) {
+ case PROP_NAME:
+ g_value_set_string (value, cell->priv->name);
+ break;
+ case PROP_PRESENCE_TYPE:
+ g_value_set_uint (value, cell->priv->presence_type);
+ break;
+ case PROP_STATUS:
+ g_value_set_string (value, cell->priv->status);
+ break;
+ case PROP_IS_GROUP:
+ g_value_set_boolean (value, cell->priv->is_group);
+ break;
+ case PROP_COMPACT:
+ g_value_set_boolean (value, cell->priv->compact);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+cell_renderer_text_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GamesCellRendererText *cell;
+ const gchar *str;
+
+ cell = GAMES_CELL_RENDERER_TEXT (object);
+
+ switch (param_id) {
+ case PROP_NAME:
+ g_free (cell->priv->name);
+ str = g_value_get_string (value);
+ cell->priv->name = g_strdup (str ? str : "");
+ g_strdelimit (cell->priv->name, "\n\r\t", ' ');
+ cell->priv->is_valid = FALSE;
+ break;
+ case PROP_PRESENCE_TYPE:
+ cell->priv->presence_type = g_value_get_uint (value);
+ cell->priv->is_valid = FALSE;
+ break;
+ case PROP_STATUS:
+ g_free (cell->priv->status);
+ str = g_value_get_string (value);
+ cell->priv->status = g_strdup (str ? str : "");
+ g_strdelimit (cell->priv->status, "\n\r\t", ' ');
+ cell->priv->is_valid = FALSE;
+ break;
+ case PROP_IS_GROUP:
+ cell->priv->is_group = g_value_get_boolean (value);
+ cell->priv->is_valid = FALSE;
+ break;
+ case PROP_COMPACT:
+ cell->priv->compact = g_value_get_boolean (value);
+ cell->priv->is_valid = FALSE;
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+cell_renderer_text_render (GtkCellRenderer *cell,
+ cairo_t *cr,
+ GtkWidget *widget,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ GamesCellRendererText *celltext;
+
+ celltext = GAMES_CELL_RENDERER_TEXT (cell);
+
+ cell_renderer_text_update_text (celltext,
+ widget,
+ (flags & GTK_CELL_RENDERER_SELECTED));
+
+ (GTK_CELL_RENDERER_CLASS (games_cell_renderer_text_parent_class)->render) (
+ cell, cr,
+ widget,
+ background_area,
+ cell_area,
+ flags);
+}
+
+static void
+cell_renderer_text_update_text (GamesCellRendererText *cell,
+ GtkWidget *widget,
+ gboolean selected)
+{
+ GamesCellRendererText *self = GAMES_CELL_RENDERER_TEXT (cell);
+ const PangoFontDescription *font_desc;
+ PangoAttrList *attr_list;
+ PangoAttribute *attr_color = NULL, *attr_size;
+ GtkStyleContext *style;
+ gchar *str;
+ gint font_size;
+
+ if (self->priv->is_valid && self->priv->is_selected == selected) {
+ return;
+ }
+
+ if (self->priv->is_group) {
+ g_object_set (cell,
+ "visible", TRUE,
+ "weight", PANGO_WEIGHT_BOLD,
+ "text", self->priv->name,
+ "attributes", NULL,
+ "xpad", 1,
+ "ypad", 1,
+ NULL);
+
+ self->priv->is_selected = selected;
+ self->priv->is_valid = TRUE;
+ return;
+ }
+
+ style = gtk_widget_get_style_context (widget);
+
+ attr_list = pango_attr_list_new ();
+
+ font_desc = gtk_style_context_get_font (style, GTK_STATE_FLAG_NORMAL);
+ font_size = pango_font_description_get_size (font_desc);
+ attr_size = pango_attr_size_new (font_size / 1.2);
+ attr_size->start_index = strlen (self->priv->name) + 1;
+ attr_size->end_index = -1;
+ pango_attr_list_insert (attr_list, attr_size);
+
+ if (!selected) {
+ GdkRGBA color;
+
+ gtk_style_context_get_color (style, 0, &color);
+
+ attr_color = pango_attr_foreground_new (color.red * 0xffff,
+ color.green * 0xffff,
+ color.blue * 0xffff);
+ attr_color->start_index = attr_size->start_index;
+ attr_color->end_index = -1;
+ pango_attr_list_insert (attr_list, attr_color);
+ }
+
+ if (self->priv->compact) {
+ if (STR_EMPTY (self->priv->status)) {
+ str = g_strdup (self->priv->name);
+ } else {
+ str = g_strdup_printf ("%s %s", self->priv->name, self->priv->status);
+ }
+ } else {
+ const gchar *status = self->priv->status;
+
+ if (STR_EMPTY (self->priv->status)) {
+ status = games_presence_get_default_message (self->priv->presence_type);
+ }
+
+ if (status == NULL)
+ str = g_strdup (self->priv->name);
+ else
+ str = g_strdup_printf ("%s\n%s", self->priv->name, status);
+ }
+
+ g_object_set (cell,
+ "visible", TRUE,
+ "weight", PANGO_WEIGHT_NORMAL,
+ "text", str,
+ "attributes", attr_list,
+ "xpad", 0,
+ "ypad", 1,
+ NULL);
+
+ g_free (str);
+ pango_attr_list_unref (attr_list);
+
+ self->priv->is_selected = selected;
+ self->priv->is_valid = TRUE;
+}
+
+GtkCellRenderer *
+games_cell_renderer_text_new (void)
+{
+ return g_object_new (GAMES_TYPE_CELL_RENDERER_TEXT, NULL);
+}
diff --git a/libgames-contacts/games-cell-renderer-text.h b/libgames-contacts/games-cell-renderer-text.h
new file mode 100644
index 0000000..b71ff29
--- /dev/null
+++ b/libgames-contacts/games-cell-renderer-text.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * 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 contacts in Empathy, a chat
+ * application based on Telepathy (https://live.gnome.org/Empathy)
+ * Copyright (C) 2004-2007 Imendio AB
+ */
+
+#ifndef __GAMES_CELL_RENDERER_TEXT_H__
+#define __GAMES_CELL_RENDERER_TEXT_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GAMES_TYPE_CELL_RENDERER_TEXT (games_cell_renderer_text_get_type ())
+#define GAMES_CELL_RENDERER_TEXT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GAMES_TYPE_CELL_RENDERER_TEXT, GamesCellRendererText))
+#define GAMES_CELL_RENDERER_TEXT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GAMES_TYPE_CELL_RENDERER_TEXT, GamesCellRendererTextClass))
+#define GAMES_IS_CELL_RENDERER_TEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GAMES_TYPE_CELL_RENDERER_TEXT))
+#define GAMES_IS_CELL_RENDERER_TEXT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GAMES_TYPE_CELL_RENDERER_TEXT))
+#define GAMES_CELL_RENDERER_TEXT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GAMES_TYPE_CELL_RENDERER_TEXT, GamesCellRendererTextClass))
+
+typedef struct _GamesCellRendererText GamesCellRendererText;
+typedef struct _GamesCellRendererTextClass GamesCellRendererTextClass;
+typedef struct _GamesCellRendererTextPriv GamesCellRendererTextPriv;
+
+struct _GamesCellRendererText {
+ GtkCellRendererText parent;
+ GamesCellRendererTextPriv *priv;
+};
+
+struct _GamesCellRendererTextClass {
+ GtkCellRendererTextClass parent_class;
+};
+
+GType games_cell_renderer_text_get_type (void) G_GNUC_CONST;
+GtkCellRenderer * games_cell_renderer_text_new (void);
+
+G_END_DECLS
+
+#endif /* __GAMES_CELL_RENDERER_TEXT_H__ */
diff --git a/libgames-contacts/games-contact.c b/libgames-contacts/games-contact.c
index d7b7ad3..9efcae6 100644
--- a/libgames-contacts/games-contact.c
+++ b/libgames-contacts/games-contact.c
@@ -1104,13 +1104,17 @@ games_contact_equal (gconstpointer contact1,
}
static GamesCapabilities
-tp_caps_to_capabilities (TpCapabilities *caps)
+tp_caps_to_capabilities (GamesContact *self,
+ TpCapabilities *caps)
{
GamesCapabilities capabilities = 0;
if (tp_capabilities_supports_dbus_tubes (caps, TP_HANDLE_TYPE_CONTACT,
- "org.gnome.glchess"))
+ "org.gnome.glchess")) {
+ g_debug ("Contact %s(%s) has glchess playing capabilities",
+ games_contact_get_alias (self), games_contact_get_id (self));
capabilities |= GAMES_CAPABILITIES_TUBE_GLCHESS;
+ }
return capabilities;
}
@@ -1121,10 +1125,13 @@ set_capabilities_from_tp_caps (GamesContact *self,
{
GamesCapabilities capabilities;
+ g_debug ("Got capabilities for contact %s(%s)",
+ games_contact_get_alias (self), games_contact_get_id (self));
+
if (caps == NULL)
return;
- capabilities = tp_caps_to_capabilities (caps);
+ capabilities = tp_caps_to_capabilities (self, caps);
games_contact_set_capabilities (self, capabilities);
}
diff --git a/libgames-contacts/games-individual-view.c b/libgames-contacts/games-individual-view.c
new file mode 100644
index 0000000..36a536b
--- /dev/null
+++ b/libgames-contacts/games-individual-view.c
@@ -0,0 +1,1238 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * 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
+ *
+ * Author: Chandni Verma <chandniverma2112 gmail com>
+ */
+
+/* Much of code here is a simplified and adapted version of the way
+ * contacts are displayed in Empathy, a chat application using Folks
+ * (https://live.gnome.org/Empathy)
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include <telepathy-glib/account-manager.h>
+#include <telepathy-glib/util.h>
+
+#include <folks/folks.h>
+#include <folks/folks-telepathy.h>
+
+#include <libgames-contacts/games-individual-manager.h>
+#include <libgames-contacts/games-individual-groups.h>
+#include <libgames-contacts/games-ui-utils.h>
+
+#include "games-individual-view.h"
+#include "games-individual-store.h"
+#include "games-cell-renderer-expander.h"
+#include "games-cell-renderer-text.h"
+#include "games-ui-utils.h"
+#include "games-enum-types.h"
+
+/* Active users are those which have recently changed state
+ * (e.g. online, offline or from normal to a busy state).
+ */
+
+struct _GamesIndividualViewPriv
+{
+ GamesIndividualStore *store;
+ GamesIndividualViewFeatureFlags view_features;
+
+ gboolean show_untrusted;
+ gboolean show_uninteresting;
+
+ GtkTreeModelFilter *filter;
+ GtkWidget *search_widget;
+
+ guint expand_groups_idle_handler;
+ /* owned string (group name) -> bool (whether to expand/contract) */
+ GHashTable *expand_groups;
+
+ GtkTreeModelFilterVisibleFunc custom_filter;
+ gpointer custom_filter_data;
+
+ GtkCellRenderer *text_renderer;
+};
+
+enum
+{
+ PROP_0,
+ PROP_STORE,
+ PROP_VIEW_FEATURES,
+ PROP_SHOW_UNTRUSTED,
+ PROP_SHOW_UNINTERESTING,
+};
+
+G_DEFINE_TYPE (GamesIndividualView, games_individual_view,
+ GTK_TYPE_TREE_VIEW);
+
+
+static void
+individual_view_cell_set_background (GamesIndividualView *view,
+ GtkCellRenderer *cell,
+ gboolean is_group,
+ gboolean is_active)
+{
+ if (!is_group && is_active)
+ {
+ GtkStyleContext *style;
+ GdkRGBA color;
+
+ style = gtk_widget_get_style_context (GTK_WIDGET (view));
+
+ gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
+ &color);
+
+ /* Here we take the current theme colour and add it to
+ * the colour for white and average the two. This
+ * gives a colour which is inline with the theme but
+ * slightly whiter.
+ */
+ const GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 };
+
+ color.red = (color.red + white.red) / 2;
+ color.green = (color.green + white.green) / 2;
+ color.blue = (color.blue + white.blue) / 2;
+
+ g_object_set (cell, "cell-background-rgba", &color, NULL);
+ }
+ else
+ g_object_set (cell, "cell-background-rgba", NULL, NULL);
+}
+
+static void
+individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GamesIndividualView *view)
+{
+ GdkPixbuf *pixbuf;
+ gboolean is_group;
+ gboolean is_active;
+
+ gtk_tree_model_get (model, iter,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+ GAMES_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
+ GAMES_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
+
+ g_object_set (cell,
+ "visible", !is_group,
+ "pixbuf", pixbuf,
+ NULL);
+
+ tp_clear_object (&pixbuf);
+
+ individual_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static void
+individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GamesIndividualView *view)
+{
+ GdkPixbuf *pixbuf = NULL;
+ gboolean is_group;
+ gchar *name;
+
+ gtk_tree_model_get (model, iter,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+ GAMES_INDIVIDUAL_STORE_COL_NAME, &name, -1);
+
+ if (!is_group)
+ goto out;
+
+ if (!tp_strdiff (name, GAMES_INDIVIDUAL_STORE_FAVORITE))
+ {
+ pixbuf = games_pixbuf_from_icon_name ("emblem-favorite",
+ GTK_ICON_SIZE_MENU);
+ }
+ else if (!tp_strdiff (name, GAMES_INDIVIDUAL_STORE_PEOPLE_NEARBY))
+ {
+ pixbuf = games_pixbuf_from_icon_name ("im-local-xmpp",
+ GTK_ICON_SIZE_MENU);
+ }
+
+out:
+ g_object_set (cell,
+ "visible", pixbuf != NULL,
+ "pixbuf", pixbuf,
+ NULL);
+
+ tp_clear_object (&pixbuf);
+
+ g_free (name);
+}
+
+static void
+individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GamesIndividualView *view)
+{
+ GdkPixbuf *pixbuf;
+ gboolean show_avatar;
+ gboolean is_group;
+ gboolean is_active;
+
+ gtk_tree_model_get (model, iter,
+ GAMES_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
+ GAMES_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+ GAMES_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
+
+ g_object_set (cell,
+ "visible", !is_group && show_avatar,
+ "pixbuf", pixbuf,
+ NULL);
+
+ tp_clear_object (&pixbuf);
+
+ individual_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static void
+individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GamesIndividualView *view)
+{
+ gboolean is_group;
+ gboolean is_active;
+
+ gtk_tree_model_get (model, iter,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+ GAMES_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
+
+ individual_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static void
+individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GamesIndividualView *view)
+{
+ gboolean is_group;
+ gboolean is_active;
+
+ gtk_tree_model_get (model, iter,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+ GAMES_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
+
+ if (gtk_tree_model_iter_has_child (model, iter))
+ {
+ GtkTreePath *path;
+ gboolean row_expanded;
+
+ path = gtk_tree_model_get_path (model, iter);
+ row_expanded =
+ gtk_tree_view_row_expanded (GTK_TREE_VIEW
+ (gtk_tree_view_column_get_tree_view (column)), path);
+ gtk_tree_path_free (path);
+
+ g_object_set (cell,
+ "visible", TRUE,
+ "expander-style",
+ row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
+ NULL);
+ }
+ else
+ g_object_set (cell, "visible", FALSE, NULL);
+
+ individual_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static void
+individual_view_row_expand_or_collapse_cb (GamesIndividualView *view,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ GtkTreeModel *model;
+ gchar *name;
+ gboolean expanded;
+
+ if (!(view->priv->view_features & GAMES_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
+ return;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+
+ gtk_tree_model_get (model, iter,
+ GAMES_INDIVIDUAL_STORE_COL_NAME, &name, -1);
+
+ expanded = GPOINTER_TO_INT (user_data);
+ games_individual_group_set_expanded (name, expanded);
+
+ g_free (name);
+}
+
+static gboolean
+individual_view_start_search_cb (GamesIndividualView *view,
+ gpointer data)
+{
+ if (view->priv->search_widget == NULL)
+ return FALSE;
+
+ games_individual_view_start_search (view);
+
+ return TRUE;
+}
+
+static void
+individual_view_search_text_notify_cb (GamesLiveSearch *search,
+ GParamSpec *pspec,
+ GamesIndividualView *view)
+{
+ GtkTreePath *path;
+ GtkTreeViewColumn *focus_column;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean set_cursor = FALSE;
+
+ gtk_tree_model_filter_refilter (view->priv->filter);
+
+ /* Set cursor on the first contact. If it is already set on a group,
+ * set it on its first child contact. Note that first child of a group
+ * is its separator, that's why we actually set to the 2nd
+ */
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+ gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
+
+ if (path == NULL)
+ {
+ path = gtk_tree_path_new_from_string ("0:1");
+ set_cursor = TRUE;
+ }
+ else if (gtk_tree_path_get_depth (path) < 2)
+ {
+ gboolean is_group;
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+ -1);
+
+ if (is_group)
+ {
+ gtk_tree_path_down (path);
+ gtk_tree_path_next (path);
+ set_cursor = TRUE;
+ }
+ }
+
+ if (set_cursor)
+ {
+ /* Make sure the path is valid. */
+ if (gtk_tree_model_get_iter (model, &iter, path))
+ {
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
+ FALSE);
+ }
+ }
+
+ gtk_tree_path_free (path);
+}
+
+static void
+individual_view_search_activate_cb (GtkWidget *search,
+ GamesIndividualView *view)
+{
+ GtkTreePath *path;
+ GtkTreeViewColumn *focus_column;
+
+ gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
+ if (path != NULL)
+ {
+ gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
+ gtk_tree_path_free (path);
+
+ gtk_widget_hide (search);
+ }
+}
+
+static gboolean
+individual_view_search_key_navigation_cb (GtkWidget *search,
+ GdkEvent *event,
+ GamesIndividualView *view)
+{
+ GdkEvent *new_event;
+ gboolean ret = FALSE;
+
+ new_event = gdk_event_copy (event);
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+ ret = gtk_widget_event (GTK_WIDGET (view), new_event);
+ gtk_widget_grab_focus (search);
+
+ gdk_event_free (new_event);
+
+ return ret;
+}
+
+static void
+individual_view_search_hide_cb (GamesLiveSearch *search,
+ GamesIndividualView *view)
+{
+ GtkTreeModel *model;
+ GtkTreePath *cursor_path;
+ GtkTreeIter iter;
+ gboolean valid = FALSE;
+
+ /* block expand or collapse handlers, they would write the
+ * expand or collapsed setting to file otherwise */
+ g_signal_handlers_block_by_func (view,
+ individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
+ g_signal_handlers_block_by_func (view,
+ individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
+
+ /* restore which groups are expanded and which are not */
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+ for (valid = gtk_tree_model_get_iter_first (model, &iter);
+ valid; valid = gtk_tree_model_iter_next (model, &iter))
+ {
+ gboolean is_group;
+ gchar *name = NULL;
+ GtkTreePath *path;
+
+ gtk_tree_model_get (model, &iter,
+ GAMES_INDIVIDUAL_STORE_COL_NAME, &name,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+ -1);
+
+ if (!is_group)
+ {
+ g_free (name);
+ continue;
+ }
+
+ path = gtk_tree_model_get_path (model, &iter);
+ if ((view->priv->view_features &
+ GAMES_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
+ games_individual_group_get_expanded (name))
+ {
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
+ }
+ else
+ {
+ gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
+ }
+
+ gtk_tree_path_free (path);
+ g_free (name);
+ }
+
+ /* unblock expand or collapse handlers */
+ g_signal_handlers_unblock_by_func (view,
+ individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
+ g_signal_handlers_unblock_by_func (view,
+ individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
+
+ /* keep the selected contact visible */
+ gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
+
+ if (cursor_path != NULL)
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
+ FALSE, 0, 0);
+
+ gtk_tree_path_free (cursor_path);
+}
+
+static void
+individual_view_search_show_cb (GamesLiveSearch *search,
+ GamesIndividualView *view)
+{
+ /* block expand or collapse handlers during expand all, they would
+ * write the expand or collapsed setting to file otherwise */
+ g_signal_handlers_block_by_func (view,
+ individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
+
+ gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
+
+ g_signal_handlers_unblock_by_func (view,
+ individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
+}
+
+static gboolean
+expand_idle_foreach_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GamesIndividualView *self)
+{
+ gboolean is_group;
+ gpointer should_expand;
+ gchar *name;
+
+ /* We only want groups */
+ if (gtk_tree_path_get_depth (path) > 1)
+ return FALSE;
+
+ 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);
+ return FALSE;
+ }
+
+ if (g_hash_table_lookup_extended (self->priv->expand_groups, name, NULL,
+ &should_expand))
+ {
+ if (GPOINTER_TO_INT (should_expand))
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
+ else
+ gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
+
+ g_hash_table_remove (self->priv->expand_groups, name);
+ }
+
+ g_free (name);
+
+ return FALSE;
+}
+
+static gboolean
+individual_view_expand_idle_cb (GamesIndividualView *self)
+{
+ g_signal_handlers_block_by_func (self,
+ individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
+ g_signal_handlers_block_by_func (self,
+ individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
+
+ /* The store/filter could've been removed while we were in the idle queue */
+ if (self->priv->filter != NULL)
+ {
+ gtk_tree_model_foreach (GTK_TREE_MODEL (self->priv->filter),
+ (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
+ }
+
+ g_signal_handlers_unblock_by_func (self,
+ individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
+ g_signal_handlers_unblock_by_func (self,
+ individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
+
+ /* Empty the table of groups to expand/contract, since it may contain groups
+ * which no longer exist in the tree view. This can happen after going
+ * offline, for example. */
+ g_hash_table_remove_all (self->priv->expand_groups);
+ self->priv->expand_groups_idle_handler = 0;
+ g_object_unref (self);
+
+ return FALSE;
+}
+
+static void
+individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GamesIndividualView *view)
+{
+ gboolean should_expand, is_group = FALSE;
+ gchar *name = NULL;
+ gpointer will_expand;
+
+ gtk_tree_model_get (model, iter,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+ GAMES_INDIVIDUAL_STORE_COL_NAME, &name,
+ -1);
+
+ if (!is_group || STR_EMPTY (name))
+ {
+ g_free (name);
+ return;
+ }
+
+ should_expand = (view->priv->view_features &
+ GAMES_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
+ (view->priv->search_widget != NULL &&
+ gtk_widget_get_visible (view->priv->search_widget)) ||
+ games_individual_group_get_expanded (name);
+
+ /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
+ * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
+ * a hash table, and expand or contract them as appropriate all at once in
+ * an idle handler which iterates over all the group rows. */
+ if (!g_hash_table_lookup_extended (view->priv->expand_groups, name, NULL,
+ &will_expand) ||
+ GPOINTER_TO_INT (will_expand) != should_expand)
+ {
+ g_hash_table_insert (view->priv->expand_groups, g_strdup (name),
+ GINT_TO_POINTER (should_expand));
+
+ if (view->priv->expand_groups_idle_handler == 0)
+ {
+ view->priv->expand_groups_idle_handler =
+ g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
+ g_object_ref (view));
+ }
+ }
+
+ g_free (name);
+}
+
+static gboolean
+individual_view_is_visible_individual (GamesIndividualView *self,
+ FolksIndividual *individual,
+ gboolean is_online,
+ gboolean is_searching,
+ const gchar *group,
+ gboolean is_fake_group,
+ GamesCapabilities individual_caps,
+ GamesActionType interest)
+{
+ GamesLiveSearch *live = GAMES_LIVE_SEARCH (self->priv->search_widget);
+ gboolean is_favorite;
+
+ /* We're only giving the visibility wrt filtering here, not things like
+ * presence. */
+ if (!self->priv->show_untrusted &&
+ folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
+ {
+ return FALSE;
+ }
+
+ if (!self->priv->show_uninteresting)
+ {
+ /* Hide all uninteresting individuals */
+ switch (interest)
+ {
+ case GAMES_ACTION_CHAT:
+ /* All individuals in individuals contain TpContacts and hence
+ * support chats. Proceed to calculate visibility. */
+ break;
+
+ case GAMES_ACTION_PLAY_GLCHESS:
+ if ((individual_caps & GAMES_ACTION_PLAY_GLCHESS) == 0)
+ return FALSE;
+ /*else proceed */
+ break;
+
+ default:
+ return FALSE;
+ }
+ }
+
+ is_favorite = folks_favourite_details_get_is_favourite (
+ FOLKS_FAVOURITE_DETAILS (individual));
+ if (!is_searching) {
+ if (is_favorite && is_fake_group &&
+ !tp_strdiff (group, GAMES_INDIVIDUAL_STORE_FAVORITE))
+ /* Always display favorite contacts in the favorite group */
+ return TRUE;
+
+ return (is_online);
+ }
+
+ return games_individual_match_string (individual,
+ games_live_search_get_text (live),
+ games_live_search_get_words (live));
+}
+
+static gchar *
+get_group (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gboolean *is_fake)
+{
+ GtkTreeIter parent_iter;
+ gchar *name = NULL;
+
+ *is_fake = FALSE;
+
+ if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
+ return NULL;
+
+ gtk_tree_model_get (model, &parent_iter,
+ GAMES_INDIVIDUAL_STORE_COL_NAME, &name,
+ GAMES_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
+ -1);
+
+ return name;
+}
+
+gboolean
+individual_view_filter_default (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer user_data,
+ GamesActionType interest)
+{
+ GamesIndividualView *self = GAMES_INDIVIDUAL_VIEW (user_data);
+ FolksIndividual *individual = NULL;
+ gboolean is_group, is_separator, valid;
+ GtkTreeIter child_iter;
+ gboolean visible, is_online;
+ gboolean is_searching = TRUE;
+ GamesCapabilities individual_caps;
+
+ if (self->priv->search_widget == NULL ||
+ !gtk_widget_get_visible (self->priv->search_widget))
+ is_searching = FALSE;
+
+ gtk_tree_model_get (model, iter,
+ GAMES_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+ GAMES_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
+ GAMES_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL_CAPABILITIES, &individual_caps,
+ -1);
+
+ if (individual != NULL)
+ {
+ gchar *group;
+ gboolean is_fake_group;
+
+ group = get_group (model, iter, &is_fake_group);
+
+ visible = individual_view_is_visible_individual (self, individual,
+ is_online, is_searching, group, is_fake_group, individual_caps,
+ interest);
+
+ g_object_unref (individual);
+ g_free (group);
+
+ return visible;
+ }
+
+ if (is_separator)
+ return TRUE;
+
+ /* Not a contact, not a separator, must be a group */
+ g_return_val_if_fail (is_group, FALSE);
+
+ /* only show groups which are not empty */
+ for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
+ valid; valid = gtk_tree_model_iter_next (model, &child_iter))
+ {
+ gchar *group;
+ gboolean is_fake_group;
+
+ gtk_tree_model_get (model, &child_iter,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
+ GAMES_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL_CAPABILITIES,
+ &individual_caps,
+ -1);
+
+ if (individual == NULL)
+ continue;
+
+ group = get_group (model, &child_iter, &is_fake_group);
+
+ visible = individual_view_is_visible_individual (self, individual,
+ is_online, is_searching, group, is_fake_group, individual_caps,
+ interest);
+
+ g_object_unref (individual);
+ g_free (group);
+
+ /* show group if it has at least one visible contact in it */
+ if (visible)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+individual_view_filter_visible_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ GamesIndividualView *self = GAMES_INDIVIDUAL_VIEW (user_data);
+
+ if (self->priv->custom_filter != NULL)
+ return self->priv->custom_filter (model, iter,
+ self->priv->custom_filter_data);
+ else
+ return individual_view_filter_default (model, iter, user_data,
+ GAMES_ACTION_CHAT);
+}
+
+static void
+individual_view_constructed (GObject *object)
+{
+ GamesIndividualView *view = GAMES_INDIVIDUAL_VIEW (object);
+ GtkCellRenderer *cell;
+ GtkTreeViewColumn *col;
+
+ /* Setup view */
+ g_object_set (view,
+ "headers-visible", FALSE,
+ "show-expanders", FALSE,
+ NULL);
+
+ col = gtk_tree_view_column_new ();
+
+ /* State */
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (col, cell, FALSE);
+ gtk_tree_view_column_set_cell_data_func (col, cell,
+ (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
+ view, NULL);
+
+ g_object_set (cell,
+ "xpad", 5,
+ "ypad", 1,
+ "visible", FALSE,
+ NULL);
+
+ /* Group icon */
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (col, cell, FALSE);
+ gtk_tree_view_column_set_cell_data_func (col, cell,
+ (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
+ view, NULL);
+
+ g_object_set (cell,
+ "xpad", 0,
+ "ypad", 0,
+ "visible", FALSE,
+ "width", 16,
+ "height", 16,
+ NULL);
+
+ /* Name */
+ view->priv->text_renderer = games_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (col, view->priv->text_renderer, TRUE);
+ gtk_tree_view_column_set_cell_data_func (col, view->priv->text_renderer,
+ (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
+
+ gtk_tree_view_column_add_attribute (col, view->priv->text_renderer,
+ "name", GAMES_INDIVIDUAL_STORE_COL_NAME);
+ gtk_tree_view_column_add_attribute (col, view->priv->text_renderer,
+ "text", GAMES_INDIVIDUAL_STORE_COL_NAME);
+ gtk_tree_view_column_add_attribute (col, view->priv->text_renderer,
+ "presence-type", GAMES_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
+ gtk_tree_view_column_add_attribute (col, view->priv->text_renderer,
+ "status", GAMES_INDIVIDUAL_STORE_COL_STATUS);
+ gtk_tree_view_column_add_attribute (col, view->priv->text_renderer,
+ "is_group", GAMES_INDIVIDUAL_STORE_COL_IS_GROUP);
+ gtk_tree_view_column_add_attribute (col, view->priv->text_renderer,
+ "compact", GAMES_INDIVIDUAL_STORE_COL_COMPACT);
+
+ /* Avatar */
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (col, cell, FALSE);
+ gtk_tree_view_column_set_cell_data_func (col, cell,
+ (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
+ view, NULL);
+
+ g_object_set (cell,
+ "xpad", 0,
+ "ypad", 0,
+ "visible", FALSE,
+ "width", 32,
+ "height", 32,
+ NULL);
+
+ /* Expander */
+ cell = games_cell_renderer_expander_new ();
+ gtk_tree_view_column_pack_end (col, cell, FALSE);
+ gtk_tree_view_column_set_cell_data_func (col, cell,
+ (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
+ view, NULL);
+
+ /* Actually add the column now we have added all cell renderers */
+ gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
+}
+
+static void
+individual_view_set_view_features (GamesIndividualView *view,
+ GamesIndividualViewFeatureFlags features)
+{
+ g_return_if_fail (GAMES_IS_INDIVIDUAL_VIEW (view));
+
+ view->priv->view_features = features;
+}
+
+static void
+individual_view_dispose (GObject *object)
+{
+ GamesIndividualView *view = GAMES_INDIVIDUAL_VIEW (object);
+
+ tp_clear_object (&view->priv->store);
+ tp_clear_object (&view->priv->filter);
+
+ games_individual_view_set_live_search (view, NULL);
+
+ G_OBJECT_CLASS (games_individual_view_parent_class)->dispose (object);
+}
+
+static void
+individual_view_finalize (GObject *object)
+{
+ GamesIndividualView *self = GAMES_INDIVIDUAL_VIEW (object);
+
+ if (self->priv->expand_groups_idle_handler != 0)
+ g_source_remove (self->priv->expand_groups_idle_handler);
+ g_hash_table_unref (self->priv->expand_groups);
+
+ G_OBJECT_CLASS (games_individual_view_parent_class)->finalize (object);
+}
+
+static void
+individual_view_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GamesIndividualView *self = GAMES_INDIVIDUAL_VIEW (object);
+
+ switch (param_id)
+ {
+ case PROP_STORE:
+ g_value_set_object (value, self->priv->store);
+ break;
+ case PROP_VIEW_FEATURES:
+ g_value_set_flags (value, self->priv->view_features);
+ break;
+ case PROP_SHOW_UNTRUSTED:
+ g_value_set_boolean (value, self->priv->show_untrusted);
+ break;
+ case PROP_SHOW_UNINTERESTING:
+ g_value_set_boolean (value, self->priv->show_uninteresting);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+individual_view_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GamesIndividualView *view = GAMES_INDIVIDUAL_VIEW (object);
+
+ switch (param_id)
+ {
+ case PROP_STORE:
+ games_individual_view_set_store (view, g_value_get_object (value));
+ break;
+ case PROP_VIEW_FEATURES:
+ individual_view_set_view_features (view, g_value_get_flags (value));
+ break;
+ case PROP_SHOW_UNTRUSTED:
+ games_individual_view_set_show_untrusted (view,
+ g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_UNINTERESTING:
+ games_individual_view_set_show_uninteresting (view,
+ g_value_get_boolean (value));
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+games_individual_view_class_init (GamesIndividualViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = individual_view_constructed;
+ object_class->dispose = individual_view_dispose;
+ object_class->finalize = individual_view_finalize;
+ object_class->get_property = individual_view_get_property;
+ object_class->set_property = individual_view_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_STORE,
+ g_param_spec_object ("store",
+ "The store of the view",
+ "The store of the view",
+ GAMES_TYPE_INDIVIDUAL_STORE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_VIEW_FEATURES,
+ g_param_spec_flags ("view-features",
+ "Features of the view",
+ "Flags for all enabled features",
+ GAMES_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
+ GAMES_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SHOW_UNTRUSTED,
+ g_param_spec_boolean ("show-untrusted",
+ "Show Untrusted Individuals",
+ "Whether the view should display untrusted individuals; "
+ "those who could not be who they say they are.",
+ TRUE, G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SHOW_UNINTERESTING,
+ g_param_spec_boolean ("show-uninteresting",
+ "Show Uninteresting Individuals",
+ "Whether the view should not filter out individuals using "
+ "capability checks against view's interest. ",
+ FALSE, G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (GamesIndividualViewPriv));
+}
+
+static void
+games_individual_view_init (GamesIndividualView *view)
+{
+ GamesIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
+ GAMES_TYPE_INDIVIDUAL_VIEW, GamesIndividualViewPriv);
+
+ view->priv = priv;
+
+ priv->show_untrusted = TRUE;
+ priv->show_uninteresting = FALSE;
+
+ /* Get saved group states. */
+ games_individual_groups_get_all ();
+
+ priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free, NULL);
+
+ gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
+ games_individual_store_row_separator_func, NULL, NULL);
+
+ /* Connect to tree view signals rather than override. */
+ g_signal_connect (view, "row-expanded",
+ G_CALLBACK (individual_view_row_expand_or_collapse_cb),
+ GINT_TO_POINTER (TRUE));
+ g_signal_connect (view, "row-collapsed",
+ G_CALLBACK (individual_view_row_expand_or_collapse_cb),
+ GINT_TO_POINTER (FALSE));
+}
+
+GamesIndividualView *
+games_individual_view_new (GamesIndividualStore *store,
+ GamesIndividualViewFeatureFlags view_features)
+{
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_STORE (store), NULL);
+
+ return g_object_new (GAMES_TYPE_INDIVIDUAL_VIEW,
+ "store", store,
+ "view-features", view_features, NULL);
+}
+
+FolksIndividual *
+games_individual_view_dup_selected (GamesIndividualView *view)
+{
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ FolksIndividual *individual;
+
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_VIEW (view), NULL);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return NULL;
+
+ gtk_tree_model_get (model, &iter,
+ GAMES_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
+
+ return individual;
+}
+
+void
+games_individual_view_set_live_search (GamesIndividualView *view,
+ GamesLiveSearch *search)
+{
+ /* remove old handlers if old search was not null */
+ if (view->priv->search_widget != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (view,
+ individual_view_start_search_cb, NULL);
+
+ g_signal_handlers_disconnect_by_func (view->priv->search_widget,
+ individual_view_search_text_notify_cb, view);
+ g_signal_handlers_disconnect_by_func (view->priv->search_widget,
+ individual_view_search_activate_cb, view);
+ g_signal_handlers_disconnect_by_func (view->priv->search_widget,
+ individual_view_search_key_navigation_cb, view);
+ g_signal_handlers_disconnect_by_func (view->priv->search_widget,
+ individual_view_search_hide_cb, view);
+ g_signal_handlers_disconnect_by_func (view->priv->search_widget,
+ individual_view_search_show_cb, view);
+ g_object_unref (view->priv->search_widget);
+ view->priv->search_widget = NULL;
+ }
+
+ /* connect handlers if new search is not null */
+ if (search != NULL)
+ {
+ view->priv->search_widget = g_object_ref (search);
+
+ g_signal_connect (view, "start-interactive-search",
+ G_CALLBACK (individual_view_start_search_cb), NULL);
+
+ g_signal_connect (view->priv->search_widget, "notify::text",
+ G_CALLBACK (individual_view_search_text_notify_cb), view);
+ g_signal_connect (view->priv->search_widget, "activate",
+ G_CALLBACK (individual_view_search_activate_cb), view);
+ g_signal_connect (view->priv->search_widget, "key-navigation",
+ G_CALLBACK (individual_view_search_key_navigation_cb), view);
+ g_signal_connect (view->priv->search_widget, "hide",
+ G_CALLBACK (individual_view_search_hide_cb), view);
+ g_signal_connect (view->priv->search_widget, "show",
+ G_CALLBACK (individual_view_search_show_cb), view);
+ }
+}
+
+gboolean
+games_individual_view_is_searching (GamesIndividualView *self)
+{
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_VIEW (self), FALSE);
+
+ return (self->priv->search_widget != NULL &&
+ gtk_widget_get_visible (self->priv->search_widget));
+}
+
+gboolean
+games_individual_view_get_show_untrusted (GamesIndividualView *self)
+{
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_VIEW (self), FALSE);
+
+ return self->priv->show_untrusted;
+}
+
+void
+games_individual_view_set_show_untrusted (GamesIndividualView *self,
+ gboolean show_untrusted)
+{
+ g_return_if_fail (GAMES_IS_INDIVIDUAL_VIEW (self));
+
+ self->priv->show_untrusted = show_untrusted;
+
+ g_object_notify (G_OBJECT (self), "show-untrusted");
+ gtk_tree_model_filter_refilter (self->priv->filter);
+}
+
+GamesIndividualStore *
+games_individual_view_get_store (GamesIndividualView *self)
+{
+ g_return_val_if_fail (GAMES_IS_INDIVIDUAL_VIEW (self), NULL);
+
+ return self->priv->store;
+}
+
+void
+games_individual_view_set_store (GamesIndividualView *self,
+ GamesIndividualStore *store)
+{
+ g_return_if_fail (GAMES_IS_INDIVIDUAL_VIEW (self));
+ g_return_if_fail (store == NULL || GAMES_IS_INDIVIDUAL_STORE (store));
+
+ /* Destroy the old filter and remove the old store */
+ if (self->priv->store != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (self->priv->filter,
+ individual_view_row_has_child_toggled_cb, self);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
+ }
+
+ tp_clear_object (&self->priv->filter);
+ tp_clear_object (&self->priv->store);
+
+ /* Set the new store */
+ self->priv->store = store;
+
+ if (store != NULL)
+ {
+ g_object_ref (store);
+
+ /* Create a new filter */
+ self->priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
+ GTK_TREE_MODEL (self->priv->store), NULL));
+ gtk_tree_model_filter_set_visible_func (self->priv->filter,
+ individual_view_filter_visible_func, self, NULL);
+
+ g_signal_connect (self->priv->filter, "row-has-child-toggled",
+ G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (self),
+ GTK_TREE_MODEL (self->priv->filter));
+ }
+}
+
+void
+games_individual_view_start_search (GamesIndividualView *self)
+{
+ g_return_if_fail (GAMES_IS_INDIVIDUAL_VIEW (self));
+ g_return_if_fail (self->priv->search_widget != NULL);
+
+ if (gtk_widget_get_visible (GTK_WIDGET (self->priv->search_widget)))
+ gtk_widget_grab_focus (GTK_WIDGET (self->priv->search_widget));
+ else
+ gtk_widget_show (GTK_WIDGET (self->priv->search_widget));
+}
+
+void
+games_individual_view_set_custom_filter (GamesIndividualView *self,
+ GtkTreeModelFilterVisibleFunc filter,
+ gpointer data)
+{
+ self->priv->custom_filter = filter;
+ self->priv->custom_filter_data = data;
+}
+
+void
+games_individual_view_refilter (GamesIndividualView *self)
+{
+ gtk_tree_model_filter_refilter (self->priv->filter);
+}
+
+void
+games_individual_view_select_first (GamesIndividualView *self)
+{
+ GtkTreeIter iter;
+
+ gtk_tree_model_filter_refilter (self->priv->filter);
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->priv->filter), &iter))
+ {
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (self));
+
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+}
+
+void
+games_individual_view_set_show_uninteresting (GamesIndividualView *self,
+ gboolean show_uninteresting)
+{
+ g_return_if_fail (GAMES_IS_INDIVIDUAL_VIEW (self));
+
+
+ self->priv->show_uninteresting = show_uninteresting;
+
+ g_object_notify (G_OBJECT (self), "show-uninteresting");
+ gtk_tree_model_filter_refilter (self->priv->filter);
+}
diff --git a/libgames-contacts/games-individual-view.h b/libgames-contacts/games-individual-view.h
new file mode 100644
index 0000000..109f3f5
--- /dev/null
+++ b/libgames-contacts/games-individual-view.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * 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
+ */
+
+/* Much of code here is a simplified adaptation of the way contacts
+ * are displayed in Empathy, a chat application using folks.
+ * (https://live.gnome.org/Empathy)
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ */
+
+
+#ifndef __GAMES_INDIVIDUAL_VIEW_H__
+#define __GAMES_INDIVIDUAL_VIEW_H__
+
+#include <gtk/gtk.h>
+
+#include <folks/folks.h>
+
+#include "games-enum-types.h"
+
+#include "games-live-search.h"
+#include "games-individual-store.h"
+
+G_BEGIN_DECLS
+#define GAMES_TYPE_INDIVIDUAL_VIEW (games_individual_view_get_type ())
+#define GAMES_INDIVIDUAL_VIEW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GAMES_TYPE_INDIVIDUAL_VIEW, GamesIndividualView))
+#define GAMES_INDIVIDUAL_VIEW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GAMES_TYPE_INDIVIDUAL_VIEW, GamesIndividualViewClass))
+#define GAMES_IS_INDIVIDUAL_VIEW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GAMES_TYPE_INDIVIDUAL_VIEW))
+#define GAMES_IS_INDIVIDUAL_VIEW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GAMES_TYPE_INDIVIDUAL_VIEW))
+#define GAMES_INDIVIDUAL_VIEW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GAMES_TYPE_INDIVIDUAL_VIEW, GamesIndividualViewClass))
+typedef struct _GamesIndividualView GamesIndividualView;
+typedef struct _GamesIndividualViewClass GamesIndividualViewClass;
+typedef struct _GamesIndividualViewPriv GamesIndividualViewPriv;
+
+typedef enum
+{
+ GAMES_INDIVIDUAL_VIEW_FEATURE_NONE = 0,
+ GAMES_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE = 1 << 0,
+} GamesIndividualViewFeatureFlags;
+
+struct _GamesIndividualView
+{
+ GtkTreeView parent;
+ GamesIndividualViewPriv *priv;
+};
+
+struct _GamesIndividualViewClass
+{
+ GtkTreeViewClass parent_class;
+};
+
+GType games_individual_view_get_type (void) G_GNUC_CONST;
+
+GamesIndividualView *games_individual_view_new (
+ GamesIndividualStore *store,
+ GamesIndividualViewFeatureFlags view_features);
+
+FolksIndividual *games_individual_view_dup_selected (
+ GamesIndividualView *view);
+
+GtkWidget *games_individual_view_get_individual_menu (
+ GamesIndividualView *view);
+
+GtkWidget *games_individual_view_get_group_menu (GamesIndividualView *view);
+
+void games_individual_view_set_live_search (GamesIndividualView *view,
+ GamesLiveSearch *search);
+
+gboolean games_individual_view_get_show_offline (
+ GamesIndividualView *view);
+
+void games_individual_view_set_show_offline (
+ GamesIndividualView *view,
+ gboolean show_offline);
+
+gboolean games_individual_view_get_show_untrusted (
+ GamesIndividualView *self);
+
+void games_individual_view_set_show_untrusted (GamesIndividualView *self,
+ gboolean show_untrusted);
+
+void games_individual_view_set_show_uninteresting (
+ GamesIndividualView *view,
+ gboolean show_uninteresting);
+
+gboolean games_individual_view_is_searching (
+ GamesIndividualView *view);
+
+GamesIndividualStore *games_individual_view_get_store (
+ GamesIndividualView *self);
+void games_individual_view_set_store (GamesIndividualView *self,
+ GamesIndividualStore *store);
+
+void games_individual_view_start_search (GamesIndividualView *self);
+
+gboolean individual_view_filter_default (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer user_data,
+ GamesActionType interest);
+
+void games_individual_view_set_custom_filter (GamesIndividualView *self,
+ GtkTreeModelFilterVisibleFunc filter,
+ gpointer data);
+
+void games_individual_view_refilter (GamesIndividualView *self);
+
+void games_individual_view_select_first (GamesIndividualView *self);
+
+G_END_DECLS
+#endif /* __GAMES_INDIVIDUAL_VIEW_H__ */
diff --git a/libgames-contacts/games-live-search.c b/libgames-contacts/games-live-search.c
new file mode 100644
index 0000000..59b1bfd
--- /dev/null
+++ b/libgames-contacts/games-live-search.c
@@ -0,0 +1,704 @@
+/*
+ * 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
+ */
+
+/*
+ * Live search is taken from Empathy (https://live.gnome.org/Empathy)
+ * Copyright (C) 2010 Collabora Ltd.
+ * Copyright (C) 2007-2010 Nokia Corporation.
+ */
+
+#include <config.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <libgames-contacts/games-ui-utils.h>
+
+#include "games-live-search.h"
+
+G_DEFINE_TYPE (GamesLiveSearch, games_live_search, GTK_TYPE_HBOX)
+
+struct _GamesLiveSearchPriv
+{
+ GtkWidget *search_entry;
+ GtkWidget *hook_widget;
+
+ GPtrArray *stripped_words;
+};
+
+enum
+{
+ PROP_0,
+ PROP_HOOK_WIDGET,
+ PROP_TEXT
+};
+
+enum
+{
+ ACTIVATE,
+ KEYNAV,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void live_search_hook_widget_destroy_cb (GtkWidget *object,
+ gpointer user_data);
+
+/**
+ * stripped_char:
+ *
+ * Returns a stripped version of @ch, removing any case, accentuation
+ * mark, or any special mark on it.
+ **/
+static gunichar
+stripped_char (gunichar ch)
+{
+ gunichar retval = 0;
+ GUnicodeType utype;
+
+ utype = g_unichar_type (ch);
+
+ switch (utype)
+ {
+ case G_UNICODE_CONTROL:
+ case G_UNICODE_FORMAT:
+ case G_UNICODE_UNASSIGNED:
+ case G_UNICODE_NON_SPACING_MARK:
+ case G_UNICODE_COMBINING_MARK:
+ case G_UNICODE_ENCLOSING_MARK:
+ /* Ignore those */
+ break;
+ case G_UNICODE_PRIVATE_USE:
+ case G_UNICODE_SURROGATE:
+ case G_UNICODE_LOWERCASE_LETTER:
+ case G_UNICODE_MODIFIER_LETTER:
+ case G_UNICODE_OTHER_LETTER:
+ case G_UNICODE_TITLECASE_LETTER:
+ case G_UNICODE_UPPERCASE_LETTER:
+ case G_UNICODE_DECIMAL_NUMBER:
+ case G_UNICODE_LETTER_NUMBER:
+ case G_UNICODE_OTHER_NUMBER:
+ case G_UNICODE_CONNECT_PUNCTUATION:
+ case G_UNICODE_DASH_PUNCTUATION:
+ case G_UNICODE_CLOSE_PUNCTUATION:
+ case G_UNICODE_FINAL_PUNCTUATION:
+ case G_UNICODE_INITIAL_PUNCTUATION:
+ case G_UNICODE_OTHER_PUNCTUATION:
+ case G_UNICODE_OPEN_PUNCTUATION:
+ case G_UNICODE_CURRENCY_SYMBOL:
+ case G_UNICODE_MODIFIER_SYMBOL:
+ case G_UNICODE_MATH_SYMBOL:
+ case G_UNICODE_OTHER_SYMBOL:
+ case G_UNICODE_LINE_SEPARATOR:
+ case G_UNICODE_PARAGRAPH_SEPARATOR:
+ case G_UNICODE_SPACE_SEPARATOR:
+ default:
+ ch = g_unichar_tolower (ch);
+ g_unichar_fully_decompose (ch, FALSE, &retval, 1);
+ }
+
+ return retval;
+}
+
+static void
+append_word (GPtrArray **word_array,
+ GString **word)
+{
+ if (*word != NULL)
+ {
+ if (*word_array == NULL)
+ *word_array = g_ptr_array_new_with_free_func (g_free);
+ g_ptr_array_add (*word_array, g_string_free (*word, FALSE));
+ *word = NULL;
+ }
+}
+
+GPtrArray *
+games_live_search_strip_utf8_string (const gchar *string)
+{
+ GPtrArray *word_array = NULL;
+ GString *word = NULL;
+ const gchar *p;
+
+ if (STR_EMPTY (string))
+ return NULL;
+
+ for (p = string; *p != '\0'; p = g_utf8_next_char (p))
+ {
+ gunichar sc;
+
+ /* Make the char lower-case, remove its accentuation marks, and ignore it
+ * if it is just unicode marks */
+ sc = stripped_char (g_utf8_get_char (p));
+ if (sc == 0)
+ continue;
+
+ /* If it is not alpha-num, it is separator between words */
+ if (!g_unichar_isalnum (sc))
+ {
+ append_word (&word_array, &word);
+ continue;
+ }
+
+ /* It is alpha-num, append this char to current word, or start new word */
+ if (word == NULL)
+ word = g_string_new (NULL);
+ g_string_append_unichar (word, sc);
+ }
+
+ append_word (&word_array, &word);
+
+ return word_array;
+}
+
+static gboolean
+live_search_match_prefix (const gchar *string,
+ const gchar *prefix)
+{
+ const gchar *p;
+ const gchar *prefix_p;
+ gboolean next_word = FALSE;
+
+ if (prefix == NULL || prefix[0] == 0)
+ return TRUE;
+
+ if (STR_EMPTY (string))
+ return FALSE;
+
+ prefix_p = prefix;
+ for (p = string; *p != '\0'; p = g_utf8_next_char (p))
+ {
+ gunichar sc;
+
+ /* Make the char lower-case, remove its accentuation marks, and ignore it
+ * if it is just unicode marks */
+ sc = stripped_char (g_utf8_get_char (p));
+ if (sc == 0)
+ continue;
+
+ /* If we want to go to next word, ignore alpha-num chars */
+ if (next_word && g_unichar_isalnum (sc))
+ continue;
+ next_word = FALSE;
+
+ /* Ignore word separators */
+ if (!g_unichar_isalnum (sc))
+ continue;
+
+ /* If this char does not match prefix_p, go to next word and start again
+ * from the beginning of prefix */
+ if (sc != g_utf8_get_char (prefix_p))
+ {
+ next_word = TRUE;
+ prefix_p = prefix;
+ continue;
+ }
+
+ /* prefix_p match, verify to next char. If this was the last of prefix,
+ * it means it completely machted and we are done. */
+ prefix_p = g_utf8_next_char (prefix_p);
+ if (*prefix_p == '\0')
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+games_live_search_match_words (const gchar *string,
+ GPtrArray *words)
+{
+ guint i;
+
+ if (words == NULL)
+ return TRUE;
+
+ for (i = 0; i < words->len; i++)
+ if (!live_search_match_prefix (string, g_ptr_array_index (words, i)))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+fire_key_navigation_sig (GamesLiveSearch *self,
+ GdkEventKey *event)
+{
+ gboolean ret;
+
+ g_signal_emit (self, signals[KEYNAV], 0, event, &ret);
+ return ret;
+}
+
+static gboolean
+live_search_entry_key_pressed_cb (GtkEntry *entry,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ GamesLiveSearch *self = GAMES_LIVE_SEARCH (user_data);
+
+ /* if esc key pressed, hide the search */
+ if (event->keyval == GDK_KEY_Escape)
+ {
+ gtk_widget_hide (GTK_WIDGET (self));
+ return TRUE;
+ }
+
+ /* emit key navigation signal, so other widgets can respond to it properly */
+ if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_Down
+ || event->keyval == GDK_KEY_Page_Up || event->keyval == GDK_KEY_Page_Down)
+ {
+ return fire_key_navigation_sig (self, event);
+ }
+
+ if (event->keyval == GDK_KEY_Home || event->keyval == GDK_KEY_End ||
+ event->keyval == GDK_KEY_space)
+ {
+ /* If the live search is visible, the entry should catch the Home/End
+ * and space events */
+ if (!gtk_widget_get_visible (GTK_WIDGET (self)))
+ {
+ return fire_key_navigation_sig (self, event);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+live_search_text_changed (GtkEntry *entry,
+ gpointer user_data)
+{
+ GamesLiveSearch *self = GAMES_LIVE_SEARCH (user_data);
+ const gchar *text;
+
+ text = gtk_entry_get_text (entry);
+
+ if (STR_EMPTY (text))
+ gtk_widget_hide (GTK_WIDGET (self));
+ else
+ gtk_widget_show (GTK_WIDGET (self));
+
+ if (self->priv->stripped_words != NULL)
+ g_ptr_array_unref (self->priv->stripped_words);
+
+ self->priv->stripped_words = games_live_search_strip_utf8_string (text);
+
+ g_object_notify (G_OBJECT (self), "text");
+}
+
+static void
+live_search_close_pressed (GtkEntry *entry,
+ GtkEntryIconPosition icon_pos,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ GamesLiveSearch *self = GAMES_LIVE_SEARCH (user_data);
+
+ gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static gboolean
+live_search_key_press_event_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ GamesLiveSearch *self = GAMES_LIVE_SEARCH (user_data);
+ GdkEvent *new_event;
+ gboolean ret;
+
+ /* dont forward this event to the entry, else the event is consumed by the
+ * entry and does not close the window */
+ if (!gtk_widget_get_visible (GTK_WIDGET (self)) &&
+ event->keyval == GDK_KEY_Escape)
+ return FALSE;
+
+ /* do not show the search if CTRL and/or ALT are pressed with a key
+ * this is needed, because otherwise the CTRL + F accel would not work,
+ * because the entry consumes it */
+ if (event->state & (GDK_MOD1_MASK | GDK_CONTROL_MASK) ||
+ event->keyval == GDK_KEY_Control_L ||
+ event->keyval == GDK_KEY_Control_R)
+ return FALSE;
+
+ /* dont forward the up/down and Page Up/Down arrow keys to the entry,
+ * they are needed for navigation in the treeview and are not needed in
+ * the search entry */
+ if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_Down ||
+ event->keyval == GDK_KEY_Page_Up || event->keyval == GDK_KEY_Page_Down)
+ return FALSE;
+
+ if (event->keyval == GDK_KEY_Home || event->keyval == GDK_KEY_End ||
+ event->keyval == GDK_KEY_space)
+ {
+ /* Home/End and space keys have to be forwarded to the entry only if
+ * the live search is visible (to move the cursor inside the entry). */
+ if (!gtk_widget_get_visible (GTK_WIDGET (self)))
+ return FALSE;
+ }
+
+ /* Don't forward shift keys events as focusing the search entry would
+ * cancel an in-progress editing on a cell renderer (like when renaming a
+ * group). There is no point focusing it anyway as we don't display the
+ * search entry when only a shift key is pressed. */
+ if (event->keyval == GDK_KEY_Shift_L ||
+ event->keyval == GDK_KEY_Shift_R)
+ return FALSE;
+
+ /* realize the widget if it is not realized yet */
+ gtk_widget_realize (self->priv->search_entry);
+ if (!gtk_widget_has_focus (self->priv->search_entry))
+ {
+ gtk_widget_grab_focus (self->priv->search_entry);
+ gtk_editable_set_position (GTK_EDITABLE (self->priv->search_entry), -1);
+ }
+
+ /* forward the event to the search entry */
+ new_event = gdk_event_copy ((GdkEvent *) event);
+ ret = gtk_widget_event (self->priv->search_entry, new_event);
+ gdk_event_free (new_event);
+
+ return ret;
+}
+
+static void
+live_search_entry_activate_cb (GtkEntry *entry,
+ GamesLiveSearch *self)
+{
+ g_signal_emit (self, signals[ACTIVATE], 0);
+}
+
+static void
+live_search_release_hook_widget (GamesLiveSearch *self)
+{
+ /* remove old handlers if old source was not null */
+ if (self->priv->hook_widget != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (self->priv->hook_widget,
+ live_search_key_press_event_cb, self);
+ g_signal_handlers_disconnect_by_func (self->priv->hook_widget,
+ live_search_hook_widget_destroy_cb, self);
+ g_object_unref (self->priv->hook_widget);
+ self->priv->hook_widget = NULL;
+ }
+}
+
+static void
+live_search_hook_widget_destroy_cb (GtkWidget *object,
+ gpointer user_data)
+{
+ GamesLiveSearch *self = GAMES_LIVE_SEARCH (user_data);
+
+ /* unref the hook widget and hide search */
+ gtk_widget_hide (GTK_WIDGET (self));
+ live_search_release_hook_widget (self);
+}
+
+static void
+live_search_dispose (GObject *obj)
+{
+ GamesLiveSearch *self = GAMES_LIVE_SEARCH (obj);
+
+ live_search_release_hook_widget (self);
+
+ if (G_OBJECT_CLASS (games_live_search_parent_class)->dispose != NULL)
+ G_OBJECT_CLASS (games_live_search_parent_class)->dispose (obj);
+}
+
+static void
+live_search_finalize (GObject *obj)
+{
+ GamesLiveSearch *self = GAMES_LIVE_SEARCH (obj);
+
+ if (self->priv->stripped_words != NULL)
+ g_ptr_array_unref (self->priv->stripped_words);
+
+ if (G_OBJECT_CLASS (games_live_search_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (games_live_search_parent_class)->finalize (obj);
+}
+
+static void
+live_search_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GamesLiveSearch *self = GAMES_LIVE_SEARCH (object);
+
+ switch (param_id)
+ {
+ case PROP_HOOK_WIDGET:
+ g_value_set_object (value, games_live_search_get_hook_widget (self));
+ break;
+ case PROP_TEXT:
+ g_value_set_string (value, games_live_search_get_text (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+live_search_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GamesLiveSearch *self = GAMES_LIVE_SEARCH (object);
+
+ switch (param_id) {
+ case PROP_HOOK_WIDGET:
+ games_live_search_set_hook_widget (self, g_value_get_object (value));
+ break;
+ case PROP_TEXT:
+ games_live_search_set_text (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+live_search_unmap (GtkWidget *widget)
+{
+ GamesLiveSearch *self = GAMES_LIVE_SEARCH (widget);
+
+ GTK_WIDGET_CLASS (games_live_search_parent_class)->unmap (widget);
+
+ /* unmap can happen if a parent gets hidden, in that case we want to hide
+ * the live search as well, so when it gets mapped again, the live search
+ * won't be shown. */
+ gtk_widget_hide (widget);
+
+ gtk_entry_set_text (GTK_ENTRY (self->priv->search_entry), "");
+ gtk_widget_grab_focus (self->priv->hook_widget);
+}
+
+static void
+live_search_show (GtkWidget *widget)
+{
+ GamesLiveSearch *self = GAMES_LIVE_SEARCH (widget);
+
+ if (!gtk_widget_has_focus (self->priv->search_entry))
+ gtk_widget_grab_focus (self->priv->search_entry);
+
+ GTK_WIDGET_CLASS (games_live_search_parent_class)->show (widget);
+}
+
+static void
+live_search_grab_focus (GtkWidget *widget)
+{
+ GamesLiveSearch *self = GAMES_LIVE_SEARCH (widget);
+
+ if (!gtk_widget_has_focus (self->priv->search_entry))
+ {
+ gtk_widget_grab_focus (self->priv->search_entry);
+ gtk_editable_set_position (GTK_EDITABLE (self->priv->search_entry), -1);
+ }
+}
+
+static void
+games_live_search_class_init (GamesLiveSearchClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
+ GParamSpec *param_spec;
+
+ object_class->finalize = live_search_finalize;
+ object_class->dispose = live_search_dispose;
+ object_class->get_property = live_search_get_property;
+ object_class->set_property = live_search_set_property;
+
+ widget_class->unmap = live_search_unmap;
+ widget_class->show = live_search_show;
+ widget_class->grab_focus = live_search_grab_focus;
+
+ signals[ACTIVATE] = g_signal_new ("activate",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 0);
+
+ signals[KEYNAV] = g_signal_new ("key-navigation",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ g_signal_accumulator_true_handled, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_BOOLEAN, 1, GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ param_spec = g_param_spec_object ("hook-widget", "Live Search Hook Widget",
+ "The live search catches key-press-events on this widget",
+ GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_HOOK_WIDGET,
+ param_spec);
+
+ param_spec = g_param_spec_string ("text", "Live Search Text",
+ "The text of the live search entry",
+ "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TEXT, param_spec);
+
+ g_type_class_add_private (klass, sizeof (GamesLiveSearchPriv));
+}
+
+static void
+games_live_search_init (GamesLiveSearch *self)
+{
+ GamesLiveSearchPriv *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE ((self), GAMES_TYPE_LIVE_SEARCH,
+ GamesLiveSearchPriv);
+
+ gtk_widget_set_no_show_all (GTK_WIDGET (self), TRUE);
+
+ priv->search_entry = gtk_entry_new ();
+ gtk_entry_set_icon_from_stock (GTK_ENTRY (priv->search_entry),
+ GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLOSE);
+ gtk_entry_set_icon_activatable (GTK_ENTRY (priv->search_entry),
+ GTK_ENTRY_ICON_SECONDARY, TRUE);
+ gtk_entry_set_icon_sensitive (GTK_ENTRY (priv->search_entry),
+ GTK_ENTRY_ICON_SECONDARY, TRUE);
+ gtk_widget_show (priv->search_entry);
+
+ gtk_box_pack_start (GTK_BOX (self), priv->search_entry, TRUE, TRUE, 0);
+
+ g_signal_connect (priv->search_entry, "icon_release",
+ G_CALLBACK (live_search_close_pressed), self);
+ g_signal_connect (priv->search_entry, "changed",
+ G_CALLBACK (live_search_text_changed), self);
+ g_signal_connect (priv->search_entry, "key-press-event",
+ G_CALLBACK (live_search_entry_key_pressed_cb), self);
+ g_signal_connect (priv->search_entry, "activate",
+ G_CALLBACK (live_search_entry_activate_cb), self);
+
+ priv->hook_widget = NULL;
+
+ self->priv = priv;
+}
+
+GtkWidget *
+games_live_search_new (GtkWidget *hook)
+{
+ g_return_val_if_fail (hook == NULL || GTK_IS_WIDGET (hook), NULL);
+
+ return g_object_new (GAMES_TYPE_LIVE_SEARCH,
+ "hook-widget", hook,
+ NULL);
+}
+
+/* public methods */
+
+GtkWidget *
+games_live_search_get_hook_widget (GamesLiveSearch *self)
+{
+ g_return_val_if_fail (GAMES_IS_LIVE_SEARCH (self), NULL);
+
+ return self->priv->hook_widget;
+}
+
+void
+games_live_search_set_hook_widget (GamesLiveSearch *self,
+ GtkWidget *hook)
+{
+ g_return_if_fail (GAMES_IS_LIVE_SEARCH (self));
+ g_return_if_fail (hook == NULL || GTK_IS_WIDGET (hook));
+
+ /* release the actual widget */
+ live_search_release_hook_widget (self);
+
+ /* connect handlers if new source is not null */
+ if (hook != NULL)
+ {
+ self->priv->hook_widget = g_object_ref (hook);
+ g_signal_connect (self->priv->hook_widget, "key-press-event",
+ G_CALLBACK (live_search_key_press_event_cb),
+ self);
+ g_signal_connect (self->priv->hook_widget, "destroy",
+ G_CALLBACK (live_search_hook_widget_destroy_cb),
+ self);
+ }
+}
+
+const gchar *
+games_live_search_get_text (GamesLiveSearch *self)
+{
+ g_return_val_if_fail (GAMES_IS_LIVE_SEARCH (self), NULL);
+
+ return gtk_entry_get_text (GTK_ENTRY (self->priv->search_entry));
+}
+
+void
+games_live_search_set_text (GamesLiveSearch *self,
+ const gchar *text)
+{
+ g_return_if_fail (GAMES_IS_LIVE_SEARCH (self));
+ g_return_if_fail (text != NULL);
+
+ gtk_entry_set_text (GTK_ENTRY (self->priv->search_entry), text);
+}
+
+/**
+ * games_live_search_match:
+ * @self: a #GamesLiveSearch
+ * @string: a string where to search, must be valid UTF-8.
+ *
+ * Search if one of the words in @string string starts with the current text
+ * of @self.
+ *
+ * Searching for "aba" in "Abasto" will match, searching in "Moraba" will not,
+ * and searching in "A tool (abacus)" will do.
+ *
+ * The match is not case-sensitive, and regardless of the accentuation marks.
+ *
+ * Returns: %TRUE if a match is found, %FALSE otherwise.
+ *
+ **/
+gboolean
+games_live_search_match (GamesLiveSearch *self,
+ const gchar *string)
+{
+ g_return_val_if_fail (GAMES_IS_LIVE_SEARCH (self), FALSE);
+
+ return games_live_search_match_words (string, self->priv->stripped_words);
+}
+
+gboolean
+games_live_search_match_string (const gchar *string,
+ const gchar *prefix)
+{
+ GPtrArray *words;
+ gboolean match;
+
+ words = games_live_search_strip_utf8_string (prefix);
+ match = games_live_search_match_words (string, words);
+ if (words != NULL)
+ g_ptr_array_unref (words);
+
+ return match;
+}
+
+GPtrArray *
+games_live_search_get_words (GamesLiveSearch *self)
+{
+ return self->priv->stripped_words;
+}
diff --git a/libgames-contacts/games-live-search.h b/libgames-contacts/games-live-search.h
new file mode 100644
index 0000000..7d4dd9b
--- /dev/null
+++ b/libgames-contacts/games-live-search.h
@@ -0,0 +1,81 @@
+/*
+ * 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
+ */
+
+ /*
+ * Live search is taken from Empathy (https://live.gnome.org/Empathy)
+ * Copyright (C) 2010 Collabora Ltd.
+ * Copyright (C) 2007-2010 Nokia Corporation.
+ */
+
+#ifndef __GAMES_LIVE_SEARCH_H__
+#define __GAMES_LIVE_SEARCH_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GAMES_TYPE_LIVE_SEARCH (games_live_search_get_type ())
+#define GAMES_LIVE_SEARCH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GAMES_TYPE_LIVE_SEARCH, GamesLiveSearch))
+#define GAMES_LIVE_SEARCH_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GAMES_TYPE_LIVE_SEARCH, GamesLiveSearchClass))
+#define GAMES_IS_LIVE_SEARCH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GAMES_TYPE_LIVE_SEARCH))
+#define GAMES_IS_LIVE_SEARCH_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GAMES_TYPE_LIVE_SEARCH))
+#define GAMES_LIVE_SEARCH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GAMES_TYPE_LIVE_SEARCH, GamesLiveSearchClass))
+
+typedef struct _GamesLiveSearch GamesLiveSearch;
+typedef struct _GamesLiveSearchClass GamesLiveSearchClass;
+typedef struct _GamesLiveSearchPriv GamesLiveSearchPriv;
+
+struct _GamesLiveSearch {
+ GtkHBox parent;
+
+ /*<private>*/
+ GamesLiveSearchPriv *priv;
+};
+
+struct _GamesLiveSearchClass {
+ GtkHBoxClass parent_class;
+};
+
+GType games_live_search_get_type (void) G_GNUC_CONST;
+GtkWidget *games_live_search_new (GtkWidget *hook);
+
+GtkWidget *games_live_search_get_hook_widget (GamesLiveSearch *self);
+void games_live_search_set_hook_widget (GamesLiveSearch *self,
+ GtkWidget *hook);
+
+const gchar *games_live_search_get_text (GamesLiveSearch *self);
+void games_live_search_set_text (GamesLiveSearch *self,
+ const gchar *text);
+
+gboolean games_live_search_match (GamesLiveSearch *self,
+ const gchar *string);
+
+GPtrArray * games_live_search_strip_utf8_string (const gchar *string);
+
+gboolean games_live_search_match_words (const gchar *string,
+ GPtrArray *words);
+
+GPtrArray * games_live_search_get_words (GamesLiveSearch *self);
+
+/* Made public for unit tests */
+gboolean games_live_search_match_string (const gchar *string,
+ const gchar *prefix);
+
+G_END_DECLS
+
+#endif /* __GAMES_LIVE_SEARCH_H__ */
diff --git a/libgames-contacts/games-ui-utils.c b/libgames-contacts/games-ui-utils.c
index 668cae7..9917996 100644
--- a/libgames-contacts/games-ui-utils.c
+++ b/libgames-contacts/games-ui-utils.c
@@ -39,6 +39,7 @@
#include <folks/folks.h>
#include "games-ui-utils.h"
+#include "games-live-search.h"
/* Contact presence image names */
@@ -972,6 +973,70 @@ out:
g_free (cmd);
}
+/* @words = games_live_search_strip_utf8_string (@text);
+ *
+ * User has to pass both so we don't have to compute @words ourself each time
+ * this function is called. */
+gboolean
+games_individual_match_string (FolksIndividual *individual,
+ const char *text,
+ GPtrArray *words)
+{
+ const gchar *str;
+ GeeSet *personas;
+ GeeIterator *iter;
+ gboolean retval = FALSE;
+
+ /* check alias name */
+ str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
+
+ if (games_live_search_match_words (str, words))
+ return TRUE;
+
+ personas = folks_individual_get_personas (individual);
+
+ /* check contact id, remove the @server.com part */
+ iter = gee_iterable_iterator (GEE_ITERABLE (personas));
+ while (retval == FALSE && gee_iterator_next (iter))
+ {
+ FolksPersona *persona = gee_iterator_get (iter);
+ const gchar *p;
+
+ if (games_folks_persona_is_interesting (persona))
+ {
+ str = folks_persona_get_display_id (persona);
+
+ /* Accept the persona if @text is a full prefix of his ID; that allows
+ * user to find, say, a jabber contact by typing his JID. */
+ if (g_str_has_prefix (str, text))
+ {
+ retval = TRUE;
+ }
+ else
+ {
+ gchar *dup_str = NULL;
+ gboolean visible;
+
+ p = strstr (str, "@");
+ if (p != NULL)
+ str = dup_str = g_strndup (str, p - str);
+
+ visible = games_live_search_match_words (str, words);
+ g_free (dup_str);
+ if (visible)
+ retval = TRUE;
+ }
+ }
+ g_clear_object (&persona);
+ }
+ g_clear_object (&iter);
+
+ /* FIXME: Add more rules here, we could check phone numbers in
+ * contact's vCard for example. */
+ return retval;
+}
+
+
/* Most of the workspace manipulation code has been copied from libwnck
* Copyright (C) 2001 Havoc Pennington
* Copyright (C) 2005-2007 Vincent Untz
diff --git a/libgames-contacts/games-ui-utils.h b/libgames-contacts/games-ui-utils.h
index 9fc7f54..e176d03 100644
--- a/libgames-contacts/games-ui-utils.h
+++ b/libgames-contacts/games-ui-utils.h
@@ -126,10 +126,9 @@ void games_url_show (GtkWidget *parent,
/* Misc */
gint64 games_get_current_action_time (void);
-gboolean games_individual_match_string (
- FolksIndividual *individual,
- const gchar *text,
- GPtrArray *words);
+gboolean games_individual_match_string (FolksIndividual *individual,
+ const gchar *text,
+ GPtrArray *words);
void games_launch_program (const gchar *dir,
const gchar *name,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]