Help: Use stable 'if' namespace instead of experimental
[nijm-empathy.git] / src / empathy-roster-window.c
blob26d0e25a3bc8c8209aa4d44ae01fe321d378f901
1 /*
2 * Copyright (C) 2002-2007 Imendio AB
3 * Copyright (C) 2007-2010 Collabora Ltd.
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301 USA
20 * Authors: Xavier Claessens <xclaesse@gmail.com>
21 * Danielle Madeley <danielle.madeley@collabora.co.uk>
24 #include "config.h"
25 #include "empathy-roster-window.h"
27 #include <sys/stat.h>
28 #include <glib/gi18n.h>
29 #include <tp-account-widgets/tpaw-builder.h>
31 #include "empathy-about-dialog.h"
32 #include "empathy-accounts-dialog.h"
33 #include "empathy-call-observer.h"
34 #include "empathy-chat-manager.h"
35 #include "empathy-chat-window.h"
36 #include "empathy-chatroom-manager.h"
37 #include "empathy-chatrooms-window.h"
38 #include "empathy-client-factory.h"
39 #include "empathy-contact-blocking-dialog.h"
40 #include "empathy-contact-search-dialog.h"
41 #include "empathy-event-manager.h"
42 #include "empathy-ft-manager.h"
43 #include "empathy-geometry.h"
44 #include "empathy-gsettings.h"
45 #include "empathy-gsettings.h"
46 #include "empathy-gtk-enum-types.h"
47 #include "empathy-individual-dialogs.h"
48 #include "empathy-log-window.h"
49 #include "empathy-new-call-dialog.h"
50 #include "empathy-new-chatroom-dialog.h"
51 #include "empathy-new-message-dialog.h"
52 #include "empathy-preferences.h"
53 #include "empathy-presence-chooser.h"
54 #include "empathy-presence-manager.h"
55 #include "empathy-request-util.h"
56 #include "empathy-roster-model-manager.h"
57 #include "empathy-roster-view.h"
58 #include "empathy-status-presets.h"
59 #include "empathy-theme-manager.h"
60 #include "empathy-theme-manager.h"
61 #include "empathy-ui-utils.h"
62 #include "empathy-utils.h"
64 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
65 #include "empathy-debug.h"
67 /* Flashing delay for icons (milliseconds). */
68 #define FLASH_TIMEOUT 500
70 /* Minimum width of roster window if something goes wrong. */
71 #define MIN_WIDTH 50
73 /* Accels (menu shortcuts) can be configured and saved */
74 #define ACCELS_FILENAME "accels.txt"
76 /* Name in the geometry file */
77 #define GEOMETRY_NAME "roster-window"
79 enum
81 PAGE_CONTACT_LIST = 0,
82 PAGE_MESSAGE
85 enum
87 PROP_0,
88 PROP_SHELL_RUNNING
91 G_DEFINE_TYPE (EmpathyRosterWindow, empathy_roster_window, GTK_TYPE_APPLICATION_WINDOW)
93 struct _EmpathyRosterWindowPriv {
94 EmpathyRosterView *view;
95 TpAccountManager *account_manager;
96 EmpathyChatManager *chat_manager;
97 EmpathyThemeManager *theme_manager;
98 EmpathyChatroomManager *chatroom_manager;
99 EmpathyEventManager *event_manager;
100 EmpathySoundManager *sound_mgr;
101 EmpathyCallObserver *call_observer;
102 EmpathyIndividualManager *individual_manager;
103 guint flash_timeout_id;
104 gboolean flash_on;
106 GSettings *gsettings_ui;
108 GtkWidget *preferences;
109 GtkWidget *main_vbox;
110 GtkWidget *throbber;
111 GtkWidget *presence_toolbar;
112 GtkWidget *presence_chooser;
113 GtkWidget *errors_vbox;
114 GtkWidget *auth_vbox;
115 GtkWidget *search_bar;
116 GtkWidget *notebook;
117 GtkWidget *no_entry_label;
118 GtkWidget *button_account_settings;
119 //GtkWidget *button_online;
120 GtkWidget *button_show_offline;
121 GtkWidget *button_add_contact;
122 GtkWidget *spinner_loading;
123 GtkWidget *tooltip_widget;
124 GtkWidget *chat_window;
126 GMenu *menumodel;
127 GMenu *rooms_section;
129 GtkWidget *balance_vbox;
131 guint size_timeout_id;
133 /* reffed TpAccount* => visible GtkInfoBar* */
134 GHashTable *errors;
136 /* EmpathyEvent* => visible GtkInfoBar* */
137 GHashTable *auths;
139 /* stores a mapping from TpAccount to Handler ID to prevent
140 * to listen more than once to the status-changed signal */
141 GHashTable *status_changed_handlers;
143 /* Actions that are enabled when there are connected accounts */
144 GList *actions_connected;
146 gboolean shell_running;
149 static void
150 roster_window_remove_auth (EmpathyRosterWindow *self,
151 EmpathyEvent *event)
153 GtkWidget *error_widget;
155 error_widget = g_hash_table_lookup (self->priv->auths, event);
156 if (error_widget != NULL)
158 gtk_widget_destroy (error_widget);
159 g_hash_table_remove (self->priv->auths, event);
163 static void
164 roster_window_auth_add_clicked_cb (GtkButton *button,
165 EmpathyRosterWindow *self)
167 EmpathyEvent *event;
169 event = g_object_get_data (G_OBJECT (button), "event");
171 empathy_event_approve (event);
173 roster_window_remove_auth (self, event);
176 static void
177 roster_window_auth_close_clicked_cb (GtkButton *button,
178 EmpathyRosterWindow *self)
180 EmpathyEvent *event;
182 event = g_object_get_data (G_OBJECT (button), "event");
184 empathy_event_decline (event);
185 roster_window_remove_auth (self, event);
188 static void
189 roster_window_auth_display (EmpathyRosterWindow *self,
190 EmpathyEvent *event)
192 TpAccount *account = event->account;
193 GtkWidget *info_bar;
194 GtkWidget *content_area;
195 GtkWidget *image;
196 GtkWidget *label;
197 GtkWidget *add_button;
198 GtkWidget *close_button;
199 GtkWidget *action_area;
200 GtkWidget *action_grid;
201 const gchar *icon_name;
202 gchar *str;
204 if (g_hash_table_lookup (self->priv->auths, event) != NULL)
205 return;
207 info_bar = gtk_info_bar_new ();
208 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_QUESTION);
210 gtk_widget_set_no_show_all (info_bar, TRUE);
211 gtk_container_add (GTK_CONTAINER (self->priv->auth_vbox), info_bar);
212 gtk_widget_show (info_bar);
214 icon_name = tp_account_get_icon_name (account);
215 image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
216 gtk_widget_show (image);
218 str = g_markup_printf_escaped ("<b>%s</b>\n%s",
219 tp_account_get_display_name (account),
220 _("Password required"));
222 label = gtk_label_new (str);
223 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
224 gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
225 gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
226 gtk_widget_show (label);
228 g_free (str);
230 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
231 gtk_container_add (GTK_CONTAINER (content_area), image);
232 gtk_container_add (GTK_CONTAINER (content_area), label);
234 image = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON);
235 add_button = gtk_button_new ();
236 gtk_button_set_image (GTK_BUTTON (add_button), image);
237 gtk_widget_set_tooltip_text (add_button, _("Provide Password"));
238 gtk_widget_show (add_button);
240 image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
241 close_button = gtk_button_new ();
242 gtk_button_set_image (GTK_BUTTON (close_button), image);
243 gtk_widget_set_tooltip_text (close_button, _("Disconnect"));
244 gtk_widget_show (close_button);
246 action_grid = gtk_grid_new ();
247 gtk_grid_set_column_spacing (GTK_GRID (action_grid), 6);
248 gtk_widget_show (action_grid);
250 action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
251 gtk_container_add (GTK_CONTAINER (action_area), action_grid);
253 gtk_grid_attach (GTK_GRID (action_grid), add_button, 0, 0, 1, 1);
254 gtk_grid_attach (GTK_GRID (action_grid), close_button, 1, 0, 1, 1);
256 g_object_set_data_full (G_OBJECT (info_bar),
257 "event", event, NULL);
258 g_object_set_data_full (G_OBJECT (add_button),
259 "event", event, NULL);
260 g_object_set_data_full (G_OBJECT (close_button),
261 "event", event, NULL);
263 g_signal_connect (add_button, "clicked",
264 G_CALLBACK (roster_window_auth_add_clicked_cb), self);
265 g_signal_connect (close_button, "clicked",
266 G_CALLBACK (roster_window_auth_close_clicked_cb), self);
268 gtk_widget_show (self->priv->auth_vbox);
270 g_hash_table_insert (self->priv->auths, event, info_bar);
273 static FolksIndividual *
274 ensure_individual_for_event (EmpathyEvent *event)
276 TpContact *contact;
278 contact = empathy_contact_get_tp_contact (event->contact);
279 if (contact == NULL)
280 return NULL;
282 return empathy_ensure_individual_from_tp_contact (contact);
285 static void
286 roster_window_event_added_cb (EmpathyEventManager *manager,
287 EmpathyEvent *event,
288 EmpathyRosterWindow *self)
290 if (event->contact)
292 FolksIndividual *individual;
294 individual = ensure_individual_for_event (event);
295 if (individual == NULL)
296 return;
298 event->roster_view_id = empathy_roster_view_add_event (self->priv->view,
299 individual, event->icon_name, event);
301 g_object_unref (individual);
303 else if (event->type == EMPATHY_EVENT_TYPE_AUTH)
305 roster_window_auth_display (self, event);
309 static void
310 roster_window_event_removed_cb (EmpathyEventManager *manager,
311 EmpathyEvent *event,
312 EmpathyRosterWindow *self)
314 if (event->type == EMPATHY_EVENT_TYPE_AUTH)
316 roster_window_remove_auth (self, event);
317 return;
320 empathy_roster_view_remove_event (self->priv->view, event->roster_view_id);
323 static gboolean
324 roster_window_load_events_idle_cb (gpointer user_data)
326 EmpathyRosterWindow *self = user_data;
327 GSList *l;
329 l = empathy_event_manager_get_events (self->priv->event_manager);
330 while (l != NULL)
332 roster_window_event_added_cb (self->priv->event_manager, l->data,
333 self);
334 l = l->next;
337 return FALSE;
340 static void
341 hide_search_bar (EmpathyRosterWindow *roster_window)
343 if (TPAW_IS_LIVE_SEARCH (roster_window->priv->search_bar) &&
344 gtk_widget_is_visible (roster_window->priv->search_bar))
345 gtk_widget_hide (roster_window->priv->search_bar);
348 static void
349 individual_activated_cb (EmpathyRosterView *self,
350 FolksIndividual *individual,
351 gpointer user_data)
353 EmpathyContact *contact;
355 contact = empathy_contact_dup_best_for_action (individual,
356 EMPATHY_ACTION_CHAT);
358 if (contact == NULL)
359 return;
361 DEBUG ("Starting a chat");
363 empathy_chat_with_contact (contact, gtk_get_current_event_time ());
365 g_object_unref (contact);
367 /* Hide the search-bar upon hitting "Enter" on an individual */
368 hide_search_bar (EMPATHY_ROSTER_WINDOW (user_data));
371 static void
372 event_activated_cb (EmpathyRosterView *self,
373 FolksIndividual *individual,
374 EmpathyEvent *event,
375 gpointer user_data)
377 empathy_event_activate (event);
379 /* Hide the search-bar upon an event activation */
380 hide_search_bar (EMPATHY_ROSTER_WINDOW (user_data));
383 static void
384 button_account_settings_clicked_cb (GtkButton *button,
385 EmpathyRosterWindow *self)
387 empathy_accounts_dialog_show_application (gdk_screen_get_default (),
388 NULL, FALSE, FALSE);
391 static void
392 button_online_clicked_cb (GtkButton *button,
393 EmpathyRosterWindow *self)
395 EmpathyPresenceManager *mgr;
397 mgr = empathy_presence_manager_dup_singleton ();
399 empathy_presence_manager_set_state (mgr,
400 TP_CONNECTION_PRESENCE_TYPE_AVAILABLE);
402 g_object_unref (mgr);
405 static void
406 button_show_offline_clicked_cb (GtkButton *button,
407 EmpathyRosterWindow *self)
409 g_settings_set_boolean (self->priv->gsettings_ui,
410 EMPATHY_PREFS_UI_SHOW_OFFLINE, TRUE);
413 static void
414 button_add_contact_clicked_cb (GtkButton *button,
415 EmpathyRosterWindow *self)
417 empathy_new_individual_dialog_show (GTK_WINDOW (self));
420 typedef enum
422 PAGE_MESSAGE_FLAG_NONE = 0,
423 PAGE_MESSAGE_FLAG_ACCOUNTS = 1 << 0,
424 PAGE_MESSAGE_FLAG_SPINNER = 1 << 2,
425 PAGE_MESSAGE_FLAG_ONLINE = 1 << 3,
426 PAGE_MESSAGE_FLAG_SHOW_OFFLINE = 1 << 4,
427 PAGE_MESSAGE_FLAG_ADD_CONTACT = 1 << 5,
428 } PageMessageFlags;
430 static gboolean
431 can_add_contact (EmpathyRosterWindow *self)
433 GList *accounts, *l;
434 gboolean result = FALSE;
436 accounts = tp_account_manager_dup_valid_accounts (
437 self->priv->account_manager);
438 for (l = accounts; l != NULL && !result; l = g_list_next (l))
440 TpAccount *account = TP_ACCOUNT (l->data);
441 TpConnection *conn;
443 conn = tp_account_get_connection (account);
444 if (conn == NULL)
445 continue;
447 if (tp_connection_get_can_change_contact_list (conn))
448 result = TRUE;
451 g_list_free_full (accounts, g_object_unref);
452 return result;
455 static void
456 display_page_message (EmpathyRosterWindow *self,
457 const gchar *msg,
458 PageMessageFlags flags)
460 if (msg != NULL)
462 gchar *tmp;
464 tmp = g_strdup_printf ("<b><span size='xx-large'>%s</span></b>", msg);
466 gtk_label_set_markup (GTK_LABEL (self->priv->no_entry_label), tmp);
467 g_free (tmp);
469 gtk_label_set_line_wrap (GTK_LABEL (self->priv->no_entry_label), TRUE);
470 gtk_widget_show (self->priv->no_entry_label);
472 else
474 gtk_widget_hide (self->priv->no_entry_label);
477 gtk_widget_set_visible (self->priv->button_account_settings,
478 (flags & PAGE_MESSAGE_FLAG_ACCOUNTS) != 0);
479 gtk_widget_set_visible (self->priv->spinner_loading,
480 (flags & PAGE_MESSAGE_FLAG_SPINNER) != 0);
481 // gtk_widget_set_visible (self->priv->button_online,
482 // (flags & PAGE_MESSAGE_FLAG_ONLINE) != 0);
483 gtk_widget_set_visible (self->priv->button_show_offline,
484 (flags & PAGE_MESSAGE_FLAG_SHOW_OFFLINE) != 0);
485 gtk_widget_set_visible (self->priv->button_add_contact,
486 (flags & PAGE_MESSAGE_FLAG_ADD_CONTACT) != 0);
488 if ((flags & PAGE_MESSAGE_FLAG_ADD_CONTACT) != 0)
489 gtk_widget_set_sensitive (self->priv->button_add_contact,
490 can_add_contact (self));
492 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
493 PAGE_MESSAGE);
496 static void
497 display_page_no_account (EmpathyRosterWindow *self)
499 display_page_message (self,
500 _("You need to set up an account to see contacts here."),
501 PAGE_MESSAGE_FLAG_ACCOUNTS);
504 static void
505 display_page_contact_list (EmpathyRosterWindow *self)
507 if (!empathy_individual_manager_get_contacts_loaded (
508 self->priv->individual_manager))
509 /* We'll display the contact list once we're done loading */
510 return;
512 gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
513 PAGE_CONTACT_LIST);
516 static void
517 roster_window_remove_error (EmpathyRosterWindow *self,
518 TpAccount *account)
520 GtkWidget *error_widget;
522 error_widget = g_hash_table_lookup (self->priv->errors, account);
523 if (error_widget != NULL)
525 gtk_widget_destroy (error_widget);
526 g_hash_table_remove (self->priv->errors, account);
530 static void
531 roster_window_account_disabled_cb (TpAccountManager *manager,
532 TpAccount *account,
533 EmpathyRosterWindow *self)
535 roster_window_remove_error (self, account);
538 typedef enum
540 ERROR_RESPONSE_RETRY,
541 ERROR_RESPONSE_EDIT,
542 ERROR_RESPONSE_CLOSE,
543 ERROR_RESPONSE_UPGRADE,
544 } ErrorResponse;
546 static void
547 roster_window_error_response_cb (GtkInfoBar *infobar,
548 gint response_id,
549 EmpathyRosterWindow *self)
551 TpAccount *account;
553 account = g_object_get_data (G_OBJECT (infobar), "account");
555 switch (response_id)
557 case ERROR_RESPONSE_RETRY:
558 tp_account_reconnect_async (account, NULL, NULL);
559 break;
561 case ERROR_RESPONSE_EDIT:
562 empathy_accounts_dialog_show_application (
563 gtk_widget_get_screen (GTK_WIDGET (infobar)),
564 account, FALSE, FALSE);
565 break;
567 case ERROR_RESPONSE_CLOSE:
568 break;
570 case ERROR_RESPONSE_UPGRADE:
572 GtkWidget *dialog;
574 dialog = gtk_message_dialog_new (GTK_WINDOW (self),
575 GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
576 _("Sorry, %s accounts can’t be used until your %s software is updated."),
577 tp_account_get_protocol_name (account),
578 tp_account_get_protocol_name (account));
580 g_signal_connect_swapped (dialog, "response",
581 G_CALLBACK (gtk_widget_destroy),
582 dialog);
584 gtk_widget_show (dialog);
588 roster_window_remove_error (self, account);
591 static GtkWidget *
592 roster_window_error_create_info_bar (EmpathyRosterWindow *self,
593 TpAccount *account,
594 GtkMessageType message_type,
595 const gchar *message_markup)
597 GtkWidget *info_bar;
598 GtkWidget *content_area;
599 GtkWidget *action_area;
600 GtkWidget *label;
601 GtkWidget *image;
602 const gchar *icon_name;
604 /* If there are other errors, remove them */
605 roster_window_remove_error (self, account);
607 info_bar = gtk_info_bar_new ();
608 gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), message_type);
610 gtk_widget_set_no_show_all (info_bar, TRUE);
611 gtk_container_add (GTK_CONTAINER (self->priv->errors_vbox), info_bar);
612 gtk_widget_show (info_bar);
614 icon_name = tp_account_get_icon_name (account);
615 image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
616 gtk_widget_show (image);
618 label = gtk_label_new (message_markup);
619 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
620 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
621 gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
622 gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
623 gtk_widget_show (label);
625 content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
626 gtk_container_add (GTK_CONTAINER (content_area), image);
627 gtk_container_add (GTK_CONTAINER (content_area), label);
629 action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
630 gtk_orientable_set_orientation (GTK_ORIENTABLE (action_area),
631 GTK_ORIENTATION_HORIZONTAL);
632 gtk_style_context_add_class (gtk_widget_get_style_context (action_area),
633 "empathy-roster-window-error-button");
635 g_object_set_data_full (G_OBJECT (info_bar),
636 "account", g_object_ref (account),
637 g_object_unref);
639 g_signal_connect (info_bar, "response",
640 G_CALLBACK (roster_window_error_response_cb), self);
642 gtk_widget_show (self->priv->errors_vbox);
644 g_hash_table_insert (self->priv->errors, g_object_ref (account), info_bar);
646 return info_bar;
649 static void
650 roster_window_error_add_stock_button (GtkInfoBar *info_bar,
651 const gchar *stock_id,
652 const gchar *tooltip,
653 ErrorResponse response_id)
655 GtkWidget *image;
656 GtkWidget *button;
658 image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_BUTTON);
659 button = gtk_button_new ();
660 gtk_button_set_image (GTK_BUTTON (button), image);
661 gtk_widget_set_tooltip_text (button, tooltip);
662 gtk_widget_show (button);
664 gtk_info_bar_add_action_widget (info_bar, button, response_id);
667 #ifdef HAVE_UOA
668 static const gchar *
669 uoa_account_display_string (TpAccount *account)
671 const gchar *service;
673 service = tp_account_get_service (account);
675 /* Use well known service name, if available */
676 if (!tp_strdiff (service, "windows-live"))
677 return _("Windows Live");
678 else if (!tp_strdiff (service, "google-talk"))
679 return _("Google Talk");
680 else if (!tp_strdiff (service, "facebook"))
681 return _("Facebook");
683 return tp_account_get_display_name (account);
686 static void
687 roster_window_uoa_auth_error (EmpathyRosterWindow *self,
688 TpAccount *account)
690 GtkWidget *info_bar;
691 GtkWidget *image;
692 GtkWidget *button;
693 gchar *str;
695 /* translators: %s is an account name like 'Facebook' or 'Google Talk' */
696 str = g_strdup_printf (_("%s account requires authorisation"),
697 uoa_account_display_string (account));
699 info_bar = roster_window_error_create_info_bar (self, account,
700 GTK_MESSAGE_OTHER, str);
701 g_free (str);
703 image = gtk_image_new_from_icon_name ("credentials-preferences",
704 GTK_ICON_SIZE_BUTTON);
705 button = gtk_button_new ();
706 gtk_button_set_image (GTK_BUTTON (button), image);
707 gtk_widget_set_tooltip_text (button, _("Online Accounts"));
708 gtk_widget_show (button);
710 gtk_info_bar_add_action_widget (GTK_INFO_BAR (info_bar), button,
711 ERROR_RESPONSE_EDIT);
713 #endif
715 static void
716 roster_window_error_display (EmpathyRosterWindow *self,
717 TpAccount *account)
719 const gchar *error_message;
720 gboolean user_requested;
721 GtkWidget *info_bar;
722 gchar *str;
724 error_message =
725 empathy_account_get_error_message (account, &user_requested);
727 if (user_requested)
728 return;
730 #ifdef HAVE_UOA
731 if (!tp_strdiff (TP_ERROR_STR_AUTHENTICATION_FAILED,
732 tp_account_get_detailed_error (account, NULL)) &&
733 !tp_strdiff (tp_account_get_storage_provider (account),
734 EMPATHY_UOA_PROVIDER))
736 roster_window_uoa_auth_error (self, account);
737 return;
739 #endif
741 str = g_markup_printf_escaped ("<b>%s</b>\n%s",
742 tp_account_get_display_name (account), error_message);
744 info_bar = roster_window_error_create_info_bar (self, account,
745 GTK_MESSAGE_ERROR, str);
746 g_free (str);
748 gtk_widget_set_tooltip_text (self->priv->errors_vbox, error_message);
750 if (!tp_strdiff (TP_ERROR_STR_SOFTWARE_UPGRADE_REQUIRED,
751 tp_account_get_detailed_error (account, NULL)))
753 roster_window_error_add_stock_button (GTK_INFO_BAR (info_bar),
754 GTK_STOCK_REFRESH, _("Update software…"),
755 ERROR_RESPONSE_RETRY);
757 else
759 roster_window_error_add_stock_button (GTK_INFO_BAR (info_bar),
760 GTK_STOCK_REFRESH, _("Reconnect"),
761 ERROR_RESPONSE_RETRY);
763 roster_window_error_add_stock_button (GTK_INFO_BAR (info_bar),
764 GTK_STOCK_EDIT, _("Edit Account"),
765 ERROR_RESPONSE_EDIT);
768 roster_window_error_add_stock_button (GTK_INFO_BAR (info_bar),
769 GTK_STOCK_CLOSE, _("Close"),
770 ERROR_RESPONSE_CLOSE);
773 static void
774 roster_window_update_status (EmpathyRosterWindow *self)
776 gboolean connected, connecting;
777 GList *l;
778 GAction *action;
780 connected = empathy_account_manager_get_accounts_connected (&connecting);
782 /* Update the spinner state */
783 if (connecting)
785 gtk_spinner_start (GTK_SPINNER (self->priv->throbber));
786 gtk_widget_show (self->priv->throbber);
788 else
790 gtk_spinner_stop (GTK_SPINNER (self->priv->throbber));
791 gtk_widget_hide (self->priv->throbber);
794 /* Update widgets sensibility */
795 for (l = self->priv->actions_connected; l; l = l->next)
796 g_simple_action_set_enabled (l->data, connected);
798 action = g_action_map_lookup_action (G_ACTION_MAP (self), "chat-add-contact");
799 if (!can_add_contact (self))
800 g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
803 static void
804 roster_window_balance_update_balance (EmpathyRosterWindow *self,
805 TpAccount *account)
807 TpConnection *conn;
808 GtkWidget *label;
809 int amount = 0;
810 guint scale = G_MAXINT32;
811 const gchar *currency = "";
812 char *money;
814 conn = tp_account_get_connection (account);
815 if (conn == NULL)
816 return;
818 if (!tp_connection_get_balance (conn, &amount, &scale, &currency))
819 return;
821 if (amount == 0 &&
822 scale == G_MAXINT32 &&
823 tp_str_empty (currency))
825 /* unknown balance */
826 money = g_strdup ("--");
828 else
830 char *tmp = empathy_format_currency (amount, scale, currency);
832 money = g_strdup_printf ("%s %s", currency, tmp);
833 g_free (tmp);
836 /* update the money label in the roster */
837 label = g_object_get_data (G_OBJECT (account), "balance-money-label");
839 gtk_label_set_text (GTK_LABEL (label), money);
840 g_free (money);
843 static void
844 roster_window_balance_changed_cb (TpConnection *conn,
845 guint balance,
846 guint scale,
847 const gchar *currency,
848 EmpathyRosterWindow *self)
850 TpAccount *account;
852 account = tp_connection_get_account (conn);
853 if (account == NULL)
854 return;
856 roster_window_balance_update_balance (self, account);
859 static void
860 roster_window_setup_balance (EmpathyRosterWindow *self,
861 TpAccount *account)
863 TpConnection *conn = tp_account_get_connection (account);
864 GtkWidget *hbox, *image, *label;
865 const gchar *uri;
867 if (conn == NULL)
868 return;
870 if (!tp_proxy_is_prepared (conn, TP_CONNECTION_FEATURE_BALANCE))
871 return;
873 DEBUG ("Setting up balance for acct: %s",
874 tp_account_get_display_name (account));
876 /* create the display widget */
877 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
878 gtk_container_set_border_width (GTK_CONTAINER (hbox), 3);
880 /* protocol icon */
881 image = gtk_image_new ();
882 gtk_container_add (GTK_CONTAINER (hbox), image);
883 g_object_bind_property (account, "icon-name", image, "icon-name",
884 G_BINDING_SYNC_CREATE);
886 /* account name label */
887 label = gtk_label_new ("");
888 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
889 gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
890 gtk_container_add (GTK_CONTAINER (hbox), label);
891 g_object_bind_property (account, "display-name", label, "label",
892 G_BINDING_SYNC_CREATE);
894 /* balance label */
895 label = gtk_label_new ("");
896 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
897 gtk_container_add (GTK_CONTAINER (hbox), label);
899 /* top up button */
900 uri = tp_connection_get_balance_uri (conn);
902 if (!tp_str_empty (uri))
904 GtkWidget *button;
906 button = gtk_button_new ();
907 gtk_container_add (GTK_CONTAINER (button),
908 gtk_image_new_from_icon_name ("emblem-symbolic-link",
909 GTK_ICON_SIZE_SMALL_TOOLBAR));
910 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
911 gtk_widget_set_tooltip_text (button, _("Top up account"));
912 gtk_container_add (GTK_CONTAINER (hbox), button);
914 g_signal_connect_data (button, "clicked",
915 G_CALLBACK (empathy_url_show),
916 g_strdup (uri), (GClosureNotify) g_free,
920 gtk_container_add (GTK_CONTAINER (self->priv->balance_vbox), hbox);
921 gtk_widget_show_all (hbox);
923 g_object_set_data (G_OBJECT (account), "balance-money-label", label);
924 g_object_set_data (G_OBJECT (account), "balance-money-hbox", hbox);
926 roster_window_balance_update_balance (self, account);
928 g_signal_connect (conn, "balance-changed",
929 G_CALLBACK (roster_window_balance_changed_cb), self);
932 static void
933 roster_window_remove_balance_action (EmpathyRosterWindow *self,
934 TpAccount *account)
936 GtkWidget *hbox =
937 g_object_get_data (G_OBJECT (account), "balance-money-hbox");
939 if (hbox == NULL)
940 return;
942 g_return_if_fail (GTK_IS_BOX (hbox));
944 gtk_widget_destroy (hbox);
947 static void set_notebook_page (EmpathyRosterWindow *self);
949 static void
950 roster_window_connection_changed_cb (TpAccount *account,
951 guint old_status,
952 guint current,
953 guint reason,
954 gchar *dbus_error_name,
955 GHashTable *details,
956 EmpathyRosterWindow *self)
958 roster_window_update_status (self);
959 set_notebook_page (self);
961 if (current == TP_CONNECTION_STATUS_DISCONNECTED &&
962 reason != TP_CONNECTION_STATUS_REASON_REQUESTED)
964 roster_window_error_display (self, account);
967 if (current == TP_CONNECTION_STATUS_DISCONNECTED)
969 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
970 EMPATHY_SOUND_ACCOUNT_DISCONNECTED);
973 if (current == TP_CONNECTION_STATUS_CONNECTED)
975 empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
976 EMPATHY_SOUND_ACCOUNT_CONNECTED);
978 /* Account connected without error, remove error message if any */
979 roster_window_remove_error (self, account);
983 static void
984 roster_window_accels_load (void)
986 gchar *filename;
988 filename = g_build_filename (g_get_user_config_dir (),
989 PACKAGE_NAME, ACCELS_FILENAME, NULL);
990 if (g_file_test (filename, G_FILE_TEST_EXISTS))
992 DEBUG ("Loading from:'%s'", filename);
993 gtk_accel_map_load (filename);
996 g_free (filename);
999 static void
1000 roster_window_accels_save (void)
1002 gchar *dir;
1003 gchar *file_with_path;
1005 dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
1006 g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
1007 file_with_path = g_build_filename (dir, ACCELS_FILENAME, NULL);
1008 g_free (dir);
1010 DEBUG ("Saving to:'%s'", file_with_path);
1011 gtk_accel_map_save (file_with_path);
1013 g_free (file_with_path);
1016 static void
1017 empathy_roster_window_finalize (GObject *window)
1019 EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (window);
1020 GHashTableIter iter;
1021 gpointer key, value;
1023 /* Save user-defined accelerators. */
1024 roster_window_accels_save ();
1026 g_list_free (self->priv->actions_connected);
1028 g_object_unref (self->priv->account_manager);
1029 g_object_unref (self->priv->sound_mgr);
1030 g_hash_table_unref (self->priv->errors);
1031 g_hash_table_unref (self->priv->auths);
1033 /* disconnect all handlers of status-changed signal */
1034 g_hash_table_iter_init (&iter, self->priv->status_changed_handlers);
1035 while (g_hash_table_iter_next (&iter, &key, &value))
1036 g_signal_handler_disconnect (TP_ACCOUNT (key), GPOINTER_TO_UINT (value));
1038 g_hash_table_unref (self->priv->status_changed_handlers);
1040 g_object_unref (self->priv->call_observer);
1041 g_object_unref (self->priv->event_manager);
1042 g_object_unref (self->priv->chat_manager);
1043 g_object_unref (self->priv->theme_manager);
1044 g_object_unref (self->priv->chatroom_manager);
1046 g_object_unref (self->priv->gsettings_ui);
1047 g_object_unref (self->priv->individual_manager);
1049 g_object_unref (self->priv->menumodel);
1050 g_object_unref (self->priv->rooms_section);
1052 g_clear_object (&self->priv->tooltip_widget);
1054 G_OBJECT_CLASS (empathy_roster_window_parent_class)->finalize (window);
1057 static gboolean
1058 roster_window_key_press_event_cb (GtkWidget *window,
1059 GdkEventKey *event,
1060 EmpathyRosterWindow *self)
1062 if (event->keyval == GDK_KEY_T
1063 && event->state & GDK_SHIFT_MASK
1064 && event->state & GDK_CONTROL_MASK)
1065 empathy_chat_manager_call_undo_closed_chat ();
1067 if (event->keyval == GDK_KEY_f
1068 && event->state & GDK_CONTROL_MASK)
1069 gtk_widget_show (self->priv->search_bar);
1071 return FALSE;
1074 static void
1075 unprepare_cb (GObject *source,
1076 GAsyncResult *result,
1077 gpointer user_data)
1079 GtkWidget *self = user_data;
1081 gtk_widget_destroy (self);
1084 static void
1085 roster_window_chat_quit_cb (GSimpleAction *action,
1086 GVariant *parameter,
1087 gpointer user_data)
1089 EmpathyRosterWindow *self = user_data;
1091 /* Destroying the window will make us leave the main loop and so exit the
1092 * process. Before doing so we want to unprepare the individual manager.
1093 * Just hide the window now and actually destroy it once Folks is done.
1095 gtk_widget_hide (GTK_WIDGET (self));
1097 empathy_individual_manager_unprepare_async (self->priv->individual_manager,
1098 unprepare_cb, self);
1101 static void
1102 roster_window_view_history_cb (GSimpleAction *action,
1103 GVariant *parameter,
1104 gpointer user_data)
1106 EmpathyRosterWindow *self = user_data;
1108 empathy_log_window_show (NULL, NULL, FALSE, GTK_WINDOW (self));
1111 static void
1112 roster_window_chat_new_message_cb (GSimpleAction *action,
1113 GVariant *parameter,
1114 gpointer user_data)
1116 EmpathyRosterWindow *self = user_data;
1118 empathy_new_message_dialog_show (GTK_WINDOW (self));
1121 static void
1122 roster_window_chat_new_call_cb (GSimpleAction *action,
1123 GVariant *parameter,
1124 gpointer user_data)
1126 EmpathyRosterWindow *self = user_data;
1128 empathy_new_call_dialog_show (GTK_WINDOW (self));
1131 static void
1132 roster_window_chat_add_contact_cb (GSimpleAction *action,
1133 GVariant *parameter,
1134 gpointer user_data)
1136 EmpathyRosterWindow *self = user_data;
1138 empathy_new_individual_dialog_show (GTK_WINDOW (self));
1141 static void
1142 roster_window_chat_search_contacts_cb (GSimpleAction *action,
1143 GVariant *parameter,
1144 gpointer user_data)
1146 EmpathyRosterWindow *self = user_data;
1147 GtkWidget *dialog;
1149 dialog = empathy_contact_search_dialog_new (
1150 GTK_WINDOW (self));
1152 gtk_widget_show (dialog);
1155 static void
1156 roster_window_view_show_ft_manager (GSimpleAction *action,
1157 GVariant *parameter,
1158 gpointer user_data)
1160 empathy_ft_manager_show ();
1163 static void
1164 join_chatroom (EmpathyChatroom *chatroom,
1165 gint64 timestamp)
1167 TpAccount *account;
1168 const gchar *room;
1170 account = empathy_chatroom_get_account (chatroom);
1171 room = empathy_chatroom_get_room (chatroom);
1173 DEBUG ("Requesting channel for '%s'", room);
1174 empathy_join_muc (account, room, timestamp);
1177 typedef struct
1179 TpAccount *account;
1180 EmpathyChatroom *chatroom;
1181 gint64 timestamp;
1182 glong sig_id;
1183 guint timeout;
1184 } join_fav_account_sig_ctx;
1186 static join_fav_account_sig_ctx *
1187 join_fav_account_sig_ctx_new (TpAccount *account,
1188 EmpathyChatroom *chatroom,
1189 gint64 timestamp)
1191 join_fav_account_sig_ctx *ctx = g_slice_new0 (
1192 join_fav_account_sig_ctx);
1194 ctx->account = g_object_ref (account);
1195 ctx->chatroom = g_object_ref (chatroom);
1196 ctx->timestamp = timestamp;
1197 return ctx;
1200 static void
1201 join_fav_account_sig_ctx_free (join_fav_account_sig_ctx *ctx)
1203 g_object_unref (ctx->account);
1204 g_object_unref (ctx->chatroom);
1205 g_slice_free (join_fav_account_sig_ctx, ctx);
1208 static void
1209 account_status_changed_cb (TpAccount *account,
1210 TpConnectionStatus old_status,
1211 TpConnectionStatus new_status,
1212 guint reason,
1213 gchar *dbus_error_name,
1214 GHashTable *details,
1215 gpointer user_data)
1217 join_fav_account_sig_ctx *ctx = user_data;
1219 switch (new_status)
1221 case TP_CONNECTION_STATUS_DISCONNECTED:
1222 /* Don't wait any longer */
1223 goto finally;
1224 break;
1226 case TP_CONNECTION_STATUS_CONNECTING:
1227 /* Wait a bit */
1228 return;
1230 case TP_CONNECTION_STATUS_CONNECTED:
1231 /* We can join the room */
1232 break;
1234 default:
1235 g_assert_not_reached ();
1238 join_chatroom (ctx->chatroom, ctx->timestamp);
1240 finally:
1241 g_source_remove (ctx->timeout);
1242 g_signal_handler_disconnect (account, ctx->sig_id);
1245 #define JOIN_FAVORITE_TIMEOUT 5
1247 static gboolean
1248 join_favorite_timeout_cb (gpointer data)
1250 join_fav_account_sig_ctx *ctx = data;
1252 /* stop waiting for joining the favorite room */
1253 g_signal_handler_disconnect (ctx->account, ctx->sig_id);
1254 return FALSE;
1257 static void
1258 roster_window_favorite_chatroom_join (EmpathyChatroom *chatroom)
1260 TpAccount *account;
1262 account = empathy_chatroom_get_account (chatroom);
1263 if (tp_account_get_connection_status (account, NULL) !=
1264 TP_CONNECTION_STATUS_CONNECTED)
1266 join_fav_account_sig_ctx *ctx;
1268 ctx = join_fav_account_sig_ctx_new (account, chatroom,
1269 empathy_get_current_action_time ());
1271 ctx->sig_id = g_signal_connect_data (account, "status-changed",
1272 G_CALLBACK (account_status_changed_cb), ctx,
1273 (GClosureNotify) join_fav_account_sig_ctx_free, 0);
1275 ctx->timeout = g_timeout_add_seconds (JOIN_FAVORITE_TIMEOUT,
1276 join_favorite_timeout_cb, ctx);
1277 return;
1280 join_chatroom (chatroom, empathy_get_current_action_time ());
1283 static void
1284 roster_window_join_chatroom_menu_activate_cb (GSimpleAction *action,
1285 GVariant *parameter,
1286 gpointer user_data)
1288 EmpathyRosterWindow *self = user_data;
1289 const gchar *room, *path;
1290 EmpathyClientFactory *factory;
1291 TpAccount *account;
1292 GError *error = NULL;
1293 EmpathyChatroom *chatroom;
1295 g_variant_get (parameter, "(&s&s)", &room, &path);
1297 factory = empathy_client_factory_dup ();
1299 account = tp_simple_client_factory_ensure_account (
1300 TP_SIMPLE_CLIENT_FACTORY (factory), path, NULL, &error);
1301 if (account == NULL)
1303 DEBUG ("Failed to get account '%s': %s", path, error->message);
1304 g_error_free (error);
1305 goto out;
1308 chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1309 account, room);
1310 if (chatroom == NULL)
1312 DEBUG ("Failed to get chatroom '%s' on '%s'",
1313 room, path);
1314 goto out;
1317 roster_window_favorite_chatroom_join (chatroom);
1319 out:
1320 g_object_unref (factory);
1323 static void
1324 roster_window_favorite_chatroom_menu_add (EmpathyRosterWindow *self,
1325 EmpathyChatroom *chatroom)
1327 GMenuItem *item;
1328 const gchar *name, *account_name, *account_path;
1329 TpAccount *account;
1330 gchar *label;
1332 account = empathy_chatroom_get_account (chatroom);
1334 name = empathy_chatroom_get_name (chatroom);
1335 account_name = tp_account_get_display_name (account);
1336 account_path = tp_proxy_get_object_path (account);
1338 label = g_strdup_printf ("%s (%s)", name, account_name);
1340 item = g_menu_item_new (label, NULL);
1341 g_menu_item_set_action_and_target (item, "win.join", "(ss)",
1342 name, account_path);
1343 g_menu_item_set_attribute (item, "room-name", "s", name);
1344 g_menu_item_set_attribute (item, "account-path", "s", account_path);
1345 g_menu_append_item (self->priv->rooms_section, item);
1347 g_free (label);
1350 static void
1351 roster_window_favorite_chatroom_menu_added_cb (EmpathyChatroomManager *manager,
1352 EmpathyChatroom *chatroom,
1353 EmpathyRosterWindow *self)
1355 roster_window_favorite_chatroom_menu_add (self, chatroom);
1358 static void
1359 roster_window_favorite_chatroom_menu_removed_cb (
1360 EmpathyChatroomManager *manager,
1361 EmpathyChatroom *chatroom,
1362 EmpathyRosterWindow *self)
1364 GList *chatrooms;
1365 guint i, n;
1366 TpAccount *account;
1367 const gchar *account_path;
1369 account = empathy_chatroom_get_account (chatroom);
1370 account_path = tp_proxy_get_object_path (account);
1372 n = g_menu_model_get_n_items (G_MENU_MODEL (self->priv->rooms_section));
1374 for (i = 0; i < n; i++)
1376 gchar *tmp;
1378 if (!g_menu_model_get_item_attribute (
1379 G_MENU_MODEL (self->priv->rooms_section), i,
1380 "room-name", "s", &tmp))
1381 continue;
1383 if (tp_strdiff (tmp, empathy_chatroom_get_name (chatroom)))
1385 g_free (tmp);
1386 continue;
1389 g_free (tmp);
1391 if (!g_menu_model_get_item_attribute (
1392 G_MENU_MODEL (self->priv->rooms_section), i,
1393 "account-path", "s", &tmp))
1394 continue;
1396 if (tp_strdiff (tmp, account_path))
1398 g_free (tmp);
1399 continue;
1402 g_menu_remove (self->priv->rooms_section, i);
1403 g_free (tmp);
1404 break;
1407 chatrooms = empathy_chatroom_manager_get_chatrooms (
1408 self->priv->chatroom_manager, NULL);
1410 g_list_free (chatrooms);
1413 static void
1414 roster_window_favorite_chatroom_menu_setup (EmpathyRosterWindow *self)
1416 GList *chatrooms, *l;
1418 self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1420 chatrooms = empathy_chatroom_manager_get_chatrooms (
1421 self->priv->chatroom_manager, NULL);
1423 for (l = chatrooms; l; l = l->next)
1424 roster_window_favorite_chatroom_menu_add (self, l->data);
1426 g_signal_connect (self->priv->chatroom_manager, "chatroom-added",
1427 G_CALLBACK (roster_window_favorite_chatroom_menu_added_cb),
1428 self);
1430 g_signal_connect (self->priv->chatroom_manager, "chatroom-removed",
1431 G_CALLBACK (roster_window_favorite_chatroom_menu_removed_cb),
1432 self);
1434 g_list_free (chatrooms);
1437 static void
1438 roster_window_room_join_new_cb (GSimpleAction *action,
1439 GVariant *parameter,
1440 gpointer user_data)
1442 EmpathyRosterWindow *self = user_data;
1444 empathy_new_chatroom_dialog_show (GTK_WINDOW (self));
1447 static void
1448 roster_window_room_join_favorites_cb (GSimpleAction *action,
1449 GVariant *parameter,
1450 gpointer user_data)
1452 EmpathyRosterWindow *self = user_data;
1453 GList *chatrooms, *l;
1455 chatrooms = empathy_chatroom_manager_get_chatrooms (self->priv->chatroom_manager,
1456 NULL);
1458 for (l = chatrooms; l; l = l->next)
1459 roster_window_favorite_chatroom_join (l->data);
1461 g_list_free (chatrooms);
1464 static void
1465 roster_window_room_manage_favorites_cb (GSimpleAction *action,
1466 GVariant *parameter,
1467 gpointer user_data)
1469 EmpathyRosterWindow *self = user_data;
1471 empathy_chatrooms_window_show (GTK_WINDOW (self));
1474 static void
1475 roster_window_edit_accounts_cb (GSimpleAction *action,
1476 GVariant *parameter,
1477 gpointer user_data)
1479 empathy_accounts_dialog_show_application (gdk_screen_get_default (),
1480 NULL, FALSE, FALSE);
1483 static void
1484 roster_window_edit_blocked_contacts_cb (GSimpleAction *action,
1485 GVariant *parameter,
1486 gpointer user_data)
1488 EmpathyRosterWindow *self = user_data;
1489 GtkWidget *dialog;
1491 dialog = empathy_contact_blocking_dialog_new (GTK_WINDOW (self));
1492 gtk_widget_show (dialog);
1494 g_signal_connect (dialog, "response",
1495 G_CALLBACK (gtk_widget_destroy), NULL);
1498 void
1499 empathy_roster_window_show_preferences (EmpathyRosterWindow *self,
1500 const gchar *tab)
1502 if (self->priv->preferences == NULL)
1504 self->priv->preferences = empathy_preferences_new (GTK_WINDOW (self),
1505 self->priv->shell_running);
1506 g_object_add_weak_pointer (G_OBJECT (self->priv->preferences),
1507 (gpointer) &self->priv->preferences);
1509 gtk_widget_show (self->priv->preferences);
1511 else
1513 gtk_window_present (GTK_WINDOW (self->priv->preferences));
1516 if (tab != NULL)
1517 empathy_preferences_show_tab (
1518 EMPATHY_PREFERENCES (self->priv->preferences), tab);
1521 static void
1522 roster_window_edit_preferences_cb (GSimpleAction *action,
1523 GVariant *parameter,
1524 gpointer user_data)
1526 EmpathyRosterWindow *self = user_data;
1528 empathy_roster_window_show_preferences (self, NULL);
1531 static void
1532 roster_window_help_about_cb (GSimpleAction *action,
1533 GVariant *parameter,
1534 gpointer user_data)
1536 EmpathyRosterWindow *self = user_data;
1538 empathy_about_dialog_new (GTK_WINDOW (self));
1541 static void
1542 roster_window_help_contents_cb (GSimpleAction *action,
1543 GVariant *parameter,
1544 gpointer user_data)
1546 EmpathyRosterWindow *self = user_data;
1548 empathy_url_show (GTK_WIDGET (self), "help:empathy");
1551 static void
1552 next_tab_cb (GSimpleAction *action,
1553 GVariant *parameter,
1554 gpointer user_data)
1556 EmpathyRosterWindow *self = user_data;
1557 empathy_chat_window_next_tab (EMPATHY_CHAT_WINDOW (self->priv->chat_window));
1560 static void
1561 prev_tab_cb (GSimpleAction *action,
1562 GVariant *parameter,
1563 gpointer user_data)
1565 EmpathyRosterWindow *self = user_data;
1566 empathy_chat_window_prev_tab (EMPATHY_CHAT_WINDOW (self->priv->chat_window));
1570 static gboolean
1571 roster_window_throbber_button_press_event_cb (GtkWidget *throbber,
1572 GdkEventButton *event,
1573 EmpathyRosterWindow *self)
1575 if (event->type != GDK_BUTTON_PRESS ||
1576 event->button != 1)
1577 return FALSE;
1579 empathy_accounts_dialog_show_application (
1580 gtk_widget_get_screen (GTK_WIDGET (throbber)),
1581 NULL, FALSE, FALSE);
1583 return FALSE;
1586 static void
1587 roster_window_account_removed_cb (TpAccountManager *manager,
1588 TpAccount *account,
1589 EmpathyRosterWindow *self)
1591 /* remove errors if any */
1592 roster_window_remove_error (self, account);
1594 /* remove the balance action if required */
1595 roster_window_remove_balance_action (self, account);
1598 static void
1599 account_connection_notify_cb (TpAccount *account,
1600 GParamSpec *spec,
1601 EmpathyRosterWindow *self)
1603 TpConnection *conn;
1605 conn = tp_account_get_connection (account);
1607 if (conn != NULL)
1609 roster_window_setup_balance (self, account);
1611 else
1613 /* remove balance action if required */
1614 roster_window_remove_balance_action (self, account);
1618 static void
1619 add_account (EmpathyRosterWindow *self,
1620 TpAccount *account)
1622 gulong handler_id;
1624 handler_id = GPOINTER_TO_UINT (g_hash_table_lookup (
1625 self->priv->status_changed_handlers, account));
1627 /* connect signal only if it was not connected yet */
1628 if (handler_id != 0)
1629 return;
1631 handler_id = g_signal_connect (account, "status-changed",
1632 G_CALLBACK (roster_window_connection_changed_cb), self);
1634 g_hash_table_insert (self->priv->status_changed_handlers,
1635 account, GUINT_TO_POINTER (handler_id));
1637 /* roster_window_setup_balance() relies on the TpConnection to be ready on
1638 * the TpAccount so we connect this signal as well. */
1639 tp_g_signal_connect_object (account, "notify::connection",
1640 G_CALLBACK (account_connection_notify_cb), self, 0);
1642 roster_window_setup_balance (self, account);
1645 /* @account: if not %NULL, the only account which can be enabled */
1646 static void
1647 display_page_account_not_enabled (EmpathyRosterWindow *self,
1648 TpAccount *account)
1650 if (account == NULL)
1652 display_page_message (self,
1653 _("You need to enable one of your accounts to see contacts here."),
1654 PAGE_MESSAGE_FLAG_ACCOUNTS);
1656 else
1658 gchar *tmp;
1660 /* translators: argument is an account name */
1661 tmp = g_strdup_printf (_("You need to enable %s to see contacts here."),
1662 tp_account_get_display_name (account));
1664 display_page_message (self, tmp, PAGE_MESSAGE_FLAG_ACCOUNTS);
1665 g_free (tmp);
1668 static gboolean
1669 has_enabled_account (GList *accounts)
1671 GList *l;
1673 for (l = accounts; l != NULL; l = g_list_next (l))
1675 TpAccount *account = l->data;
1677 if (tp_account_is_enabled (account))
1678 return TRUE;
1681 return FALSE;
1684 static void
1685 set_notebook_page (EmpathyRosterWindow *self)
1687 GList *accounts;
1688 guint len;
1689 TpConnectionPresenceType presence;
1690 gboolean connected, connecting;
1692 connected = empathy_account_manager_get_accounts_connected (&connecting);
1694 /* Display the loading page if either:
1695 * - We are fetching contacts from Folks (startup)
1696 * - There is no account connected but at least one is connecting
1698 if (!empathy_individual_manager_get_contacts_loaded (
1699 self->priv->individual_manager) ||
1700 (!connected && connecting))
1702 display_page_message (self, NULL, PAGE_MESSAGE_FLAG_SPINNER);
1703 gtk_spinner_start (GTK_SPINNER (self->priv->spinner_loading));
1704 return;
1707 gtk_spinner_stop (GTK_SPINNER (self->priv->spinner_loading));
1709 accounts = tp_account_manager_dup_valid_accounts (
1710 self->priv->account_manager);
1712 len = g_list_length (accounts);
1714 if (len == 0)
1716 /* No account */
1717 display_page_no_account (self);
1718 goto out;
1721 if (!has_enabled_account (accounts))
1723 TpAccount *account = NULL;
1725 /* Pass the account if there is only one which can be enabled */
1726 if (len == 1)
1727 account = accounts->data;
1729 display_page_account_not_enabled (self, account);
1730 goto out;
1733 presence = tp_account_manager_get_most_available_presence (
1734 self->priv->account_manager, NULL, NULL);
1736 if (presence == TP_CONNECTION_PRESENCE_TYPE_OFFLINE)
1738 // This message shows up always. WTF telepathy?
1739 // display_page_message (self,
1740 // _("Change your presence to see contacts here"),
1741 // PAGE_MESSAGE_FLAG_ONLINE);
1742 goto out;
1745 if (empathy_roster_view_is_empty (self->priv->view))
1747 if (empathy_roster_view_is_searching (self->priv->view))
1749 display_page_message (self, _("No match found"),
1750 PAGE_MESSAGE_FLAG_NONE);
1752 else
1754 if (g_settings_get_boolean (self->priv->gsettings_ui,
1755 EMPATHY_PREFS_UI_SHOW_OFFLINE))
1756 display_page_message (self, _("You haven’t added any contacts yet"),
1757 PAGE_MESSAGE_FLAG_ADD_CONTACT);
1758 else
1759 display_page_message (self, _("No online contacts"),
1760 PAGE_MESSAGE_FLAG_SHOW_OFFLINE);
1762 goto out;
1765 display_page_contact_list (self);
1767 out:
1768 g_list_free_full (accounts, g_object_unref);
1771 static void
1772 roster_window_account_validity_changed_cb (TpAccountManager *manager,
1773 TpAccount *account,
1774 gboolean valid,
1775 EmpathyRosterWindow *self)
1777 if (valid)
1778 add_account (self, account);
1779 else
1780 roster_window_account_removed_cb (manager, account, self);
1782 set_notebook_page (self);
1785 static void
1786 roster_window_connection_items_setup (EmpathyRosterWindow *self)
1788 guint i;
1789 const gchar *actions_connected[] = {
1790 "room-join-new",
1791 "room-join-favorites",
1792 "chat-new-message",
1793 "chat-new-call",
1794 "chat-search-contacts",
1795 "chat-add-contact",
1796 "edit-blocked-contacts",
1799 for (i = 0; i < G_N_ELEMENTS (actions_connected); i++)
1801 GAction *action;
1803 action = g_action_map_lookup_action (G_ACTION_MAP (self),
1804 actions_connected[i]);
1806 self->priv->actions_connected = g_list_prepend (
1807 self->priv->actions_connected, action);
1811 static void
1812 account_enabled_cb (TpAccountManager *manager,
1813 TpAccount *account,
1814 EmpathyRosterWindow *self)
1816 set_notebook_page (self);
1819 static void
1820 account_disabled_cb (TpAccountManager *manager,
1821 TpAccount *account,
1822 EmpathyRosterWindow *self)
1824 set_notebook_page (self);
1827 static void
1828 account_removed_cb (TpAccountManager *manager,
1829 TpAccount *account,
1830 EmpathyRosterWindow *self)
1832 set_notebook_page (self);
1835 static void
1836 account_manager_prepared_cb (GObject *source_object,
1837 GAsyncResult *result,
1838 gpointer user_data)
1840 GList *accounts, *j;
1841 TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
1842 EmpathyRosterWindow *self = user_data;
1843 GError *error = NULL;
1845 if (!tp_proxy_prepare_finish (manager, result, &error))
1847 DEBUG ("Failed to prepare account manager: %s", error->message);
1848 g_error_free (error);
1849 return;
1852 accounts = tp_account_manager_dup_valid_accounts (
1853 self->priv->account_manager);
1854 for (j = accounts; j != NULL; j = j->next)
1856 TpAccount *account = TP_ACCOUNT (j->data);
1858 add_account (self, account);
1861 g_signal_connect (manager, "account-validity-changed",
1862 G_CALLBACK (roster_window_account_validity_changed_cb), self);
1863 tp_g_signal_connect_object (manager, "account-removed",
1864 G_CALLBACK (account_removed_cb), self, 0);
1865 tp_g_signal_connect_object (manager, "account-disabled",
1866 G_CALLBACK (account_disabled_cb), self, 0);
1867 tp_g_signal_connect_object (manager, "account-enabled",
1868 G_CALLBACK (account_enabled_cb), self, 0);
1870 roster_window_update_status (self);
1872 set_notebook_page (self);
1874 g_list_free_full (accounts, g_object_unref);
1877 void
1878 empathy_roster_window_set_shell_running (EmpathyRosterWindow *self,
1879 gboolean shell_running)
1881 if (self->priv->shell_running == shell_running)
1882 return;
1884 self->priv->shell_running = shell_running;
1885 g_object_notify (G_OBJECT (self), "shell-running");
1888 static GObject *
1889 empathy_roster_window_constructor (GType type,
1890 guint n_construct_params,
1891 GObjectConstructParam *construct_params)
1893 static GObject *window = NULL;
1895 if (window != NULL)
1896 return g_object_ref (window);
1898 window = G_OBJECT_CLASS (empathy_roster_window_parent_class)->constructor (
1899 type, n_construct_params, construct_params);
1901 g_object_add_weak_pointer (window, (gpointer) &window);
1903 return window;
1906 static GActionEntry menubar_entries[] = {
1907 {"chat-new-message", roster_window_chat_new_message_cb},
1908 {"chat-new-call", roster_window_chat_new_call_cb},
1909 {"chat-add-contact", roster_window_chat_add_contact_cb},
1910 {"chat-search-contacts", roster_window_chat_search_contacts_cb},
1911 {"chat-quit", roster_window_chat_quit_cb},
1913 {"edit-accounts", roster_window_edit_accounts_cb},
1914 {"edit-blocked-contacts", roster_window_edit_blocked_contacts_cb},
1915 {"edit-preferences", roster_window_edit_preferences_cb},
1917 {"view-history", roster_window_view_history_cb},
1918 {"view-show-ft-manager", roster_window_view_show_ft_manager},
1920 {"room-join-new", roster_window_room_join_new_cb},
1921 {"room-join-favorites", roster_window_room_join_favorites_cb},
1922 {"join", roster_window_join_chatroom_menu_activate_cb, "(ss)"},
1923 {"room-manage-favorites", roster_window_room_manage_favorites_cb},
1925 {"help-contents", roster_window_help_contents_cb},
1926 {"help-about", roster_window_help_about_cb},
1929 static GActionEntry app_entries[] =
1931 { "tab-next", next_tab_cb },
1932 { "tab-prev", prev_tab_cb }
1936 static void
1937 empathy_roster_window_set_property (GObject *object,
1938 guint property_id,
1939 const GValue *value,
1940 GParamSpec *pspec)
1942 EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
1944 switch (property_id)
1946 case PROP_SHELL_RUNNING:
1947 self->priv->shell_running = g_value_get_boolean (value);
1948 break;
1949 default:
1950 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1951 break;
1955 static void
1956 empathy_roster_window_get_property (GObject *object,
1957 guint property_id,
1958 GValue *value,
1959 GParamSpec *pspec)
1961 EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
1963 switch (property_id)
1965 case PROP_SHELL_RUNNING:
1966 g_value_set_boolean (value, self->priv->shell_running);
1967 break;
1968 default:
1969 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1970 break;
1974 static void
1975 empathy_roster_window_constructed (GObject *self)
1977 G_OBJECT_CLASS (empathy_roster_window_parent_class)->constructed (self);
1980 static void
1981 empathy_roster_window_class_init (EmpathyRosterWindowClass *klass)
1983 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1984 GParamSpec *pspec;
1986 object_class->finalize = empathy_roster_window_finalize;
1987 object_class->constructor = empathy_roster_window_constructor;
1988 object_class->constructed = empathy_roster_window_constructed;
1990 object_class->set_property = empathy_roster_window_set_property;
1991 object_class->get_property = empathy_roster_window_get_property;
1993 pspec = g_param_spec_boolean ("shell-running",
1994 "Shell running",
1995 "Whether the Shell is running or not",
1996 FALSE,
1997 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1998 g_object_class_install_property (object_class, PROP_SHELL_RUNNING, pspec);
2000 g_type_class_add_private (object_class, sizeof (EmpathyRosterWindowPriv));
2003 static void
2004 contacts_loaded_cb (EmpathyIndividualManager *manager,
2005 EmpathyRosterWindow *self)
2007 set_notebook_page (self);
2010 static void
2011 roster_window_setup_actions (EmpathyRosterWindow *self)
2013 GAction *action;
2015 #define ADD_GSETTINGS_ACTION(schema, key) \
2016 action = g_settings_create_action (self->priv->gsettings_##schema, \
2017 EMPATHY_PREFS_##key); \
2018 g_action_map_add_action (G_ACTION_MAP (self), action); \
2019 g_object_unref (action);
2021 ADD_GSETTINGS_ACTION (ui, UI_SHOW_OFFLINE);
2023 #undef ADD_GSETTINGS_ACTION
2026 static void
2027 menu_deactivate_cb (GtkMenuShell *menushell,
2028 gpointer user_data)
2030 /* FIXME: we shouldn't have to disconnect the signal (bgo #641327) */
2031 g_signal_handlers_disconnect_by_func (menushell,
2032 menu_deactivate_cb, user_data);
2034 gtk_menu_detach (GTK_MENU (menushell));
2037 static void
2038 menu_item_activated_cb (GtkMenuShell *menushell,
2039 gpointer user_data)
2041 EmpathyRosterWindow *roster_window = EMPATHY_ROSTER_WINDOW (user_data);
2043 hide_search_bar (roster_window);
2046 static void
2047 popup_individual_menu_cb (EmpathyRosterView *view,
2048 const gchar *active_group,
2049 FolksIndividual *individual,
2050 guint button,
2051 guint time,
2052 gpointer user_data)
2054 GtkWidget *menu;
2055 EmpathyIndividualFeatureFlags features = EMPATHY_INDIVIDUAL_FEATURE_CHAT |
2056 EMPATHY_INDIVIDUAL_FEATURE_CALL |
2057 EMPATHY_INDIVIDUAL_FEATURE_EDIT |
2058 EMPATHY_INDIVIDUAL_FEATURE_INFO |
2059 EMPATHY_INDIVIDUAL_FEATURE_LOG |
2060 EMPATHY_INDIVIDUAL_FEATURE_SMS |
2061 EMPATHY_INDIVIDUAL_FEATURE_CALL_PHONE |
2062 EMPATHY_INDIVIDUAL_FEATURE_REMOVE |
2063 EMPATHY_INDIVIDUAL_FEATURE_FILE_TRANSFER;
2065 menu = empathy_individual_menu_new (individual, active_group,
2066 features, NULL);
2068 /* menu is initially unowned but gtk_menu_attach_to_widget() takes its
2069 * floating ref. We can either wait for the view to release its ref
2070 * when it is destroyed (when leaving Empathy) or explicitly
2071 * detach the menu when it's not displayed any more.
2072 * We go for the latter as we don't want to keep useless menus in memory
2073 * during the whole lifetime of Empathy. */
2074 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
2075 NULL);
2076 g_signal_connect (menu, "menu-item-activated",
2077 G_CALLBACK (menu_item_activated_cb), user_data);
2079 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
2080 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
2083 static void
2084 view_empty_cb (EmpathyRosterView *view,
2085 GParamSpec *spec,
2086 EmpathyRosterWindow *self)
2088 set_notebook_page (self);
2090 if (!empathy_roster_view_is_empty (view))
2092 gtk_widget_grab_focus (GTK_WIDGET (self->priv->view));
2094 /* The store is being filled, it will be done after an idle cb.
2095 * So we can then get events. If we do that too soon, event's
2096 * contact is not yet in the store and it won't get marked as
2097 * having events. */
2098 g_idle_add (roster_window_load_events_idle_cb, self);
2102 static void
2103 tooltip_destroy_cb (GtkWidget *widget,
2104 EmpathyRosterWindow *self)
2106 g_clear_object (&self->priv->tooltip_widget);
2109 static gboolean
2110 individual_tooltip_cb (EmpathyRosterView *view,
2111 FolksIndividual *individual,
2112 gboolean keyboard_mode,
2113 GtkTooltip *tooltip,
2114 EmpathyRosterWindow *self)
2116 if (self->priv->tooltip_widget == NULL)
2118 self->priv->tooltip_widget = empathy_individual_widget_new (individual,
2119 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
2120 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
2121 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
2123 gtk_container_set_border_width (
2124 GTK_CONTAINER (self->priv->tooltip_widget), 8);
2126 g_object_ref (self->priv->tooltip_widget);
2128 tp_g_signal_connect_object (self->priv->tooltip_widget, "destroy",
2129 G_CALLBACK (tooltip_destroy_cb), self, 0);
2131 gtk_widget_show (self->priv->tooltip_widget);
2133 else
2135 empathy_individual_widget_set_individual (
2136 EMPATHY_INDIVIDUAL_WIDGET (self->priv->tooltip_widget), individual);
2139 gtk_tooltip_set_custom (tooltip, self->priv->tooltip_widget);
2141 return TRUE;
2144 typedef enum
2146 DND_DRAG_TYPE_INVALID = -1,
2147 DND_DRAG_TYPE_URI_LIST,
2148 } DndDragType;
2150 #define DRAG_TYPE(T,I) \
2151 { (gchar *) T, 0, I }
2153 static const GtkTargetEntry drag_types_dest[] = {
2154 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
2155 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
2158 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
2160 static DndDragType
2161 get_drag_type (GtkWidget *widget,
2162 GdkDragContext *context)
2164 GdkAtom target;
2165 guint i;
2167 target = gtk_drag_dest_find_target (widget, context, NULL);
2169 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
2171 if (target == drag_atoms_dest[i])
2172 return drag_types_dest[i].info;
2175 return DND_DRAG_TYPE_INVALID;
2178 static gboolean
2179 individual_supports_ft (FolksIndividual *individual)
2181 EmpathyContact *contact;
2182 EmpathyCapabilities caps;
2183 gboolean result;
2185 contact = empathy_contact_dup_from_folks_individual (individual);
2186 if (contact == NULL)
2187 return FALSE;
2189 caps = empathy_contact_get_capabilities (contact);
2190 result = (caps & EMPATHY_CAPABILITIES_FT);
2192 g_object_unref (contact);
2193 return result;
2196 static gboolean
2197 view_drag_motion_cb (GtkWidget *widget,
2198 GdkDragContext *context,
2199 gint x,
2200 gint y,
2201 guint time_,
2202 EmpathyRosterWindow *self)
2204 DndDragType type;
2206 type = get_drag_type (widget, context);
2208 if (type == DND_DRAG_TYPE_URI_LIST)
2210 /* Check if contact supports FT */
2211 FolksIndividual *individual;
2212 GtkListBoxRow *row;
2214 individual = empathy_roster_view_get_individual_at_y (self->priv->view,
2215 y, &row);
2216 if (individual == NULL)
2217 goto no_hl;
2219 if (!individual_supports_ft (individual))
2220 goto no_hl;
2222 gtk_list_box_drag_highlight_row (GTK_LIST_BOX (widget), row);
2223 return FALSE;
2226 no_hl:
2227 gtk_list_box_drag_unhighlight_row (GTK_LIST_BOX (widget));
2228 return FALSE;
2231 static gboolean
2232 view_drag_drop_cb (GtkWidget *widget,
2233 GdkDragContext *context,
2234 gint x,
2235 gint y,
2236 guint time_,
2237 EmpathyRosterWindow *self)
2239 DndDragType type;
2240 FolksIndividual *individual;
2242 type = get_drag_type (widget, context);
2243 if (type == DND_DRAG_TYPE_INVALID)
2244 return FALSE;
2246 individual = empathy_roster_view_get_individual_at_y (self->priv->view, y,
2247 NULL);
2248 if (individual == NULL)
2249 return FALSE;
2251 if (!individual_supports_ft (individual))
2252 return FALSE;
2254 gtk_drag_get_data (widget, context,
2255 gtk_drag_dest_find_target (widget, context, NULL), time_);
2257 return TRUE;
2260 static void
2261 view_drag_data_received_cb (GtkWidget *widget,
2262 GdkDragContext *context,
2263 gint x,
2264 gint y,
2265 GtkSelectionData *selection,
2266 guint info,
2267 guint time_,
2268 EmpathyRosterWindow *self)
2270 gboolean success = FALSE;
2272 if (selection == NULL)
2273 goto out;
2275 if (info == DND_DRAG_TYPE_URI_LIST)
2277 const gchar *path;
2278 FolksIndividual *individual;
2279 EmpathyContact *contact;
2281 individual = empathy_roster_view_get_individual_at_y (self->priv->view,
2282 y, NULL);
2283 g_return_if_fail (individual != NULL);
2285 path = (const gchar *) gtk_selection_data_get_data (selection);
2287 contact = empathy_contact_dup_from_folks_individual (individual);
2288 empathy_send_file_from_uri_list (contact, path);
2290 g_object_unref (contact);
2292 success = TRUE;
2295 out:
2296 gtk_drag_finish (context, success, FALSE, time_);
2299 static void
2300 roster_window_most_available_presence_changed_cb (TpAccountManager *manager,
2301 TpConnectionPresenceType presence,
2302 const gchar *status,
2303 const gchar *message,
2304 EmpathyRosterWindow *self)
2306 set_notebook_page (self);
2309 static void
2310 empathy_roster_window_init (EmpathyRosterWindow *self)
2312 GtkBuilder *gui;
2313 GtkWidget *sw;
2314 gchar *filename;
2315 GtkWidget *header_bar;
2316 GtkWidget *new_conversation_button;
2317 GtkWidget *image;
2318 GtkWidget *chat_vbox;
2319 guint i;
2320 EmpathyRosterModel *model;
2322 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2323 EMPATHY_TYPE_ROSTER_WINDOW, EmpathyRosterWindowPriv);
2325 empathy_set_css_provider (GTK_WIDGET (self));
2327 self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2329 self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2331 gtk_window_set_title (GTK_WINDOW (self), _("Contact List"));
2332 gtk_window_set_role (GTK_WINDOW (self), "contact_list");
2333 gtk_window_set_default_size (GTK_WINDOW (self), 225, 325);
2335 /* Set up interface */
2336 filename = empathy_file_lookup ("empathy-roster-window.ui", "src");
2337 gui = tpaw_builder_get_file (filename,
2338 "main_vbox", &self->priv->main_vbox,
2339 "chat_vbox", &chat_vbox,
2340 "balance_vbox", &self->priv->balance_vbox,
2341 "errors_vbox", &self->priv->errors_vbox,
2342 "auth_vbox", &self->priv->auth_vbox,
2343 "presence_toolbar", &self->priv->presence_toolbar,
2344 "notebook", &self->priv->notebook,
2345 "no_entry_label", &self->priv->no_entry_label,
2346 "roster_scrolledwindow", &sw,
2347 "button_account_settings", &self->priv->button_account_settings,
2348 // "button_online", &self->priv->button_online,
2349 "button_show_offline", &self->priv->button_show_offline,
2350 "button_add_contact", &self->priv->button_add_contact,
2351 "spinner_loading", &self->priv->spinner_loading,
2352 NULL);
2353 g_free (filename);
2355 header_bar = gtk_header_bar_new ();
2356 gtk_header_bar_set_title (GTK_HEADER_BAR(header_bar), _("Conversations"));
2357 gtk_header_bar_set_show_close_button (GTK_HEADER_BAR(header_bar), TRUE);
2359 image = gtk_image_new_from_icon_name ("list-add-symbolic", GTK_ICON_SIZE_BUTTON);
2360 new_conversation_button = gtk_button_new ();
2361 g_signal_connect (new_conversation_button, "clicked",
2362 G_CALLBACK (roster_window_chat_new_message_cb), self);
2363 gtk_button_set_image (GTK_BUTTON (new_conversation_button), image);
2364 gtk_widget_set_tooltip_text (new_conversation_button, _("New Conversation"));
2365 gtk_window_set_titlebar (GTK_WINDOW (self), header_bar);
2366 gtk_container_add (GTK_CONTAINER (header_bar), new_conversation_button);
2367 gtk_widget_show_all (header_bar);
2369 gtk_container_add (GTK_CONTAINER (self), self->priv->main_vbox);
2370 gtk_widget_show (self->priv->main_vbox);
2372 g_signal_connect (self, "key-press-event",
2373 G_CALLBACK (roster_window_key_press_event_cb), self);
2375 g_object_unref (gui);
2377 self->priv->account_manager = tp_account_manager_dup ();
2379 tp_proxy_prepare_async (self->priv->account_manager, NULL,
2380 account_manager_prepared_cb, self);
2382 self->priv->errors = g_hash_table_new_full (g_direct_hash, g_direct_equal,
2383 g_object_unref, NULL);
2385 self->priv->auths = g_hash_table_new (NULL, NULL);
2387 self->priv->status_changed_handlers = g_hash_table_new_full (g_direct_hash,
2388 g_direct_equal, NULL, NULL);
2390 /* set up accelerators */
2391 g_action_map_add_action_entries (G_ACTION_MAP (self),
2392 app_entries, G_N_ELEMENTS (app_entries), self);
2394 /* set up menus */
2395 g_action_map_add_action_entries (G_ACTION_MAP (self),
2396 menubar_entries, G_N_ELEMENTS (menubar_entries), self);
2397 roster_window_setup_actions (self);
2399 filename = empathy_file_lookup ("empathy-roster-window-menubar.ui", "src");
2400 gui = tpaw_builder_get_file (filename,
2401 "appmenu", &self->priv->menumodel,
2402 "rooms", &self->priv->rooms_section,
2403 NULL);
2404 g_free (filename);
2406 g_object_ref (self->priv->menumodel);
2407 g_object_ref (self->priv->rooms_section);
2409 /* Set up connection related actions. */
2410 roster_window_connection_items_setup (self);
2411 roster_window_favorite_chatroom_menu_setup (self);
2413 g_object_unref (gui);
2415 /* Set up contact list. */
2416 empathy_status_presets_get_all ();
2418 /* Set up presence chooser */
2419 self->priv->presence_chooser = empathy_presence_chooser_new ();
2420 gtk_widget_show (self->priv->presence_chooser);
2421 gtk_container_add (GTK_CONTAINER (self->priv->presence_toolbar), self->priv->presence_chooser);
2423 /* Set up the throbber */
2424 self->priv->throbber = gtk_spinner_new ();
2425 gtk_widget_set_size_request (self->priv->throbber, 16, -1);
2426 gtk_widget_set_events (self->priv->throbber, GDK_BUTTON_PRESS_MASK);
2427 g_signal_connect (self->priv->throbber, "button-press-event",
2428 G_CALLBACK (roster_window_throbber_button_press_event_cb),
2429 self);
2430 gtk_container_add (GTK_CONTAINER (self->priv->presence_toolbar), self->priv->throbber);
2432 self->priv->individual_manager = empathy_individual_manager_dup_singleton ();
2434 model = EMPATHY_ROSTER_MODEL (empathy_roster_model_manager_new (self->priv->individual_manager));
2436 tp_g_signal_connect_object (self->priv->individual_manager,
2437 "contacts-loaded", G_CALLBACK (contacts_loaded_cb), self, 0);
2439 self->priv->view = EMPATHY_ROSTER_VIEW (
2440 empathy_roster_view_new (model));
2443 g_object_unref (model);
2445 gtk_widget_show (GTK_WIDGET (self->priv->view));
2447 gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (self->priv->view));
2449 g_signal_connect (self->priv->view, "individual-activated",
2450 G_CALLBACK (individual_activated_cb), self);
2451 g_signal_connect (self->priv->view, "event-activated",
2452 G_CALLBACK (event_activated_cb), self);
2453 g_signal_connect (self->priv->view, "popup-individual-menu",
2454 G_CALLBACK (popup_individual_menu_cb), self);
2455 g_signal_connect (self->priv->view, "notify::empty",
2456 G_CALLBACK (view_empty_cb), self);
2457 g_signal_connect (self->priv->view, "individual-tooltip",
2458 G_CALLBACK (individual_tooltip_cb), self);
2460 /* DnD - destination */
2461 gtk_drag_dest_set (GTK_WIDGET (self->priv->view), GTK_DEST_DEFAULT_MOTION,
2462 drag_types_dest, G_N_ELEMENTS (drag_types_dest), GDK_ACTION_COPY);
2464 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
2465 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
2467 g_signal_connect (self->priv->view, "drag-motion",
2468 G_CALLBACK (view_drag_motion_cb), self);
2469 g_signal_connect (self->priv->view, "drag-drop",
2470 G_CALLBACK (view_drag_drop_cb), self);
2471 g_signal_connect (self->priv->view, "drag-data-received",
2472 G_CALLBACK (view_drag_data_received_cb), self);
2474 gtk_widget_set_has_tooltip (GTK_WIDGET (self->priv->view), TRUE);
2476 /* Set up search bar */
2477 self->priv->search_bar = tpaw_live_search_new (
2478 GTK_WIDGET (self->priv->view));
2479 empathy_roster_view_set_live_search (self->priv->view,
2480 TPAW_LIVE_SEARCH (self->priv->search_bar));
2482 g_signal_connect_swapped (self, "map",
2483 G_CALLBACK (gtk_widget_grab_focus), self->priv->view);
2485 /* Load user-defined accelerators. */
2486 roster_window_accels_load ();
2488 gtk_window_set_default_size (GTK_WINDOW (self), 900, 600);
2489 /* Set window size. */
2490 empathy_geometry_bind (GTK_WINDOW (self), GEOMETRY_NAME);
2492 self->priv->chat_window = GTK_WIDGET (empathy_chat_window_new ());
2493 gtk_widget_show (GTK_WIDGET (self->priv->chat_window) );
2494 gtk_container_add (GTK_CONTAINER (chat_vbox), self->priv->chat_window);
2496 /* Enable event handling */
2497 self->priv->call_observer = empathy_call_observer_dup_singleton ();
2498 self->priv->event_manager = empathy_event_manager_dup_singleton ();
2499 self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2501 self->priv->theme_manager = empathy_theme_manager_dup_singleton ();
2503 tp_g_signal_connect_object (self->priv->event_manager, "event-added",
2504 G_CALLBACK (roster_window_event_added_cb), self, 0);
2505 tp_g_signal_connect_object (self->priv->event_manager, "event-removed",
2506 G_CALLBACK (roster_window_event_removed_cb), self, 0);
2508 g_signal_connect (self->priv->account_manager, "account-validity-changed",
2509 G_CALLBACK (roster_window_account_validity_changed_cb), self);
2510 g_signal_connect (self->priv->account_manager, "account-removed",
2511 G_CALLBACK (roster_window_account_removed_cb), self);
2512 g_signal_connect (self->priv->account_manager, "account-disabled",
2513 G_CALLBACK (roster_window_account_disabled_cb), self);
2514 g_signal_connect (self->priv->account_manager,
2515 "most-available-presence-changed",
2516 G_CALLBACK (roster_window_most_available_presence_changed_cb), self);
2518 g_object_set (G_OBJECT (self->priv->view),
2519 "show-offline", TRUE,
2520 "show-groups", FALSE,
2521 NULL);
2523 g_settings_bind (self->priv->gsettings_ui, "show-balance-in-roster",
2524 self->priv->balance_vbox, "visible",
2525 G_SETTINGS_BIND_GET);
2527 g_signal_connect (self->priv->button_account_settings, "clicked",
2528 G_CALLBACK (button_account_settings_clicked_cb), self);
2529 // g_signal_connect (self->priv->button_online, "clicked",
2530 // G_CALLBACK (button_online_clicked_cb), self);
2531 g_signal_connect (self->priv->button_show_offline, "clicked",
2532 G_CALLBACK (button_show_offline_clicked_cb), self);
2533 g_signal_connect (self->priv->button_add_contact, "clicked",
2534 G_CALLBACK (button_add_contact_clicked_cb), self);
2537 GtkWidget *
2538 empathy_roster_window_new (GtkApplication *app)
2540 return g_object_new (EMPATHY_TYPE_ROSTER_WINDOW,
2541 "application", app,
2542 NULL);
2545 GMenuModel *
2546 empathy_roster_window_get_menu_model (EmpathyRosterWindow *self)
2548 g_return_val_if_fail (EMPATHY_IS_ROSTER_WINDOW (self), NULL);
2550 return G_MENU_MODEL (self->priv->menumodel);