empathy-contact-list-view.c 60.9 KB
Newer Older
Xavier Claessens's avatar
Xavier Claessens committed
1 2 3
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 2005-2007 Imendio AB
4
 * Copyright (C) 2007-2008 Collabora Ltd.
Xavier Claessens's avatar
Xavier Claessens committed
5 6 7 8 9 10 11 12 13 14 15 16 17
 *
 * 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
18 19
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
Xavier Claessens's avatar
Xavier Claessens committed
20 21 22 23 24 25 26 27 28 29
 *
 * Authors: Mikael Hallendal <micke@imendio.com>
 *          Martyn Russell <martyn@imendio.com>
 *          Xavier Claessens <xclaesse@gmail.com>
 */

#include "config.h"

#include <string.h>

30
#include <glib/gi18n-lib.h>
31
#include <gdk/gdkkeysyms.h>
Xavier Claessens's avatar
Xavier Claessens committed
32 33
#include <gtk/gtk.h>

34
#include <telepathy-glib/account-manager.h>
35
#include <telepathy-glib/util.h>
Xavier Claessens's avatar
Xavier Claessens committed
36

37
#include <libempathy/empathy-tp-contact-factory.h>
Xavier Claessens's avatar
Xavier Claessens committed
38
#include <libempathy/empathy-contact-list.h>
39
#include <libempathy/empathy-contact-groups.h>
40
#include <libempathy/empathy-request-util.h>
41
#include <libempathy/empathy-utils.h>
Xavier Claessens's avatar
Xavier Claessens committed
42

43 44
#include "empathy-contact-list-view.h"
#include "empathy-contact-list-store.h"
Xavier Claessens's avatar
Xavier Claessens committed
45
#include "empathy-images.h"
46 47
#include "empathy-cell-renderer-expander.h"
#include "empathy-cell-renderer-text.h"
48
#include "empathy-cell-renderer-activatable.h"
49
#include "empathy-ui-utils.h"
50
#include "empathy-gtk-enum-types.h"
51
#include "empathy-gtk-marshal.h"
Xavier Claessens's avatar
Xavier Claessens committed
52

53 54
#define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
#include <libempathy/empathy-debug.h>
Xavier Claessens's avatar
Xavier Claessens committed
55 56 57 58 59

/* Active users are those which have recently changed state
 * (e.g. online, offline or from normal to a busy state).
 */

60
#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
61
typedef struct {
62 63 64 65
	EmpathyContactListStore        *store;
	GtkTreeRowReference            *drag_row;
	EmpathyContactListFeatureFlags  list_features;
	EmpathyContactFeatureFlags      contact_features;
66
	GtkWidget                      *tooltip_widget;
67
	GtkTargetList                  *file_targets;
68 69 70

	GtkTreeModelFilter             *filter;
	GtkWidget                      *search_widget;
71
} EmpathyContactListViewPriv;
Xavier Claessens's avatar
Xavier Claessens committed
72 73

typedef struct {
74
	EmpathyContactListView *view;
Xavier Claessens's avatar
Xavier Claessens committed
75 76 77 78 79
	GtkTreePath           *path;
	guint                  timeout_id;
} DragMotionData;

typedef struct {
80 81
	EmpathyContactListView *view;
	EmpathyContact         *contact;
Xavier Claessens's avatar
Xavier Claessens committed
82 83 84 85 86
	gboolean               remove;
} ShowActiveData;

enum {
	PROP_0,
87 88 89
	PROP_STORE,
	PROP_LIST_FEATURES,
	PROP_CONTACT_FEATURES,
Xavier Claessens's avatar
Xavier Claessens committed
90 91 92 93
};

enum DndDragType {
	DND_DRAG_TYPE_CONTACT_ID,
94
	DND_DRAG_TYPE_URI_LIST,
Xavier Claessens's avatar
Xavier Claessens committed
95 96 97 98
	DND_DRAG_TYPE_STRING,
};

