Rebase the favourites support upon the telepathy-logger instead of a specially-named...
[empathy-mirror.git] / libempathy-gtk / empathy-contact-list-view.c
blobb95f055802a5185c91b385577bc9af76c21b8a9e
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
26 #include "config.h"
28 #include <string.h>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <gtk/gtk.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <extensions/extensions.h>
38 #include <libempathy/empathy-call-factory.h>
39 #include <libempathy/empathy-tp-contact-factory.h>
40 #include <libempathy/empathy-contact-list.h>
41 #include <libempathy/empathy-contact-groups.h>
42 #include <libempathy/empathy-dispatcher.h>
43 #include <libempathy/empathy-utils.h>
45 #include "empathy-contact-list-view.h"
46 #include "empathy-contact-list-store.h"
47 #include "empathy-images.h"
48 #include "empathy-cell-renderer-expander.h"
49 #include "empathy-cell-renderer-text.h"
50 #include "empathy-cell-renderer-activatable.h"
51 #include "empathy-ui-utils.h"
52 #include "empathy-gtk-enum-types.h"
53 #include "empathy-gtk-marshal.h"
55 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
56 #include <libempathy/empathy-debug.h>
58 /* Active users are those which have recently changed state
59 * (e.g. online, offline or from normal to a busy state).
62 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 typedef struct {
64 EmpathyContactListStore *store;
65 GtkTreeRowReference *drag_row;
66 EmpathyContactListFeatureFlags list_features;
67 EmpathyContactFeatureFlags contact_features;
68 GtkWidget *tooltip_widget;
69 GtkTargetList *file_targets;
70 } EmpathyContactListViewPriv;
72 typedef struct {
73 EmpathyContactListView *view;
74 GtkTreePath *path;
75 guint timeout_id;
76 } DragMotionData;
78 typedef struct {
79 EmpathyContactListView *view;
80 EmpathyContact *contact;
81 gboolean remove;
82 } ShowActiveData;
84 enum {
85 PROP_0,
86 PROP_STORE,
87 PROP_LIST_FEATURES,
88 PROP_CONTACT_FEATURES,
91 enum DndDragType {
92 DND_DRAG_TYPE_CONTACT_ID,
93 DND_DRAG_TYPE_URI_LIST,
94 DND_DRAG_TYPE_STRING,
97 static const GtkTargetEntry drag_types_dest[] = {
98 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
99 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
100 { "text/plain", 0, DND_DRAG_TYPE_STRING },
101 { "STRING", 0, DND_DRAG_TYPE_STRING },
104 static const GtkTargetEntry drag_types_dest_file[] = {
105 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
108 static const GtkTargetEntry drag_types_source[] = {
109 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
112 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
113 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
115 enum {
116 DRAG_CONTACT_RECEIVED,
117 LAST_SIGNAL
120 static guint signals[LAST_SIGNAL];
122 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
124 static void
125 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
126 EmpathyContactListView *view)
128 EmpathyContactListViewPriv *priv = GET_PRIV (view);
130 if (priv->tooltip_widget) {
131 DEBUG ("Tooltip destroyed");
132 g_object_unref (priv->tooltip_widget);
133 priv->tooltip_widget = NULL;
137 static gboolean
138 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
139 gint x,
140 gint y,
141 gboolean keyboard_mode,
142 GtkTooltip *tooltip,
143 gpointer user_data)
145 EmpathyContactListViewPriv *priv = GET_PRIV (view);
146 EmpathyContact *contact;
147 GtkTreeModel *model;
148 GtkTreeIter iter;
149 GtkTreePath *path;
150 static gint running = 0;
151 gboolean ret = FALSE;
153 /* Avoid an infinite loop. See GNOME bug #574377 */
154 if (running > 0) {
155 return FALSE;
157 running++;
159 /* Don't show the tooltip if there's already a popup menu */
160 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
161 goto OUT;
164 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
165 keyboard_mode,
166 &model, &path, &iter)) {
167 goto OUT;
170 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
171 gtk_tree_path_free (path);
173 gtk_tree_model_get (model, &iter,
174 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
175 -1);
176 if (!contact) {
177 goto OUT;
180 if (!priv->tooltip_widget) {
181 priv->tooltip_widget = empathy_contact_widget_new (contact,
182 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
183 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
184 gtk_container_set_border_width (
185 GTK_CONTAINER (priv->tooltip_widget), 8);
186 g_object_ref (priv->tooltip_widget);
187 g_signal_connect (priv->tooltip_widget, "destroy",
188 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
189 view);
190 gtk_widget_show (priv->tooltip_widget);
191 } else {
192 empathy_contact_widget_set_contact (priv->tooltip_widget,
193 contact);
196 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
197 ret = TRUE;
199 g_object_unref (contact);
200 OUT:
201 running--;
203 return ret;
206 typedef struct {
207 gchar *new_group;
208 gchar *old_group;
209 GdkDragAction action;
210 } DndGetContactData;
212 static void
213 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
215 g_free (data->new_group);
216 g_free (data->old_group);
217 g_slice_free (DndGetContactData, data);
220 static void
221 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
222 EmpathyContact *contact,
223 const GError *error,
224 gpointer user_data,
225 GObject *view)
227 EmpathyContactListViewPriv *priv = GET_PRIV (view);
228 DndGetContactData *data = user_data;
229 EmpathyContactList *list;
231 if (error != NULL) {
232 DEBUG ("Error: %s", error->message);
233 return;
236 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
237 empathy_contact_get_id (contact),
238 empathy_contact_get_handle (contact),
239 data->old_group, data->new_group);
241 list = empathy_contact_list_store_get_list_iface (priv->store);
242 if (data->new_group) {
243 empathy_contact_list_add_to_group (list, contact, data->new_group);
245 if (data->old_group && data->action == GDK_ACTION_MOVE) {
246 empathy_contact_list_remove_from_group (list, contact, data->old_group);
250 static gboolean
251 contact_list_view_contact_drag_received (GtkWidget *view,
252 GdkDragContext *context,
253 GtkTreeModel *model,
254 GtkTreePath *path,
255 GtkSelectionData *selection)
257 EmpathyContactListViewPriv *priv;
258 TpAccountManager *account_manager;
259 EmpathyTpContactFactory *factory = NULL;
260 TpAccount *account;
261 DndGetContactData *data;
262 GtkTreePath *source_path;
263 const gchar *sel_data;
264 gchar **strv = NULL;
265 const gchar *account_id = NULL;
266 const gchar *contact_id = NULL;
267 gchar *new_group = NULL;
268 gchar *old_group = NULL;
269 gboolean success = TRUE;
271 priv = GET_PRIV (view);
273 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
274 new_group = empathy_contact_list_store_get_parent_group (model,
275 path, NULL);
277 /* Get source group information. */
278 if (priv->drag_row) {
279 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
280 if (source_path) {
281 old_group = empathy_contact_list_store_get_parent_group (
282 model, source_path, NULL);
283 gtk_tree_path_free (source_path);
287 if (!tp_strdiff (old_group, new_group)) {
288 g_free (new_group);
289 g_free (old_group);
290 return FALSE;
293 account_manager = tp_account_manager_dup ();
294 strv = g_strsplit (sel_data, ":", 2);
295 if (g_strv_length (strv) == 2) {
296 account_id = strv[0];
297 contact_id = strv[1];
298 account = tp_account_manager_ensure_account (account_manager, account_id);
300 if (account) {
301 TpConnection *connection;
303 connection = tp_account_get_connection (account);
304 if (connection) {
305 factory = empathy_tp_contact_factory_dup_singleton (connection);
308 g_object_unref (account_manager);
310 if (!factory) {
311 DEBUG ("Failed to get factory for account '%s'", account_id);
312 success = FALSE;
313 g_free (new_group);
314 g_free (old_group);
315 return FALSE;
318 data = g_slice_new0 (DndGetContactData);
319 data->new_group = new_group;
320 data->old_group = old_group;
321 data->action = context->action;
323 /* FIXME: We should probably wait for the cb before calling
324 * gtk_drag_finish */
325 empathy_tp_contact_factory_get_from_id (factory, contact_id,
326 contact_list_view_drag_got_contact,
327 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
328 G_OBJECT (view));
329 g_strfreev (strv);
330 g_object_unref (factory);
332 return TRUE;
335 static gboolean
336 contact_list_view_file_drag_received (GtkWidget *view,
337 GdkDragContext *context,
338 GtkTreeModel *model,
339 GtkTreePath *path,
340 GtkSelectionData *selection)
342 GtkTreeIter iter;
343 const gchar *sel_data;
344 EmpathyContact *contact;
346 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
348 gtk_tree_model_get_iter (model, &iter, path);
349 gtk_tree_model_get (model, &iter,
350 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
351 -1);
352 if (!contact) {
353 return FALSE;
356 empathy_send_file_from_uri_list (contact, sel_data);
358 g_object_unref (contact);
360 return TRUE;
363 static void
364 contact_list_view_drag_data_received (GtkWidget *view,
365 GdkDragContext *context,
366 gint x,
367 gint y,
368 GtkSelectionData *selection,
369 guint info,
370 guint time_)
372 GtkTreeModel *model;
373 gboolean is_row;
374 GtkTreeViewDropPosition position;
375 GtkTreePath *path;
376 gboolean success = TRUE;
378 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
380 /* Get destination group information. */
381 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
384 &path,
385 &position);
386 if (!is_row) {
387 success = FALSE;
389 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
390 success = contact_list_view_contact_drag_received (view,
391 context,
392 model,
393 path,
394 selection);
396 else if (info == DND_DRAG_TYPE_URI_LIST) {
397 success = contact_list_view_file_drag_received (view,
398 context,
399 model,
400 path,
401 selection);
404 gtk_tree_path_free (path);
405 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
408 static gboolean
409 contact_list_view_drag_motion_cb (DragMotionData *data)
411 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
412 data->path,
413 FALSE);
415 data->timeout_id = 0;
417 return FALSE;
420 static gboolean
421 contact_list_view_drag_motion (GtkWidget *widget,
422 GdkDragContext *context,
423 gint x,
424 gint y,
425 guint time_)
427 EmpathyContactListViewPriv *priv;
428 GtkTreeModel *model;
429 GdkAtom target;
430 GtkTreeIter iter;
431 static DragMotionData *dm = NULL;
432 GtkTreePath *path;
433 gboolean is_row;
434 gboolean is_different = FALSE;
435 gboolean cleanup = TRUE;
436 gboolean retval = TRUE;
438 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
439 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
441 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
444 &path,
445 NULL,
446 NULL,
447 NULL);
449 cleanup &= (!dm);
451 if (is_row) {
452 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
453 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
454 } else {
455 cleanup &= FALSE;
458 if (path == NULL) {
459 /* Coordinates don't point to an actual row, so make sure the pointer
460 and highlighting don't indicate that a drag is possible.
462 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
463 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
464 return FALSE;
466 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
467 gtk_tree_model_get_iter (model, &iter, path);
469 if (target == GDK_NONE) {
470 /* If target == GDK_NONE, then we don't have a target that can be
471 dropped on a contact. This means a contact drag. If we're
472 pointing to a group, highlight it. Otherwise, if the contact
473 we're pointing to is in a group, highlight that. Otherwise,
474 set the drag position to before the first row for a drag into
475 the "non-group" at the top.
477 GtkTreeIter group_iter;
478 gboolean is_group;
479 GtkTreePath *group_path;
480 gtk_tree_model_get (model, &iter,
481 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
482 -1);
483 if (is_group) {
484 group_iter = iter;
486 else {
487 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
488 gtk_tree_model_get (model, &group_iter,
489 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
490 -1);
492 if (is_group) {
493 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
494 group_path = gtk_tree_model_get_path (model, &group_iter);
495 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
496 group_path,
497 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
498 gtk_tree_path_free (group_path);
500 else {
501 group_path = gtk_tree_path_new_first ();
502 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
503 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
504 group_path,
505 GTK_TREE_VIEW_DROP_BEFORE);
508 else {
509 /* This is a file drag, and it can only be dropped on contacts,
510 not groups.
512 EmpathyContact *contact;
513 gtk_tree_model_get (model, &iter,
514 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
515 -1);
516 if (contact != NULL &&
517 empathy_contact_is_online (contact) &&
518 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
519 gdk_drag_status (context, GDK_ACTION_COPY, time_);
520 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
521 path,
522 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
523 g_object_unref (contact);
525 else {
526 gdk_drag_status (context, 0, time_);
527 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
528 retval = FALSE;
532 if (!is_different && !cleanup) {
533 return retval;
536 if (dm) {
537 gtk_tree_path_free (dm->path);
538 if (dm->timeout_id) {
539 g_source_remove (dm->timeout_id);
542 g_free (dm);
544 dm = NULL;
547 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
548 dm = g_new0 (DragMotionData, 1);
550 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
551 dm->path = gtk_tree_path_copy (path);
553 dm->timeout_id = g_timeout_add_seconds (1,
554 (GSourceFunc) contact_list_view_drag_motion_cb,
555 dm);
558 return retval;
561 static void
562 contact_list_view_drag_begin (GtkWidget *widget,
563 GdkDragContext *context)
565 EmpathyContactListViewPriv *priv;
566 GtkTreeSelection *selection;
567 GtkTreeModel *model;
568 GtkTreePath *path;
569 GtkTreeIter iter;
571 priv = GET_PRIV (widget);
573 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
574 context);
576 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
577 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
578 return;
581 path = gtk_tree_model_get_path (model, &iter);
582 priv->drag_row = gtk_tree_row_reference_new (model, path);
583 gtk_tree_path_free (path);
586 static void
587 contact_list_view_drag_data_get (GtkWidget *widget,
588 GdkDragContext *context,
589 GtkSelectionData *selection,
590 guint info,
591 guint time_)
593 EmpathyContactListViewPriv *priv;
594 GtkTreePath *src_path;
595 GtkTreeIter iter;
596 GtkTreeModel *model;
597 EmpathyContact *contact;
598 TpAccount *account;
599 const gchar *contact_id;
600 const gchar *account_id;
601 gchar *str;
603 priv = GET_PRIV (widget);
605 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
606 if (!priv->drag_row) {
607 return;
610 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
611 if (!src_path) {
612 return;
615 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
616 gtk_tree_path_free (src_path);
617 return;
620 gtk_tree_path_free (src_path);
622 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
623 if (!contact) {
624 return;
627 account = empathy_contact_get_account (contact);
628 account_id = tp_proxy_get_object_path (account);
629 contact_id = empathy_contact_get_id (contact);
630 g_object_unref (contact);
631 str = g_strconcat (account_id, ":", contact_id, NULL);
633 switch (info) {
634 case DND_DRAG_TYPE_CONTACT_ID:
635 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
636 (guchar *) str, strlen (str) + 1);
637 break;
640 g_free (str);
643 static void
644 contact_list_view_drag_end (GtkWidget *widget,
645 GdkDragContext *context)
647 EmpathyContactListViewPriv *priv;
649 priv = GET_PRIV (widget);
651 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
652 context);
654 if (priv->drag_row) {
655 gtk_tree_row_reference_free (priv->drag_row);
656 priv->drag_row = NULL;
660 static gboolean
661 contact_list_view_drag_drop (GtkWidget *widget,
662 GdkDragContext *drag_context,
663 gint x,
664 gint y,
665 guint time_)
667 return FALSE;
670 typedef struct {
671 EmpathyContactListView *view;
672 guint button;
673 guint32 time;
674 } MenuPopupData;
676 static gboolean
677 contact_list_view_popup_menu_idle_cb (gpointer user_data)
679 MenuPopupData *data = user_data;
680 GtkWidget *menu;
682 menu = empathy_contact_list_view_get_contact_menu (data->view);
683 if (!menu) {
684 menu = empathy_contact_list_view_get_group_menu (data->view);
687 if (menu) {
688 g_signal_connect (menu, "deactivate",
689 G_CALLBACK (gtk_menu_detach), NULL);
690 gtk_menu_attach_to_widget (GTK_MENU (menu),
691 GTK_WIDGET (data->view), NULL);
692 gtk_widget_show (menu);
693 gtk_menu_popup (GTK_MENU (menu),
694 NULL, NULL, NULL, NULL,
695 data->button, data->time);
696 g_object_ref_sink (menu);
697 g_object_unref (menu);
700 g_slice_free (MenuPopupData, data);
702 return FALSE;
705 static gboolean
706 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
707 GdkEventButton *event,
708 gpointer user_data)
710 if (event->button == 3) {
711 MenuPopupData *data;
713 data = g_slice_new (MenuPopupData);
714 data->view = view;
715 data->button = event->button;
716 data->time = event->time;
717 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
720 return FALSE;
723 static gboolean
724 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
725 GdkEventKey *event,
726 gpointer user_data)
728 if (event->keyval == GDK_Menu) {
729 MenuPopupData *data;
731 data = g_slice_new (MenuPopupData);
732 data->view = view;
733 data->button = 0;
734 data->time = event->time;
735 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
738 return FALSE;
741 static void
742 contact_list_view_row_activated (GtkTreeView *view,
743 GtkTreePath *path,
744 GtkTreeViewColumn *column)
746 EmpathyContactListViewPriv *priv = GET_PRIV (view);
747 EmpathyContact *contact;
748 GtkTreeModel *model;
749 GtkTreeIter iter;
751 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
752 return;
755 model = GTK_TREE_MODEL (priv->store);
756 gtk_tree_model_get_iter (model, &iter, path);
757 gtk_tree_model_get (model, &iter,
758 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
759 -1);
761 if (contact) {
762 DEBUG ("Starting a chat");
763 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
764 g_object_unref (contact);
768 static void
769 contact_list_view_call_activated_cb (
770 EmpathyCellRendererActivatable *cell,
771 const gchar *path_string,
772 EmpathyContactListView *view)
774 GtkWidget *menu;
775 GtkTreeModel *model;
776 GtkTreeIter iter;
777 EmpathyContact *contact;
778 GdkEventButton *event;
779 GtkMenuShell *shell;
780 GtkWidget *item;
782 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
783 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
784 return;
786 gtk_tree_model_get (model, &iter,
787 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
788 -1);
789 if (contact == NULL)
790 return;
792 event = (GdkEventButton *) gtk_get_current_event ();
794 menu = gtk_menu_new ();
795 shell = GTK_MENU_SHELL (menu);
797 /* audio */
798 item = empathy_contact_audio_call_menu_item_new (contact);
799 gtk_menu_shell_append (shell, item);
800 gtk_widget_show (item);
802 /* video */
803 item = empathy_contact_video_call_menu_item_new (contact);
804 gtk_menu_shell_append (shell, item);
805 gtk_widget_show (item);
807 g_signal_connect (menu, "deactivate",
808 G_CALLBACK (gtk_menu_detach), NULL);
809 gtk_menu_attach_to_widget (GTK_MENU (menu),
810 GTK_WIDGET (view), NULL);
811 gtk_widget_show (menu);
812 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
813 event->button, event->time);
814 g_object_ref_sink (menu);
815 g_object_unref (menu);
817 g_object_unref (contact);
820 #if HAVE_FAVOURITE_CONTACTS
821 static void
822 contact_list_view_favourite_toggled_cb (
823 EmpathyCellRendererActivatable *cell,
824 const gchar *path_string,
825 EmpathyContactListView *view)
827 EmpathyContactListViewPriv *priv = GET_PRIV (view);
828 GtkTreeModel *model;
829 GtkTreeIter iter;
830 EmpathyContact *contact;
831 EmpathyContactList *list;
833 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
834 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
835 return;
837 gtk_tree_model_get (model, &iter,
838 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
839 -1);
840 if (contact == NULL)
841 return;
843 list = empathy_contact_list_store_get_list_iface (priv->store);
844 if (empathy_contact_list_is_favourite (list, contact)) {
845 empathy_contact_list_remove_from_favourites (list, contact);
846 } else {
847 empathy_contact_list_add_to_favourites (list, contact);
850 g_object_unref (contact);
852 #endif /* HAVE_FAVOURITE_CONTACTS */
854 static void
855 contact_list_view_cell_set_background (EmpathyContactListView *view,
856 GtkCellRenderer *cell,
857 gboolean is_group,
858 gboolean is_active)
860 GdkColor color;
861 GtkStyle *style;
863 style = gtk_widget_get_style (GTK_WIDGET (view));
865 if (!is_group && is_active) {
866 color = style->bg[GTK_STATE_SELECTED];
868 /* Here we take the current theme colour and add it to
869 * the colour for white and average the two. This
870 * gives a colour which is inline with the theme but
871 * slightly whiter.
873 color.red = (color.red + (style->white).red) / 2;
874 color.green = (color.green + (style->white).green) / 2;
875 color.blue = (color.blue + (style->white).blue) / 2;
877 g_object_set (cell,
878 "cell-background-gdk", &color,
879 NULL);
880 } else {
881 g_object_set (cell,
882 "cell-background-gdk", NULL,
883 NULL);
887 static void
888 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
889 GtkCellRenderer *cell,
890 GtkTreeModel *model,
891 GtkTreeIter *iter,
892 EmpathyContactListView *view)
894 GdkPixbuf *pixbuf;
895 gboolean is_group;
896 gboolean is_active;
898 gtk_tree_model_get (model, iter,
899 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
900 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
901 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
902 -1);
904 g_object_set (cell,
905 "visible", !is_group,
906 "pixbuf", pixbuf,
907 NULL);
909 if (pixbuf != NULL) {
910 g_object_unref (pixbuf);
913 contact_list_view_cell_set_background (view, cell, is_group, is_active);
916 static void
917 contact_list_view_audio_call_cell_data_func (
918 GtkTreeViewColumn *tree_column,
919 GtkCellRenderer *cell,
920 GtkTreeModel *model,
921 GtkTreeIter *iter,
922 EmpathyContactListView *view)
924 gboolean is_group;
925 gboolean is_active;
926 gboolean can_audio, can_video;
928 gtk_tree_model_get (model, iter,
929 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
930 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
931 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
932 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
933 -1);
935 g_object_set (cell,
936 "visible", !is_group && (can_audio || can_video),
937 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
938 NULL);
940 contact_list_view_cell_set_background (view, cell, is_group, is_active);
943 static void
944 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
945 GtkCellRenderer *cell,
946 GtkTreeModel *model,
947 GtkTreeIter *iter,
948 EmpathyContactListView *view)
950 GdkPixbuf *pixbuf;
951 gboolean show_avatar;
952 gboolean is_group;
953 gboolean is_active;
955 gtk_tree_model_get (model, iter,
956 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
957 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
958 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
959 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
960 -1);
962 g_object_set (cell,
963 "visible", !is_group && show_avatar,
964 "pixbuf", pixbuf,
965 NULL);
967 if (pixbuf) {
968 g_object_unref (pixbuf);
971 contact_list_view_cell_set_background (view, cell, is_group, is_active);
974 static void
975 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
976 GtkCellRenderer *cell,
977 GtkTreeModel *model,
978 GtkTreeIter *iter,
979 EmpathyContactListView *view)
981 gboolean is_group;
982 gboolean is_active;
983 gboolean show_status;
984 gchar *name;
986 gtk_tree_model_get (model, iter,
987 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
988 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
989 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
990 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
991 -1);
993 g_object_set (cell,
994 "show-status", show_status,
995 "text", name,
996 NULL);
997 g_free (name);
999 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1002 static void
1003 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1004 GtkCellRenderer *cell,
1005 GtkTreeModel *model,
1006 GtkTreeIter *iter,
1007 EmpathyContactListView *view)
1009 gboolean is_group;
1010 gboolean is_active;
1012 gtk_tree_model_get (model, iter,
1013 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1014 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1015 -1);
1017 if (gtk_tree_model_iter_has_child (model, iter)) {
1018 GtkTreePath *path;
1019 gboolean row_expanded;
1021 path = gtk_tree_model_get_path (model, iter);
1022 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1023 gtk_tree_path_free (path);
1025 g_object_set (cell,
1026 "visible", TRUE,
1027 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1028 NULL);
1029 } else {
1030 g_object_set (cell, "visible", FALSE, NULL);
1033 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1036 #if HAVE_FAVOURITE_CONTACTS
1037 static void
1038 contact_list_view_favourite_cell_data_func (
1039 GtkTreeViewColumn *tree_column,
1040 GtkCellRenderer *cell,
1041 GtkTreeModel *model,
1042 GtkTreeIter *iter,
1043 EmpathyContactListView *view)
1045 gboolean is_group;
1046 gboolean is_active;
1047 gboolean is_separator;
1048 gboolean is_favourite;
1049 const gchar *icon_name = NULL;
1051 gtk_tree_model_get (model, iter,
1052 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1053 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1054 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
1055 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAVOURITE, &is_favourite,
1056 -1);
1058 if (!is_separator && !is_group)
1059 icon_name = (is_favourite? EMPATHY_IMAGE_FAVOURITE :
1060 EMPATHY_IMAGE_UNFAVOURITE);
1062 g_object_set (cell,
1063 "visible", (icon_name != NULL),
1064 "icon-name", icon_name,
1065 NULL);
1067 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1069 #endif /* HAVE_FAVOURITE_CONTACTS */
1071 static void
1072 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1073 GtkTreeIter *iter,
1074 GtkTreePath *path,
1075 gpointer user_data)
1077 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1078 GtkTreeModel *model;
1079 gchar *name;
1080 gboolean expanded;
1082 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1083 return;
1086 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1088 gtk_tree_model_get (model, iter,
1089 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1090 -1);
1092 expanded = GPOINTER_TO_INT (user_data);
1093 empathy_contact_group_set_expanded (name, expanded);
1095 g_free (name);
1098 static void
1099 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1100 GtkTreePath *path,
1101 GtkTreeIter *iter,
1102 EmpathyContactListView *view)
1104 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1105 gboolean is_group = FALSE;
1106 gchar *name = NULL;
1108 gtk_tree_model_get (model, iter,
1109 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1110 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1111 -1);
1113 if (!is_group || EMP_STR_EMPTY (name)) {
1114 g_free (name);
1115 return;
1118 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1119 empathy_contact_group_get_expanded (name)) {
1120 g_signal_handlers_block_by_func (view,
1121 contact_list_view_row_expand_or_collapse_cb,
1122 GINT_TO_POINTER (TRUE));
1123 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1124 g_signal_handlers_unblock_by_func (view,
1125 contact_list_view_row_expand_or_collapse_cb,
1126 GINT_TO_POINTER (TRUE));
1127 } else {
1128 g_signal_handlers_block_by_func (view,
1129 contact_list_view_row_expand_or_collapse_cb,
1130 GINT_TO_POINTER (FALSE));
1131 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1132 g_signal_handlers_unblock_by_func (view,
1133 contact_list_view_row_expand_or_collapse_cb,
1134 GINT_TO_POINTER (FALSE));
1137 g_free (name);
1140 static void
1141 contact_list_view_setup (EmpathyContactListView *view)
1143 EmpathyContactListViewPriv *priv;
1144 GtkCellRenderer *cell;
1145 GtkTreeViewColumn *col;
1146 guint i;
1148 priv = GET_PRIV (view);
1150 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1151 empathy_contact_list_store_search_equal_func,
1152 NULL, NULL);
1154 g_signal_connect (priv->store, "row-has-child-toggled",
1155 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1156 view);
1157 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1158 GTK_TREE_MODEL (priv->store));
1160 /* Setup view */
1161 /* Setting reorderable is a hack that gets us row previews as drag icons
1162 for free. We override all the drag handlers. It's tricky to get the
1163 position of the drag icon right in drag_begin. GtkTreeView has special
1164 voodoo for it, so we let it do the voodoo that he do.
1166 g_object_set (view,
1167 "headers-visible", FALSE,
1168 "reorderable", TRUE,
1169 "show-expanders", FALSE,
1170 NULL);
1172 col = gtk_tree_view_column_new ();
1174 #if HAVE_FAVOURITE_CONTACTS
1175 /* Favourite Icon */
1176 cell = empathy_cell_renderer_activatable_new ();
1177 gtk_tree_view_column_pack_start (col, cell, FALSE);
1178 gtk_tree_view_column_set_cell_data_func (
1179 col, cell,
1180 (GtkTreeCellDataFunc) contact_list_view_favourite_cell_data_func,
1181 view, NULL);
1183 g_object_set (cell,
1184 "visible", FALSE,
1185 NULL);
1187 g_signal_connect (cell, "path-activated",
1188 G_CALLBACK (contact_list_view_favourite_toggled_cb),
1189 view);
1190 #endif
1192 /* State */
1193 cell = gtk_cell_renderer_pixbuf_new ();
1194 gtk_tree_view_column_pack_start (col, cell, FALSE);
1195 gtk_tree_view_column_set_cell_data_func (
1196 col, cell,
1197 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1198 view, NULL);
1200 g_object_set (cell,
1201 "xpad", 5,
1202 "ypad", 1,
1203 "visible", FALSE,
1204 NULL);
1206 /* Name */
1207 cell = empathy_cell_renderer_text_new ();
1208 gtk_tree_view_column_pack_start (col, cell, TRUE);
1209 gtk_tree_view_column_set_cell_data_func (
1210 col, cell,
1211 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1212 view, NULL);
1214 gtk_tree_view_column_add_attribute (col, cell,
1215 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1216 gtk_tree_view_column_add_attribute (col, cell,
1217 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1218 gtk_tree_view_column_add_attribute (col, cell,
1219 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1221 /* Audio Call Icon */
1222 cell = empathy_cell_renderer_activatable_new ();
1223 gtk_tree_view_column_pack_start (col, cell, FALSE);
1224 gtk_tree_view_column_set_cell_data_func (
1225 col, cell,
1226 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1227 view, NULL);
1229 g_object_set (cell,
1230 "visible", FALSE,
1231 NULL);
1233 g_signal_connect (cell, "path-activated",
1234 G_CALLBACK (contact_list_view_call_activated_cb),
1235 view);
1237 /* Avatar */
1238 cell = gtk_cell_renderer_pixbuf_new ();
1239 gtk_tree_view_column_pack_start (col, cell, FALSE);
1240 gtk_tree_view_column_set_cell_data_func (
1241 col, cell,
1242 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1243 view, NULL);
1245 g_object_set (cell,
1246 "xpad", 0,
1247 "ypad", 0,
1248 "visible", FALSE,
1249 "width", 32,
1250 "height", 32,
1251 NULL);
1253 /* Expander */
1254 cell = empathy_cell_renderer_expander_new ();
1255 gtk_tree_view_column_pack_end (col, cell, FALSE);
1256 gtk_tree_view_column_set_cell_data_func (
1257 col, cell,
1258 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1259 view, NULL);
1261 /* Actually add the column now we have added all cell renderers */
1262 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1264 /* Drag & Drop. */
1265 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1266 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1267 FALSE);
1270 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1271 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1272 FALSE);
1276 static void
1277 contact_list_view_set_list_features (EmpathyContactListView *view,
1278 EmpathyContactListFeatureFlags features)
1280 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1281 gboolean has_tooltip;
1283 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1285 priv->list_features = features;
1287 /* Update DnD source/dest */
1288 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1289 gtk_drag_source_set (GTK_WIDGET (view),
1290 GDK_BUTTON1_MASK,
1291 drag_types_source,
1292 G_N_ELEMENTS (drag_types_source),
1293 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1294 } else {
1295 gtk_drag_source_unset (GTK_WIDGET (view));
1299 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1300 gtk_drag_dest_set (GTK_WIDGET (view),
1301 GTK_DEST_DEFAULT_ALL,
1302 drag_types_dest,
1303 G_N_ELEMENTS (drag_types_dest),
1304 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1305 } else {
1306 /* FIXME: URI could still be droped depending on FT feature */
1307 gtk_drag_dest_unset (GTK_WIDGET (view));
1310 /* Update has-tooltip */
1311 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1312 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1315 static void
1316 contact_list_view_finalize (GObject *object)
1318 EmpathyContactListViewPriv *priv;
1320 priv = GET_PRIV (object);
1322 if (priv->store) {
1323 g_object_unref (priv->store);
1325 if (priv->tooltip_widget) {
1326 gtk_widget_destroy (priv->tooltip_widget);
1328 if (priv->file_targets) {
1329 gtk_target_list_unref (priv->file_targets);
1332 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1335 static void
1336 contact_list_view_get_property (GObject *object,
1337 guint param_id,
1338 GValue *value,
1339 GParamSpec *pspec)
1341 EmpathyContactListViewPriv *priv;
1343 priv = GET_PRIV (object);
1345 switch (param_id) {
1346 case PROP_STORE:
1347 g_value_set_object (value, priv->store);
1348 break;
1349 case PROP_LIST_FEATURES:
1350 g_value_set_flags (value, priv->list_features);
1351 break;
1352 case PROP_CONTACT_FEATURES:
1353 g_value_set_flags (value, priv->contact_features);
1354 break;
1355 default:
1356 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1357 break;
1361 static void
1362 contact_list_view_set_property (GObject *object,
1363 guint param_id,
1364 const GValue *value,
1365 GParamSpec *pspec)
1367 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1368 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1370 switch (param_id) {
1371 case PROP_STORE:
1372 priv->store = g_value_dup_object (value);
1373 contact_list_view_setup (view);
1374 break;
1375 case PROP_LIST_FEATURES:
1376 contact_list_view_set_list_features (view, g_value_get_flags (value));
1377 break;
1378 case PROP_CONTACT_FEATURES:
1379 priv->contact_features = g_value_get_flags (value);
1380 break;
1381 default:
1382 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1383 break;
1387 static void
1388 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1390 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1391 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1392 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1394 object_class->finalize = contact_list_view_finalize;
1395 object_class->get_property = contact_list_view_get_property;
1396 object_class->set_property = contact_list_view_set_property;
1398 widget_class->drag_data_received = contact_list_view_drag_data_received;
1399 widget_class->drag_drop = contact_list_view_drag_drop;
1400 widget_class->drag_begin = contact_list_view_drag_begin;
1401 widget_class->drag_data_get = contact_list_view_drag_data_get;
1402 widget_class->drag_end = contact_list_view_drag_end;
1403 widget_class->drag_motion = contact_list_view_drag_motion;
1405 /* We use the class method to let user of this widget to connect to
1406 * the signal and stop emission of the signal so the default handler
1407 * won't be called. */
1408 tree_view_class->row_activated = contact_list_view_row_activated;
1410 signals[DRAG_CONTACT_RECEIVED] =
1411 g_signal_new ("drag-contact-received",
1412 G_OBJECT_CLASS_TYPE (klass),
1413 G_SIGNAL_RUN_LAST,
1415 NULL, NULL,
1416 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1417 G_TYPE_NONE,
1418 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1420 g_object_class_install_property (object_class,
1421 PROP_STORE,
1422 g_param_spec_object ("store",
1423 "The store of the view",
1424 "The store of the view",
1425 EMPATHY_TYPE_CONTACT_LIST_STORE,
1426 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1427 g_object_class_install_property (object_class,
1428 PROP_LIST_FEATURES,
1429 g_param_spec_flags ("list-features",
1430 "Features of the view",
1431 "Falgs for all enabled features",
1432 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1433 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1434 G_PARAM_READWRITE));
1435 g_object_class_install_property (object_class,
1436 PROP_CONTACT_FEATURES,
1437 g_param_spec_flags ("contact-features",
1438 "Features of the contact menu",
1439 "Falgs for all enabled features for the menu",
1440 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1441 EMPATHY_CONTACT_FEATURE_NONE,
1442 G_PARAM_READWRITE));
1444 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1447 static void
1448 empathy_contact_list_view_init (EmpathyContactListView *view)
1450 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1451 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1453 view->priv = priv;
1454 /* Get saved group states. */
1455 empathy_contact_groups_get_all ();
1457 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1458 empathy_contact_list_store_row_separator_func,
1459 NULL, NULL);
1461 /* Set up drag target lists. */
1462 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1463 G_N_ELEMENTS (drag_types_dest_file));
1465 /* Connect to tree view signals rather than override. */
1466 g_signal_connect (view, "button-press-event",
1467 G_CALLBACK (contact_list_view_button_press_event_cb),
1468 NULL);
1469 g_signal_connect (view, "key-press-event",
1470 G_CALLBACK (contact_list_view_key_press_event_cb),
1471 NULL);
1472 g_signal_connect (view, "row-expanded",
1473 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1474 GINT_TO_POINTER (TRUE));
1475 g_signal_connect (view, "row-collapsed",
1476 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1477 GINT_TO_POINTER (FALSE));
1478 g_signal_connect (view, "query-tooltip",
1479 G_CALLBACK (contact_list_view_query_tooltip_cb),
1480 NULL);
1483 EmpathyContactListView *
1484 empathy_contact_list_view_new (EmpathyContactListStore *store,
1485 EmpathyContactListFeatureFlags list_features,
1486 EmpathyContactFeatureFlags contact_features)
1488 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1490 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1491 "store", store,
1492 "contact-features", contact_features,
1493 "list-features", list_features,
1494 NULL);
1497 EmpathyContact *
1498 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1500 EmpathyContactListViewPriv *priv;
1501 GtkTreeSelection *selection;
1502 GtkTreeIter iter;
1503 GtkTreeModel *model;
1504 EmpathyContact *contact;
1506 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1508 priv = GET_PRIV (view);
1510 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1511 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1512 return NULL;
1515 gtk_tree_model_get (model, &iter,
1516 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1517 -1);
1519 return contact;
1522 EmpathyContactListFlags
1523 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1525 EmpathyContactListViewPriv *priv;
1526 GtkTreeSelection *selection;
1527 GtkTreeIter iter;
1528 GtkTreeModel *model;
1529 EmpathyContactListFlags flags;
1531 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1533 priv = GET_PRIV (view);
1535 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1536 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1537 return 0;
1540 gtk_tree_model_get (model, &iter,
1541 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1542 -1);
1544 return flags;
1547 gchar *
1548 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1550 EmpathyContactListViewPriv *priv;
1551 GtkTreeSelection *selection;
1552 GtkTreeIter iter;
1553 GtkTreeModel *model;
1554 gboolean is_group;
1555 gchar *name;
1557 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1559 priv = GET_PRIV (view);
1561 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1562 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1563 return NULL;
1566 gtk_tree_model_get (model, &iter,
1567 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1568 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1569 -1);
1571 if (!is_group) {
1572 g_free (name);
1573 return NULL;
1576 return name;
1579 static gboolean
1580 contact_list_view_remove_dialog_show (GtkWindow *parent,
1581 const gchar *message,
1582 const gchar *secondary_text)
1584 GtkWidget *dialog;
1585 gboolean res;
1587 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1588 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1589 "%s", message);
1590 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1591 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1592 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1593 NULL);
1594 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1595 "%s", secondary_text);
1597 gtk_widget_show (dialog);
1599 res = gtk_dialog_run (GTK_DIALOG (dialog));
1600 gtk_widget_destroy (dialog);
1602 return (res == GTK_RESPONSE_YES);
1605 static void
1606 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1607 EmpathyContactListView *view)
1609 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1610 gchar *group;
1612 group = empathy_contact_list_view_get_selected_group (view);
1613 if (group) {
1614 gchar *text;
1615 GtkWindow *parent;
1617 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1618 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1619 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1620 EmpathyContactList *list;
1622 list = empathy_contact_list_store_get_list_iface (priv->store);
1623 empathy_contact_list_remove_group (list, group);
1626 g_free (text);
1629 g_free (group);
1632 GtkWidget *
1633 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1635 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1636 gchar *group;
1637 GtkWidget *menu;
1638 GtkWidget *item;
1639 GtkWidget *image;
1641 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1643 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1644 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1645 return NULL;
1648 group = empathy_contact_list_view_get_selected_group (view);
1649 if (!group) {
1650 return NULL;
1653 menu = gtk_menu_new ();
1655 /* FIXME: Not implemented yet
1656 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1657 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1658 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1659 gtk_widget_show (item);
1660 g_signal_connect (item, "activate",
1661 G_CALLBACK (contact_list_view_group_rename_activate_cb),
1662 view);
1665 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1666 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1667 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1668 GTK_ICON_SIZE_MENU);
1669 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1670 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1671 gtk_widget_show (item);
1672 g_signal_connect (item, "activate",
1673 G_CALLBACK (contact_list_view_group_remove_activate_cb),
1674 view);
1677 g_free (group);
1679 return menu;
1682 static void
1683 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
1684 EmpathyContactListView *view)
1686 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1687 EmpathyContact *contact;
1689 contact = empathy_contact_list_view_dup_selected (view);
1691 if (contact) {
1692 gchar *text;
1693 GtkWindow *parent;
1695 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1696 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1697 empathy_contact_get_name (contact));
1698 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1699 EmpathyContactList *list;
1701 list = empathy_contact_list_store_get_list_iface (priv->store);
1702 empathy_contact_list_remove (list, contact, "");
1705 g_free (text);
1706 g_object_unref (contact);
1710 GtkWidget *
1711 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1713 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1714 EmpathyContact *contact;
1715 GtkWidget *menu;
1716 GtkWidget *item;
1717 GtkWidget *image;
1718 EmpathyContactListFlags flags;
1720 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1722 contact = empathy_contact_list_view_dup_selected (view);
1723 if (!contact) {
1724 return NULL;
1726 flags = empathy_contact_list_view_get_flags (view);
1728 menu = empathy_contact_menu_new (contact, priv->contact_features);
1730 /* Remove contact */
1731 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1732 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1733 /* create the menu if required, or just add a separator */
1734 if (!menu) {
1735 menu = gtk_menu_new ();
1736 } else {
1737 item = gtk_separator_menu_item_new ();
1738 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1739 gtk_widget_show (item);
1742 /* Remove */
1743 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1744 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1745 GTK_ICON_SIZE_MENU);
1746 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1747 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1748 gtk_widget_show (item);
1749 g_signal_connect (item, "activate",
1750 G_CALLBACK (contact_list_view_remove_activate_cb),
1751 view);
1754 g_object_unref (contact);
1756 return menu;