static const GtkTargetEntry drag_types_dest[] = {
99
	{ "text/path-list",  0, DND_DRAG_TYPE_URI_LIST },
100
	{ "text/uri-list",   0, DND_DRAG_TYPE_URI_LIST },
Xavier Claessens's avatar
Xavier Claessens committed
101 102 103 104 105
	{ "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
	{ "text/plain",      0, DND_DRAG_TYPE_STRING },
	{ "STRING",          0, DND_DRAG_TYPE_STRING },
};

106
static const GtkTargetEntry drag_types_dest_file[] = {
107
	{ "text/path-list",  0, DND_DRAG_TYPE_URI_LIST },
108 109 110
	{ "text/uri-list",   0, DND_DRAG_TYPE_URI_LIST },
};

Xavier Claessens's avatar
Xavier Claessens committed
111 112 113 114 115 116 117 118 119 120 121
static const GtkTargetEntry drag_types_source[] = {
	{ "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
};

enum {
	DRAG_CONTACT_RECEIVED,
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

122
G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
Xavier Claessens's avatar
Xavier Claessens committed
123

124 125 126 127 128
static void
contact_list_view_tooltip_destroy_cb (GtkWidget              *widget,
				      EmpathyContactListView *view)
{
	EmpathyContactListViewPriv *priv = GET_PRIV (view);
129

130 131
	if (priv->tooltip_widget) {
		DEBUG ("Tooltip destroyed");
132
		g_object_unref (priv->tooltip_widget);
133 134 135 136
		priv->tooltip_widget = NULL;
	}
}

137 138 139 140 141 142 143
static gboolean
contact_list_view_is_visible_contact (EmpathyContactListView *self,
				      EmpathyContact *contact)
{
	EmpathyContactListViewPriv *priv = GET_PRIV (self);
	EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
	const gchar *str;
144 145 146
	const gchar *p;
	gchar *dup_str = NULL;
	gboolean visible;
147

148 149
	g_assert (live != NULL);

150
	/* check alias name */
151
	str = empathy_contact_get_alias (contact);
152 153 154
	if (empathy_live_search_match (live, str))
		return TRUE;

155
	/* check contact id, remove the @server.com part */
156
	str = empathy_contact_get_id (contact);
157 158 159 160 161 162 163
	p = strstr (str, "@");
	if (p != NULL)
		str = dup_str = g_strndup (str, p - str);

	visible = empathy_live_search_match (live, str);
	g_free (dup_str);
	if (visible)
164 165
		return TRUE;

166 167 168
	/* FIXME: Add more rules here, we could check phone numbers in
	 * contact's vCard for example. */

169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
	return FALSE;
}

static gboolean
contact_list_view_filter_visible_func (GtkTreeModel *model,
				       GtkTreeIter  *iter,
				       gpointer      user_data)
{
	EmpathyContactListView     *self = EMPATHY_CONTACT_LIST_VIEW (user_data);
	EmpathyContactListViewPriv *priv = GET_PRIV (self);
	EmpathyContact             *contact = NULL;
	gboolean                    is_group, is_separator, valid;
	GtkTreeIter                 child_iter;
	gboolean                    visible;

184 185
	if (priv->search_widget == NULL ||
	    !gtk_widget_get_visible (priv->search_widget))
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
		return TRUE;

	gtk_tree_model_get (model, iter,
		EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
		EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
		EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
		-1);

	if (contact != NULL) {
		visible = contact_list_view_is_visible_contact (self, contact);
		g_object_unref (contact);
		return visible;
	}

	if (is_separator) {
		return TRUE;
	}

	/* Not a contact, not a separator, must be a group */
	g_return_val_if_fail (is_group, FALSE);

	/* only show groups which are not empty */
	for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
	     valid; valid = gtk_tree_model_iter_next (model, &child_iter)) {
		gtk_tree_model_get (model, &child_iter,
			EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
			-1);

		if (contact == NULL)
			continue;

		visible = contact_list_view_is_visible_contact (self, contact);
		g_object_unref (contact);

		/* show group if it has at least one visible contact in it */
		if (visible)
			return TRUE;
	}

	return FALSE;
}

228 229 230 231 232 233 234 235 236 237 238 239 240
static gboolean
contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
				    gint                    x,
				    gint                    y,
				    gboolean                keyboard_mode,
				    GtkTooltip             *tooltip,
				    gpointer                user_data)
{
	EmpathyContactListViewPriv *priv = GET_PRIV (view);
	EmpathyContact             *contact;
	GtkTreeModel               *model;
	GtkTreeIter                 iter;
	GtkTreePath                *path;
241
	static gint                 running = 0;
242 243 244
	gboolean                    ret = FALSE;

	/* Avoid an infinite loop. See GNOME bug #574377 */
245
	if (running > 0) {
246 247
		return FALSE;
	}
248
	running++;
249

250 251 252 253 254
	/* Don't show the tooltip if there's already a popup menu */
	if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
		goto OUT;
	}

255 256 257
	if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
						keyboard_mode,
						&model, &path, &iter)) {
258
		goto OUT;
259 260 261 262 263 264 265 266 267
	}

	gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
	gtk_tree_path_free (path);

	gtk_tree_model_get (model, &iter,
			    EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
			    -1);
	if (!contact) {
268
		goto OUT;
269 270 271 272
	}

	if (!priv->tooltip_widget) {
		priv->tooltip_widget = empathy_contact_widget_new (contact,
273 274
			EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
			EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
275 276
		gtk_container_set_border_width (
			GTK_CONTAINER (priv->tooltip_widget), 8);
277 278 279 280
		g_object_ref (priv->tooltip_widget);
		g_signal_connect (priv->tooltip_widget, "destroy",
				  G_CALLBACK (contact_list_view_tooltip_destroy_cb),
				  view);
281
		gtk_widget_show (priv->tooltip_widget);
282
	} else {
283 284
		empathy_contact_widget_set_contact (priv->tooltip_widget,
						    contact);
285
	}
286

287
	gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
288
	ret = TRUE;
289 290

	g_object_unref (contact);
291
OUT:
292
	running--;
293

294
	return ret;
295 296
}

297 298 299 300 301 302 303 304 305 306 307 308 309 310
typedef struct {
	gchar *new_group;
	gchar *old_group;
	GdkDragAction action;
} DndGetContactData;

static void
contact_list_view_dnd_get_contact_free (DndGetContactData *data)
{
	g_free (data->new_group);
	g_free (data->old_group);
	g_slice_free (DndGetContactData, data);
}

Xavier Claessens's avatar
Xavier Claessens committed
311
static void
312
contact_list_view_drag_got_contact (TpConnection            *connection,
313 314
				    EmpathyContact          *contact,
				    const GError            *error,
315 316 317 318 319 320 321
				    gpointer                 user_data,
				    GObject                 *view)
{
	EmpathyContactListViewPriv *priv = GET_PRIV (view);
	DndGetContactData          *data = user_data;
	EmpathyContactList         *list;

322 323 324 325
	if (error != NULL) {
		DEBUG ("Error: %s", error->message);
		return;
	}
326 327 328 329 330 331 332

	DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
		empathy_contact_get_id (contact),
		empathy_contact_get_handle (contact),
		data->old_group, data->new_group);

	list = empathy_contact_list_store_get_list_iface (priv->store);
333 334 335 336 337 338 339

	if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
		/* Mark contact as favourite */
		empathy_contact_list_add_to_favourites (list, contact);
		return;
	}

340 341 342 343 344 345 346 347
	if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
		/* Remove contact as favourite */
		empathy_contact_list_remove_from_favourites (list, contact);
		/* Don't try to remove it */
		g_free (data->old_group);
		data->old_group = NULL;
	}

348 349 350
	if (data->new_group) {
		empathy_contact_list_add_to_group (list, contact, data->new_group);
	}
351
	if (data->old_group && data->action == GDK_ACTION_MOVE) {
352 353 354 355
		empathy_contact_list_remove_from_group (list, contact, data->old_group);
	}
}

356 357
static gboolean
group_can_be_modified (const gchar *name,
358 359
		       gboolean     is_fake_group,
		       gboolean     adding)
360 361 362 363 364
{
	/* Real groups can always be modified */
	if (!is_fake_group)
		return TRUE;

365
	/* The favorite fake group can be modified so users can
366 367 368 369
	 * add/remove favorites using DnD */
	if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
		return TRUE;

370 371 372 373
	/* We can remove contacts from the 'ungrouped' fake group */
	if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
		return TRUE;

374 375 376
	return FALSE;
}

377 378 379 380 381 382 383 384 385
static gboolean
contact_list_view_contact_drag_received (GtkWidget         *view,
					 GdkDragContext    *context,
					 GtkTreeModel      *model,
					 GtkTreePath       *path,
					 GtkSelectionData  *selection)
{
	EmpathyContactListViewPriv *priv;
	TpAccountManager           *account_manager;
Xavier Claessens's avatar
Xavier Claessens committed
386 387
	TpConnection               *connection = NULL;
	TpAccount                  *account = NULL;
388 389 390 391 392 393 394 395
	DndGetContactData          *data;
	GtkTreePath                *source_path;
	const gchar   *sel_data;
	gchar        **strv = NULL;
	const gchar   *account_id = NULL;
	const gchar   *contact_id = NULL;
	gchar         *new_group = NULL;
	gchar         *old_group = NULL;
396
	gboolean       new_group_is_fake, old_group_is_fake = TRUE;
397 398 399 400 401

	priv = GET_PRIV (view);

	sel_data = (const gchar *) gtk_selection_data_get_data (selection);
	new_group = empathy_contact_list_store_get_parent_group (model,
402
								 path, NULL, &new_group_is_fake);
403

404
	if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
405
		return FALSE;
406 407 408 409 410 411

	/* Get source group information. */
	if (priv->drag_row) {
		source_path = gtk_tree_row_reference_get_path (priv->drag_row);
		if (source_path) {
			old_group = empathy_contact_list_store_get_parent_group (
412
										 model, source_path, NULL, &old_group_is_fake);
413 414 415 416
			gtk_tree_path_free (source_path);
		}
	}

417 418 419
	if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
		return FALSE;

420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
	if (!tp_strdiff (old_group, new_group)) {
		g_free (new_group);
		g_free (old_group);
		return FALSE;
	}

	account_manager = tp_account_manager_dup ();
	strv = g_strsplit (sel_data, ":", 2);
	if (g_strv_length (strv) == 2) {
		account_id = strv[0];
		contact_id = strv[1];
		account = tp_account_manager_ensure_account (account_manager, account_id);
	}
	if (account) {
		connection = tp_account_get_connection (account);
	}

437 438
	if (!connection) {
		DEBUG ("Failed to get connection for account '%s'", account_id);
439 440
		g_free (new_group);
		g_free (old_group);
441
		g_object_unref (account_manager);
442 443 444 445 446 447
		return FALSE;
	}

	data = g_slice_new0 (DndGetContactData);
	data->new_group = new_group;
	data->old_group = old_group;
448
	data->action = gdk_drag_context_get_selected_action (context);
449 450 451

	/* FIXME: We should probably wait for the cb before calling
	 * gtk_drag_finish */
452
	empathy_tp_contact_factory_get_from_id (connection, contact_id,
453 454 455 456
						contact_list_view_drag_got_contact,
						data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
						G_OBJECT (view));
	g_strfreev (strv);
457
	g_object_unref (account_manager);
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482

	return TRUE;
}

static gboolean
contact_list_view_file_drag_received (GtkWidget         *view,
				      GdkDragContext    *context,
				      GtkTreeModel      *model,
				      GtkTreePath       *path,
				      GtkSelectionData  *selection)
{
	GtkTreeIter     iter;
	const gchar    *sel_data;
	EmpathyContact *contact;

	sel_data = (const gchar *) gtk_selection_data_get_data (selection);

	gtk_tree_model_get_iter (model, &iter, path);
	gtk_tree_model_get (model, &iter,
			    EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
			    -1);
	if (!contact) {
		return FALSE;
	}

483
	empathy_send_file_from_uri_list (contact, sel_data);
484

485
	g_object_unref (contact);
486 487 488 489

	return TRUE;
}

490 491
static void
contact_list_view_drag_data_received (GtkWidget         *view,
492 493 494 495 496
				      GdkDragContext    *context,
				      gint               x,
				      gint               y,
				      GtkSelectionData  *selection,
				      guint              info,
497
				      guint              time_)
Xavier Claessens's avatar
Xavier Claessens committed
498
{
499 500
	GtkTreeModel               *model;
	gboolean                    is_row;
501 502
	GtkTreeViewDropPosition     position;
	GtkTreePath                *path;
503
	gboolean                    success = TRUE;
504

505
	model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
506

507 508 509 510 511 512
	/* Get destination group information. */
	is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
						    x,
						    y,
						    &path,
						    &position);
513 514 515
	if (!is_row) {
		success = FALSE;
	}
516 517 518 519 520 521
	else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
		success = contact_list_view_contact_drag_received (view,
								   context,
								   model,
								   path,
								   selection);
522
	}
523
	else if (info == DND_DRAG_TYPE_URI_LIST) {
524 525 526 527 528
		success = contact_list_view_file_drag_received (view,
								context,
								model,
								path,
								selection);
529
	}
530

531
	gtk_tree_path_free (path);
532
	gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
Xavier Claessens's avatar
Xavier Claessens committed
533 534
}

535 536
static gboolean
contact_list_view_drag_motion_cb (DragMotionData *data)
Xavier Claessens's avatar
Xavier Claessens committed
537
{
538 539 540
	gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
				  data->path,
				  FALSE);
Xavier Claessens's avatar
Xavier Claessens committed
541

542
	data->timeout_id = 0;
Xavier Claessens's avatar
Xavier Claessens committed
543

544
	return FALSE;
Xavier Claessens's avatar
Xavier Claessens committed
545 546
}

547 548 549 550 551
static gboolean
contact_list_view_drag_motion (GtkWidget      *widget,
			       GdkDragContext *context,
			       gint            x,
			       gint            y,
552
			       guint           time_)
Xavier Claessens's avatar
Xavier Claessens committed
553
{
554 555 556 557
	EmpathyContactListViewPriv *priv;
	GtkTreeModel               *model;
	GdkAtom                target;
	GtkTreeIter            iter;
558 559 560 561 562
	static DragMotionData *dm = NULL;
	GtkTreePath           *path;
	gboolean               is_row;
	gboolean               is_different = FALSE;
	gboolean               cleanup = TRUE;
563 564 565 566 567
	gboolean               retval = TRUE;

	priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
	model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));

568 569 570 571 572 573 574
	is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
						x,
						y,
						&path,
						NULL,
						NULL,
						NULL);
Xavier Claessens's avatar
Xavier Claessens committed
575

576
	cleanup &= (!dm);
Xavier Claessens's avatar
Xavier Claessens committed
577

578 579 580 581 582 583
	if (is_row) {
		cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
		is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
	} else {
		cleanup &= FALSE;
	}
584

585
	if (path == NULL) {
586 587 588 589
		/* Coordinates don't point to an actual row, so make sure the pointer
		   and highlighting don't indicate that a drag is possible.
		 */
		gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
590 591 592
		gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
		return FALSE;
	}
593
	target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
594 595 596
	gtk_tree_model_get_iter (model, &iter, path);

	if (target == GDK_NONE) {
597 598 599 600 601 602 603
		/* If target == GDK_NONE, then we don't have a target that can be
		   dropped on a contact.  This means a contact drag.  If we're
		   pointing to a group, highlight it.  Otherwise, if the contact
		   we're pointing to is in a group, highlight that.  Otherwise,
		   set the drag position to before the first row for a drag into
		   the "non-group" at the top.
		 */
604 605 606
		GtkTreeIter  group_iter;
		gboolean     is_group;
		GtkTreePath *group_path;
607 608 609
		gtk_tree_model_get (model, &iter,
				    EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
				    -1);
610 611 612 613 614 615 616 617 618
		if (is_group) {
			group_iter = iter;
		}
		else {
			if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
				gtk_tree_model_get (model, &group_iter,
						    EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
						    -1);
		}
619 620
		if (is_group) {
			gdk_drag_status (context, GDK_ACTION_MOVE, time_);
621
			group_path = gtk_tree_model_get_path (model, &group_iter);
622
			gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
623
							 group_path,
624
							 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
625
			gtk_tree_path_free (group_path);
626 627
		}
		else {
628 629 630 631 632
			group_path = gtk_tree_path_new_first ();
			gdk_drag_status (context, GDK_ACTION_MOVE, time_);
			gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
							 group_path,
							 GTK_TREE_VIEW_DROP_BEFORE);
633 634 635
		}
	}
	else {
636 637 638
		/* This is a file drag, and it can only be dropped on contacts,
		   not groups.
		 */
639 640 641 642
		EmpathyContact *contact;
		gtk_tree_model_get (model, &iter,
				    EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
				    -1);
643 644 645
		if (contact != NULL &&
		    empathy_contact_is_online (contact) &&
		    (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
646 647 648 649
			gdk_drag_status (context, GDK_ACTION_COPY, time_);
			gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
							 path,
							 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
650
			g_object_unref (contact);
651 652 653 654 655 656
		}
		else {
			gdk_drag_status (context, 0, time_);
			gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
			retval = FALSE;
		}
657 658
	}

659
	if (!is_different && !cleanup) {
660
		return retval;
661
	}
662

663 664 665 666 667
	if (dm) {
		gtk_tree_path_free (dm->path);
		if (dm->timeout_id) {
			g_source_remove (dm->timeout_id);
		}
668

669
		g_free (dm);
670

671
		dm = NULL;
672 673
	}

674 675
	if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
		dm = g_new0 (DragMotionData, 1);
676

677 678
		dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
		dm->path = gtk_tree_path_copy (path);
679

680 681 682 683
		dm->timeout_id = g_timeout_add_seconds (1,
			(GSourceFunc) contact_list_view_drag_motion_cb,
			dm);
	}
684

685
	return retval;
686 687
}

688 689 690
static void
contact_list_view_drag_begin (GtkWidget      *widget,
			      GdkDragContext *context)
Xavier Claessens's avatar
Xavier Claessens committed
691
{
692
	EmpathyContactListViewPriv *priv;
Xavier Claessens's avatar
Xavier Claessens committed
693 694
	GtkTreeSelection          *selection;
	GtkTreeModel              *model;
695 696
	GtkTreePath               *path;
	GtkTreeIter                iter;
Xavier Claessens's avatar
Xavier Claessens committed
697

698
	priv = GET_PRIV (widget);
Xavier Claessens's avatar
Xavier Claessens committed
699

700 701
	GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
									      context);
Xavier Claessens's avatar
Xavier Claessens committed
702

703
	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
Xavier Claessens's avatar
Xavier Claessens committed
704
	if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
705
		return;
Xavier Claessens's avatar
Xavier Claessens committed
706 707
	}

708 709 710
	path = gtk_tree_model_get_path (model, &iter);
	priv->drag_row = gtk_tree_row_reference_new (model, path);
	gtk_tree_path_free (path);
Xavier Claessens's avatar
Xavier Claessens committed
711 712
}

713 714 715 716 717
static void
contact_list_view_drag_data_get (GtkWidget        *widget,
				 GdkDragContext   *context,
				 GtkSelectionData *selection,
				 guint             info,
718
				 guint             time_)
Xavier Claessens's avatar
Xavier Claessens committed
719
{
720
	EmpathyContactListViewPriv *priv;
721 722 723 724
	GtkTreePath                *src_path;
	GtkTreeIter                 iter;
	GtkTreeModel               *model;
	EmpathyContact             *contact;
725
	TpAccount                  *account;
726 727 728
	const gchar                *contact_id;
	const gchar                *account_id;
	gchar                      *str;
Xavier Claessens's avatar
Xavier Claessens committed
729

730
	priv = GET_PRIV (widget);
Xavier Claessens's avatar
Xavier Claessens committed
731

732 733 734 735
	model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
	if (!priv->drag_row) {
		return;
	}
Xavier Claessens's avatar
Xavier Claessens committed
736

737 738 739
	src_path = gtk_tree_row_reference_get_path (priv->drag_row);
	if (!src_path) {
		return;
Xavier Claessens's avatar
Xavier Claessens committed
740 741
	}

742 743 744 745
	if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
		gtk_tree_path_free (src_path);
		return;
	}
Xavier Claessens's avatar
Xavier Claessens committed
746

747 748
	gtk_tree_path_free (src_path);

749
	contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
750 751
	if (!contact) {
		return;
Xavier Claessens's avatar
Xavier Claessens committed
752 753
	}

754
	account = empathy_contact_get_account (contact);
755
	account_id = tp_proxy_get_object_path (account);
756 757
	contact_id = empathy_contact_get_id (contact);
	g_object_unref (contact);
758
	str = g_strconcat (account_id, ":", contact_id, NULL);
759

760
	if (info == DND_DRAG_TYPE_CONTACT_ID) {
761 762
		gtk_selection_data_set (selection,
					gdk_atom_intern ("text/contact-id", FALSE), 8,
763
					(guchar *) str, strlen (str) + 1);
764 765 766
	}

	g_free (str);
Xavier Claessens's avatar
Xavier Claessens committed
767 768 769
}

static void
770 771
contact_list_view_drag_end (GtkWidget      *widget,
			    GdkDragContext *context)
Xavier Claessens's avatar
Xavier Claessens committed
772
{
773
	EmpathyContactListViewPriv *priv;
Xavier Claessens's avatar
Xavier Claessens committed
774

775
	priv = GET_PRIV (widget);
Xavier Claessens's avatar
Xavier Claessens committed
776

777 778
	GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
									    context);
779

780 781 782 783 784
	if (priv->drag_row) {
		gtk_tree_row_reference_free (priv->drag_row);
		priv->drag_row = NULL;
	}
}
Xavier Claessens's avatar
Xavier Claessens committed
785

786 787 788 789 790
static gboolean
contact_list_view_drag_drop (GtkWidget      *widget,
			     GdkDragContext *drag_context,
			     gint            x,
			     gint            y,
791
			     guint           time_)
792 793 794
{
	return FALSE;
}
Xavier Claessens's avatar
Xavier Claessens committed
795

796 797 798 799 800 801
typedef struct {
	EmpathyContactListView *view;
	guint                   button;
	guint32                 time;
} MenuPopupData;

802 803 804 805 806 807 808
static void
menu_deactivate_cb (GtkMenuShell *menushell,
		    gpointer user_data)
{
	/* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
	g_signal_handlers_disconnect_by_func (menushell,
		menu_deactivate_cb, user_data);
809 810

	gtk_menu_detach (GTK_MENU (menushell));
811 812
}

813
static gboolean
814
contact_list_view_popup_menu_idle_cb (gpointer user_data)
815
{
816 817
	MenuPopupData *data = user_data;
	GtkWidget     *menu;
818

819 820 821
	menu = empathy_contact_list_view_get_contact_menu (data->view);
	if (!menu) {
		menu = empathy_contact_list_view_get_group_menu (data->view);
822
	}
Xavier Claessens's avatar
Xavier Claessens committed
823

Xavier Claessens's avatar
Xavier Claessens committed
824
	if (menu) {
825 826
		gtk_menu_attach_to_widget (GTK_MENU (menu),
					   GTK_WIDGET (data->view), NULL);
Xavier Claessens's avatar
Xavier Claessens committed
827 828 829 830
		gtk_widget_show (menu);
		gtk_menu_popup (GTK_MENU (menu),
				NULL, NULL, NULL, NULL,
				data->button, data->time);
831

Guillaume Desmottes's avatar
Guillaume Desmottes committed
832
		/* menu is initially unowned but gtk_menu_attach_to_widget () taked its
833 834 835 836 837 838 839
		 * floating ref. We can either wait that the treeview releases its ref
		 * when it will be destroyed (when leaving Empathy) or explicitely
		 * detach the menu when it's not displayed any more.
		 * We go for the latter as we don't want to keep useless menus in memory
		 * during the whole lifetime of Empathy. */
		g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
			NULL);
840
	}
Xavier Claessens's avatar
Xavier Claessens committed
841

842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
	g_slice_free (MenuPopupData, data);

	return FALSE;
}

static gboolean
contact_list_view_button_press_event_cb (EmpathyContactListView *view,
					 GdkEventButton         *event,
					 gpointer                user_data)
{
	if (event->button == 3) {
		MenuPopupData *data;

		data = g_slice_new (MenuPopupData);
		data->view = view;
		data->button = event->button;
		data->time = event->time;
		g_idle_add (contact_list_view_popup_menu_idle_cb, data);
	}

	return FALSE;
Xavier Claessens's avatar
Xavier Claessens committed
863 864
}

865 866 867 868 869
static gboolean
contact_list_view_key_press_event_cb (EmpathyContactListView *view,
				      GdkEventKey	     *event,
				      gpointer		      user_data)
{
870
	if (event->keyval == GDK_KEY_Menu) {
871
		MenuPopupData *data;
872

873 874 875 876 877
		data = g_slice_new (MenuPopupData);
		data->view = view;
		data->button = 0;
		data->time = event->time;
		g_idle_add (contact_list_view_popup_menu_idle_cb, data);
878 879 880 881 882
	}

	return FALSE;
}

Xavier Claessens's avatar
Xavier Claessens committed
883
static void
884 885 886
contact_list_view_row_activated (GtkTreeView       *view,
				 GtkTreePath       *path,
				 GtkTreeViewColumn *column)
Xavier Claessens's avatar
Xavier Claessens committed
887
{
888
	EmpathyContactListViewPriv *priv = GET_PRIV (view);
889 890 891
	EmpathyContact             *contact;
	GtkTreeModel               *model;
	GtkTreeIter                 iter;
Xavier Claessens's avatar
Xavier Claessens committed
892

893 894 895 896
	if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
		return;
	}

897
	model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
898 899 900 901 902
	gtk_tree_model_get_iter (model, &iter, path);
	gtk_tree_model_get (model, &iter,
			    EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
			    -1);

903
	if (contact) {
904
		DEBUG ("Starting a chat");
905
		empathy_chat_with_contact (contact,
906
			empathy_get_current_action_time ());
907
		g_object_unref (contact);
908
	}
Xavier Claessens's avatar
Xavier Claessens committed
909 910 911
}

static void
912 913
contact_list_view_call_activated_cb (
    EmpathyCellRendererActivatable *cell,
914
    const gchar                    *path_string,
915
    EmpathyContactListView         *view)
Xavier Claessens's avatar
Xavier Claessens committed
916
{
917 918 919 920 921 922 923
	GtkWidget *menu;
	GtkTreeModel *model;
	GtkTreeIter iter;
	EmpathyContact *contact;
	GdkEventButton *event;
	GtkMenuShell *shell;
	GtkWidget *item;
Xavier Claessens's avatar
Xavier Claessens committed
924

925
	model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
926
	if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
927
		return;
Xavier Claessens's avatar
Xavier Claessens committed
928

929 930 931
	gtk_tree_model_get (model, &iter,
			    EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
			    -1);
932 933
	if (contact == NULL)
		return;
Xavier Claessens's avatar
Xavier Claessens committed
934

935
	event = (GdkEventButton *) gtk_get_current_event ();
Xavier Claessens's avatar
Xavier Claessens committed
936

937
	menu = empathy_context_menu_new (GTK_WIDGET (view));
938
	shell = GTK_MENU_SHELL (menu);
939

940 941 942 943
	/* audio */
	item = empathy_contact_audio_call_menu_item_new (contact);
	gtk_menu_shell_append (shell, item);
	gtk_widget_show (item);
944

945 946 947 948 949 950 951 952 953 954
	/* video */
	item = empathy_contact_video_call_menu_item_new (contact);
	gtk_menu_shell_append (shell, item);
	gtk_widget_show (item);

	gtk_widget_show (menu);
	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
			event->button, event->time);

	g_object_unref (contact);
955 956
}

957 958
static void
contact_list_view_cell_set_background (EmpathyContactListView *view,
959 960 961
				       GtkCellRenderer        *cell,
				       gboolean                is_group,
				       gboolean                is_active)
Xavier Claessens's avatar
Xavier Claessens committed
962
{
963 964 965
	if (!is_group && is_active) {
		GdkRGBA color;
		GtkStyleContext *style;
Xavier Claessens's avatar
Xavier Claessens committed
966

967
		style = gtk_widget_get_style_context (GTK_WIDGET (view));
Xavier Claessens's avatar
Xavier Claessens committed
968

969 970
		gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
        &color);
Xavier Claessens's avatar
Xavier Claessens committed
971

972 973 974 975 976
		/* 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.
		 */
977
		empathy_make_color_whiter (&color);
Xavier Claessens's avatar
Xavier Claessens committed
978

979
		g_object_set (cell,
980
			      "cell-background-rgba", &color,
981 982 983
			      NULL);
	} else {
		g_object_set (cell,
984
			      "cell-background-rgba", NULL,
985
			      NULL);
Xavier Claessens's avatar
Xavier Claessens committed
986
	}
987
}
Xavier Claessens's avatar
Xavier Claessens committed
988

989
static void
990 991 992 993
contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn      *tree_column,
					 GtkCellRenderer        *cell,
					 GtkTreeModel           *model,
					 GtkTreeIter            *iter,
994 995
					 EmpathyContactListView *view)
{
996 997 998
	GdkPixbuf *pixbuf;
	gboolean   is_group;
	gboolean   is_active;
Xavier Claessens's avatar
Xavier Claessens committed
999

1000 1001 1002
	gtk_tree_model_get (model, iter,
			    EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
			    EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1003
			    EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
1004
			    -1);
Xavier Claessens's avatar
Xavier Claessens committed
1005

1006 1007
	g_object_set (cell,
		      "visible", !is_group,
1008
		      "pixbuf", pixbuf,
1009
		      NULL);
Xavier Claessens's avatar
Xavier Claessens committed
1010

1011 1012 1013
	if (pixbuf != NULL) {
		g_object_unref (pixbuf);
	}
Xavier Claessens's avatar
Xavier Claessens committed
1014

1015 1016
	contact_list_view_cell_set_background (view, cell, is_group, is_active);
}
Xavier Claessens's avatar
Xavier Claessens committed
1017

1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034
static void
contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn     *tree_column,
					     GtkCellRenderer       *cell,
					     GtkTreeModel          *model,
					     GtkTreeIter           *iter,
					     EmpathyContactListView *view)
{
	GdkPixbuf *pixbuf = NULL;
	gboolean is_group;
	gchar *name;

	gtk_tree_model_get (model, iter,
			    EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
			    EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
			    -1);

	if (!is_group)
Guillaume Desmottes's avatar
Guillaume Desmottes committed
1035
		goto out;
1036

1037 1038 1039 1040 1041 1042 1043 1044
	if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
		pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
			GTK_ICON_SIZE_MENU);
	}
	else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
		pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
			GTK_ICON_SIZE_MENU);
	}
1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057

out:
	g_object_set (cell,
		      "visible", pixbuf != NULL,
		      "pixbuf", pixbuf,
		      NULL);

	if (pixbuf != NULL)
		g_object_unref (pixbuf);

	g_free (name);
}

Xavier Claessens's avatar
Xavier Claessens committed
1058
static void
1059 1060
contact_list_view_audio_call_cell_data_func (
				       GtkTreeViewColumn      *tree_column,
1061 1062 1063 1064
				       GtkCellRenderer        *cell,
				       GtkTreeModel           *model,
				       GtkTreeIter            *iter,
				       EmpathyContactListView *view)
Xavier Claessens's avatar
Xavier Claessens committed
1065
{
1066 1067
	gboolean is_group;
	gboolean is_active;
1068
	gboolean can_audio, can_video;
Xavier Claessens's avatar
Xavier Claessens committed
1069

1070 1071 1072
	gtk_tree_model_get (model, iter,
			    EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
			    EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1073 1074
			    EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
			    EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
1075
			    -1);
1076

1077
	g_object_set (cell,
1078 1079
		      "visible", !is_group && (can_audio || can_video),
		      "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1080
		      NULL);
Xavier Claessens's avatar
Xavier Claessens committed
1081

1082
	contact_list_view_cell_set_background (view, cell, is_group, is_active);
Xavier Claessens's avatar
Xavier Claessens committed
1083 1084 1085
}

static void
1086 1087 1088 1089
contact_list_view_avatar_cell_data_func (GtkTreeViewColumn      *tree_column,
					 GtkCellRenderer        *cell,
					 GtkTreeModel           *model,
					 GtkTreeIter            *iter,
1090
					 EmpathyContactListView *view)
Xavier Claessens's avatar
Xavier Claessens committed
1091
{
1092 1093 1094 1095
	GdkPixbuf *pixbuf;
	gboolean   show_avatar;
	gboolean   is_group;
	gboolean   is_active;
Xavier Claessens's avatar
Xavier Claessens committed
1096

1097 1098 1099 1100 1101 1102
	gtk_tree_model_get (model, iter,
			    EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
			    EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
			    EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
			    EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
			    -1);
Xavier Claessens's avatar
Xavier Claessens committed
1103

1104 1105 1106 1107
	g_object_set (cell,
		      "visible", !is_group && show_avatar,
		      "pixbuf", pixbuf,
		      NULL);
Xavier Claessens's avatar
Xavier Claessens committed
1108

1109 1110
	if (pixbuf) {
		g_object_unref (pixbuf);
Xavier Claessens's avatar
Xavier Claessens committed
1111
	}
1112

1113
	contact_list_view_cell_set_background (view, cell, is_group, is_active);
Xavier Claessens's avatar
Xavier Claessens committed
1114 1115 1116
}

static void
1117 1118 1119 1120
contact_list_view_text_cell_data_func (GtkTreeViewColumn      *tree_column,
				       GtkCellRenderer        *cell,
				       GtkTreeModel           *model,
				       GtkTreeIter            *iter,
1121
				       EmpathyContactListView *view)
Xavier Claessens's avatar
Xavier Claessens committed
1122
{
1123 1124
	gboolean is_group;
	gboolean is_active;
Xavier Claessens's avatar
Xavier Claessens committed
1125

1126 1127 1128 1129
	gtk_tree_model_get (model, iter,
			    EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
			    EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
			    -1);
1130

1131
	contact_list_view_cell_set_background (view, cell, is_group, is_active);
1132 1133
}

Xavier Claessens's avatar
Xavier Claessens committed
1134
static void
1135 1136 1137 1138
contact_list_view_expander_cell_data_func (GtkTreeViewColumn      *column,
					   GtkCellRenderer        *cell,
					   GtkTreeModel           *model,
					   GtkTreeIter            *iter,
1139
					   EmpathyContactListView *view)
Xavier Claessens's avatar
Xavier Claessens committed
1140
{
1141 1142
	gboolean is_group;
	gboolean is_active;
Xavier Claessens's avatar
Xavier Claessens committed
1143

1144 1145 1146 1147
	gtk_tree_model_get (model, iter,
			    EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
			    EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
			    -1);
Xavier Claessens's avatar
Xavier Claessens committed
1148

1149 1150 1151
	if (gtk_tree_model_iter_has_child (model, iter)) {
		GtkTreePath *path;
		gboolean     row_expanded;
1152

1153
		path = gtk_tree_model_get_path (model, iter);
1154
		row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1155
		gtk_tree_path_free (path);
1156 1157

		g_object_set (cell,
1158 1159
			      "visible", TRUE,
			      "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1160
			      NULL);
Xavier Claessens's avatar
Xavier Claessens committed
1161
	} else {
1162
		g_object_set (cell, "visible", FALSE, NULL);
Xavier Claessens's avatar
Xavier Claessens committed
1163
	}
1164 1165

	contact_list_view_cell_set_background (view, cell, is_group, is_active);
Xavier Claessens's avatar
Xavier Claessens committed
1166 1167 1168
}

static void
1169
contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1170 1171 1172
					     GtkTreeIter            *iter,
					     GtkTreePath            *path,
					     gpointer                user_data)
Xavier Claessens's avatar
Xavier Claessens committed
1173
{
1174 1175 1176 1177 1178
	EmpathyContactListViewPriv *priv = GET_PRIV (view);
	GtkTreeModel               *model;
	gchar                      *name;
	gboolean                    expanded;

1179
	if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1180 1181 1182 1183
		return;
	}

	model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
Xavier Claessens's avatar
Xavier Claessens committed
1184 1185

	gtk_tree_model_get (model, iter,
1186
			    EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
Xavier Claessens's avatar
Xavier Claessens committed
1187 1188
			    -1);

1189 1190
	expanded = GPOINTER_TO_INT (user_data);
	empathy_contact_group_set_expanded (name, expanded);
Xavier Claessens's avatar
Xavier Claessens committed
1191

1192
	g_free (name);
Xavier Claessens's avatar
Xavier Claessens committed
1193 1194
}

1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217
static gboolean
contact_list_view_start_search_cb (EmpathyContactListView *view,
				   gpointer                data)
{
	EmpathyContactListViewPriv *priv = GET_PRIV (view);

	if (priv->search_widget == NULL)
		return FALSE;

	if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
		gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
	else
		gtk_widget_show (GTK_WIDGET (priv->search_widget));

	return TRUE;
}

static void
contact_list_view_search_text_notify_cb (EmpathyLiveSearch      *search,
					 GParamSpec             *pspec,
					 EmpathyContactListView *view)
{
	EmpathyContactListViewPriv *priv = GET_PRIV (view);
1218 1219 1220 1221 1222
	GtkTreePath *path;
	GtkTreeViewColumn *focus_column;
	GtkTreeModel *model;
	GtkTreeIter iter;
	gboolean set_cursor = FALSE;
1223 1224

	gtk_tree_model_filter_refilter (priv->filter);
1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269

	/* 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,
			EMPATHY_CONTACT_LIST_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) {
		/* FIXME: Workaround for GTK bug #621651, we have to 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
contact_list_view_search_activate_cb (GtkWidget *search,
				      EmpathyContactListView *view)
{
	GtkTreePath *path;
	GtkTreeViewColumn *focus_column;
1270

1271 1272 1273 1274 1275 1276 1277 1278
	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);
	}
1279 1280
}

1281
static gboolean
1282
contact_list_view_search_key_navigation_cb (GtkWidget *search,
1283
					    GdkEvent *event,
1284 1285
					    EmpathyContactListView *view)
{
1286 1287 1288
	GdkEventKey *eventkey = ((GdkEventKey *) event);
	gboolean ret = FALSE;

1289
	if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down) {
1290 1291
		GdkEvent *new_event;

1292
		new_event = gdk_event_copy (event);
1293
		gtk_widget_grab_focus (GTK_WIDGET (view));
1294
		ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1295 1296 1297 1298
		gtk_widget_grab_focus (search);

		gdk_event_free (new_event);
	}
1299 1300

	return ret;
1301 1302
}

1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347
static void
contact_list_view_search_hide_cb (EmpathyLiveSearch      *search,
				  EmpathyContactListView *view)
{
	EmpathyContactListViewPriv *priv = GET_PRIV (view);
	GtkTreeModel               *model;
	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,
		contact_list_view_row_expand_or_collapse_cb,
		GINT_TO_POINTER (TRUE));
	g_signal_handlers_block_by_func (view,
		contact_list_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,
			EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
			EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
			-1);

		if (!is_group) {
			g_free (name);
			continue;
		}

		path = gtk_tree_model_get_path (model, &iter);
		if ((priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
		    empathy_contact_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);
		}

Guillaume Desmottes's avatar
Guillaume Desmottes committed
1348
		gtk_tree_path_free (path);
1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379
		g_free (name);
	}

	/* unblock expand or collapse handlers */
	g_signal_handlers_unblock_by_func (view,
		contact_list_view_row_expand_or_collapse_cb,
		GINT_TO_POINTER (TRUE));
	g_signal_handlers_unblock_by_func (view,
		contact_list_view_row_expand_or_collapse_cb,
		GINT_TO_POINTER (FALSE));
}

static void
contact_list_view_search_show_cb (EmpathyLiveSearch      *search,
				  EmpathyContactListView *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,
		contact_list_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,
		contact_list_view_row_expand_or_collapse_cb,
		GINT_TO_POINTER (TRUE));
}

typedef struct {
	EmpathyContactListView *view;
1380
	GtkTreeRowReference *row_ref;
1381 1382 1383 1384 1385 1386 1387
	gboolean expand;
} ExpandData;

static gboolean
contact_list_view_expand_idle_cb (gpointer user_data)
{
	ExpandData *data = user_data;
1388
	GtkTreePath *path;
1389

1390
	path = gtk_tree_row_reference_get_path (data->row_ref);
1391 1392 1393
	if (path == NULL)
		goto done;

1394 1395 1396 1397
	g_signal_handlers_block_by_func (data->view,
		contact_list_view_row_expand_or_collapse_cb,
		GINT_TO_POINTER (data->expand));

1398
	if (data->expand) {
1399 1400
		gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path,
		    TRUE);
1401
	} else {
1402
		gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1403
	}
1404
	gtk_tree_path_free (path);
1405 1406 1407 1408 1409

	g_signal_handlers_unblock_by_func (data->view,
		contact_list_view_row_expand_or_collapse_cb,
		GINT_TO_POINTER (data->expand));