]> git.0d.be Git - empathy.git/blob - src/empathy-status-icon.c
Merge remote-tracking branch 'origin/gnome-3-8'
[empathy.git] / src / empathy-status-icon.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007-2008 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library 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  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  */
21
22 #include "config.h"
23 #include "empathy-status-icon.h"
24
25 #include "empathy-event-manager.h"
26 #include "empathy-gsettings.h"
27 #include "empathy-new-call-dialog.h"
28 #include "empathy-new-message-dialog.h"
29 #include "empathy-presence-chooser.h"
30 #include "empathy-ui-utils.h"
31 #include "empathy-utils.h"
32
33 #define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER
34 #include "empathy-debug.h"
35
36 /* Number of ms to wait when blinking */
37 #define BLINK_TIMEOUT 500
38
39 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyStatusIcon)
40 typedef struct {
41         GtkStatusIcon       *icon;
42         TpAccountManager    *account_manager;
43         gboolean             showing_event_icon;
44         guint                blink_timeout;
45         EmpathyEventManager *event_manager;
46         EmpathyEvent        *event;
47         GSettings           *gsettings_ui;
48
49         GtkWidget           *window;
50         GtkUIManager        *ui_manager;
51         GtkWidget           *popup_menu;
52         GtkAction           *show_window_item;
53         GtkAction           *new_message_item;
54         GtkAction           *status_item;
55 } EmpathyStatusIconPriv;
56
57 G_DEFINE_TYPE (EmpathyStatusIcon, empathy_status_icon, G_TYPE_OBJECT);
58
59 static void
60 status_icon_update_tooltip (EmpathyStatusIcon *icon)
61 {
62         EmpathyStatusIconPriv *priv = GET_PRIV (icon);
63
64         if (priv->event) {
65                 gchar *tooltip = NULL;
66
67                 if (priv->event->message != NULL)
68                                 tooltip = g_markup_printf_escaped ("<i>%s</i>\n%s",
69                                                                    priv->event->header,
70                                                                    priv->event->message);
71                 else
72                                 tooltip = g_markup_printf_escaped ("<i>%s</i>",
73                                                                    priv->event->header);
74                 gtk_status_icon_set_tooltip_markup (priv->icon, tooltip);
75                 g_free (tooltip);
76         } else {
77                 TpConnectionPresenceType type;
78                 gchar *msg;
79
80                 type = tp_account_manager_get_most_available_presence (
81                         priv->account_manager, NULL, &msg);
82
83                 if (!EMP_STR_EMPTY (msg)) {
84                         gtk_status_icon_set_tooltip_text (priv->icon, msg);
85                 }
86                 else {
87                         gtk_status_icon_set_tooltip_text (priv->icon,
88                                                 empathy_presence_get_default_message (type));
89                 }
90
91                 g_free (msg);
92         }
93 }
94
95 static void
96 status_icon_update_icon (EmpathyStatusIcon *icon)
97 {
98         EmpathyStatusIconPriv *priv = GET_PRIV (icon);
99         const gchar           *icon_name;
100
101         if (priv->event && priv->showing_event_icon) {
102                 icon_name = priv->event->icon_name;
103         } else {
104                 TpConnectionPresenceType state;
105
106                 state = tp_account_manager_get_most_available_presence (
107                         priv->account_manager, NULL, NULL);
108
109                 /* An unset presence type here doesn't make sense. Force it
110                  * to be offline. */
111                 if (state == TP_CONNECTION_PRESENCE_TYPE_UNSET) {
112                         state = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
113                 }
114
115                 icon_name = empathy_icon_name_for_presence (state);
116         }
117
118         if (icon_name != NULL)
119                 gtk_status_icon_set_from_icon_name (priv->icon, icon_name);
120 }
121
122 static gboolean
123 status_icon_blink_timeout_cb (EmpathyStatusIcon *icon)
124 {
125         EmpathyStatusIconPriv *priv = GET_PRIV (icon);
126
127         priv->showing_event_icon = !priv->showing_event_icon;
128         status_icon_update_icon (icon);
129
130         return TRUE;
131 }
132 static void
133 status_icon_event_added_cb (EmpathyEventManager *manager,
134                             EmpathyEvent        *event,
135                             EmpathyStatusIcon   *icon)
136 {
137         EmpathyStatusIconPriv *priv = GET_PRIV (icon);
138
139         if (priv->event) {
140                 return;
141         }
142
143         DEBUG ("New event %p", event);
144
145         priv->event = event;
146         if (event->must_ack || event->type == EMPATHY_EVENT_TYPE_AUTH) {
147                 priv->showing_event_icon = TRUE;
148                 status_icon_update_icon (icon);
149                 status_icon_update_tooltip (icon);
150         }
151
152         if (!priv->blink_timeout && priv->showing_event_icon) {
153                 priv->blink_timeout = g_timeout_add (BLINK_TIMEOUT,
154                                                      (GSourceFunc) status_icon_blink_timeout_cb,
155                                                      icon);
156         }
157 }
158
159 static void
160 status_icon_event_removed_cb (EmpathyEventManager *manager,
161                               EmpathyEvent        *event,
162                               EmpathyStatusIcon   *icon)
163 {
164         EmpathyStatusIconPriv *priv = GET_PRIV (icon);
165
166         if (event != priv->event) {
167                 return;
168         }
169
170         priv->event = empathy_event_manager_get_top_event (priv->event_manager);
171
172         status_icon_update_tooltip (icon);
173         status_icon_update_icon (icon);
174
175         if (!priv->event && priv->blink_timeout) {
176                 g_source_remove (priv->blink_timeout);
177                 priv->blink_timeout = 0;
178         }
179 }
180
181 static void
182 status_icon_event_updated_cb (EmpathyEventManager *manager,
183                               EmpathyEvent        *event,
184                               EmpathyStatusIcon   *icon)
185 {
186         EmpathyStatusIconPriv *priv = GET_PRIV (icon);
187
188         if (event != priv->event) {
189                 return;
190         }
191
192         status_icon_update_tooltip (icon);
193 }
194
195 static void
196 status_icon_set_visibility (EmpathyStatusIcon *icon,
197                             gboolean           visible,
198                             gboolean           store)
199 {
200         EmpathyStatusIconPriv *priv = GET_PRIV (icon);
201
202         if (store) {
203                 g_settings_set_boolean (priv->gsettings_ui,
204                                         EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN,
205                                         !visible);
206         }
207
208         if (!visible) {
209                 gtk_widget_hide (priv->window);
210         } else {
211                 empathy_window_present (GTK_WINDOW (priv->window));
212         }
213 }
214
215 static void
216 status_icon_notify_visibility_cb (GSettings   *gsettings,
217                                   const gchar *key,
218                                   gpointer     user_data)
219 {
220         EmpathyStatusIcon *icon = user_data;
221         gboolean           hidden = FALSE;
222
223         hidden = g_settings_get_boolean (gsettings, key);
224         status_icon_set_visibility (icon, !hidden, FALSE);
225 }
226
227 static void
228 status_icon_toggle_visibility (EmpathyStatusIcon *icon)
229 {
230         EmpathyStatusIconPriv *priv = GET_PRIV (icon);
231         gboolean               visible;
232
233         visible = gtk_window_is_active (GTK_WINDOW (priv->window));
234         status_icon_set_visibility (icon, !visible, TRUE);
235 }
236
237 static void
238 status_icon_presence_changed_cb (EmpathyStatusIcon *icon)
239 {
240         status_icon_update_icon (icon);
241         status_icon_update_tooltip (icon);
242 }
243
244 static gboolean
245 status_icon_delete_event_cb (GtkWidget         *widget,
246                              GdkEvent          *event,
247                              EmpathyStatusIcon *icon)
248 {
249         status_icon_set_visibility (icon, FALSE, TRUE);
250         return TRUE;
251 }
252
253 static gboolean
254 status_icon_key_press_event_cb  (GtkWidget *window,
255                                  GdkEventKey *event,
256                                  EmpathyStatusIcon *icon)
257 {
258         if (event->keyval == GDK_KEY_Escape) {
259                 status_icon_set_visibility (icon, FALSE, TRUE);
260         }
261         return FALSE;
262 }
263
264 static void
265 status_icon_activate_cb (GtkStatusIcon     *status_icon,
266                          EmpathyStatusIcon *icon)
267 {
268         EmpathyStatusIconPriv *priv = GET_PRIV (icon);
269
270         DEBUG ("%s", priv->event ? "event" : "toggle");
271
272         if (priv->event) {
273                 empathy_event_activate (priv->event);
274         } else {
275                 status_icon_toggle_visibility (icon);
276         }
277 }
278
279 static void
280 status_icon_show_hide_window_cb (GtkToggleAction   *action,
281                                  EmpathyStatusIcon *icon)
282 {
283         gboolean visible;
284
285         visible = gtk_toggle_action_get_active (action);
286         status_icon_set_visibility (icon, visible, TRUE);
287 }
288
289 static void
290 status_icon_new_message_cb (GtkAction         *action,
291                             EmpathyStatusIcon *icon)
292 {
293         empathy_new_message_dialog_show (NULL);
294 }
295
296 static void
297 status_icon_new_call_cb (GtkAction         *action,
298                             EmpathyStatusIcon *icon)
299 {
300         empathy_new_call_dialog_show (NULL);
301 }
302
303 static void
304 status_icon_quit_cb (GtkAction         *action,
305                      EmpathyStatusIcon *icon)
306 {
307         EmpathyStatusIconPriv *priv = GET_PRIV (icon);
308
309         gtk_widget_destroy (priv->window);
310 }
311
312 static void
313 status_icon_popup_menu_cb (GtkStatusIcon     *status_icon,
314                            guint              button,
315                            guint              activate_time,
316                            EmpathyStatusIcon *icon)
317 {
318         EmpathyStatusIconPriv *priv = GET_PRIV (icon);
319         GtkWidget             *menu_item;
320         GtkWidget             *submenu;
321         gboolean               show;
322
323         show = gtk_widget_get_visible (priv->window);
324
325         g_signal_handlers_block_by_func (priv->show_window_item,
326                                          status_icon_show_hide_window_cb,
327                                          icon);
328         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_window_item),
329                                       show);
330         g_signal_handlers_unblock_by_func (priv->show_window_item,
331                                            status_icon_show_hide_window_cb,
332                                            icon);
333
334         menu_item = gtk_ui_manager_get_widget (priv->ui_manager, "/menu/status");
335         submenu = empathy_presence_chooser_create_menu ();
336         gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu);
337
338         gtk_menu_popup (GTK_MENU (priv->popup_menu),
339                         NULL, NULL,
340                         gtk_status_icon_position_menu,
341                         priv->icon,
342                         button,
343                         activate_time);
344 }
345
346 static void
347 status_icon_create_menu (EmpathyStatusIcon *icon)
348 {
349         EmpathyStatusIconPriv *priv = GET_PRIV (icon);
350         GtkBuilder            *gui;
351         gchar                 *filename;
352
353         filename = empathy_file_lookup ("empathy-status-icon.ui", "src");
354         gui = empathy_builder_get_file (filename,
355                                         "ui_manager", &priv->ui_manager,
356                                         "menu", &priv->popup_menu,
357                                         "show_list", &priv->show_window_item,
358                                         "new_message", &priv->new_message_item,
359                                         "status", &priv->status_item,
360                                        NULL);
361         g_free (filename);
362
363         empathy_builder_connect (gui, icon,
364                               "show_list", "toggled", status_icon_show_hide_window_cb,
365                               "new_message", "activate", status_icon_new_message_cb,
366                               "new_call", "activate", status_icon_new_call_cb,
367                               "quit", "activate", status_icon_quit_cb,
368                               NULL);
369
370         g_object_ref (priv->ui_manager);
371         g_object_unref (gui);
372 }
373
374 static void
375 status_icon_status_changed_cb (TpAccount *account,
376                                TpConnectionStatus current,
377                                TpConnectionStatus previous,
378                                TpConnectionStatusReason reason,
379                                gchar *dbus_error_name,
380                                GHashTable *details,
381                                EmpathyStatusIcon *icon)
382 {
383         EmpathyStatusIconPriv *priv = GET_PRIV (icon);
384
385         gtk_action_set_sensitive (priv->new_message_item,
386                                   empathy_account_manager_get_accounts_connected (NULL));
387 }
388
389 static void
390 status_icon_finalize (GObject *object)
391 {
392         EmpathyStatusIconPriv *priv = GET_PRIV (object);
393
394         if (priv->blink_timeout) {
395                 g_source_remove (priv->blink_timeout);
396         }
397
398         g_object_unref (priv->icon);
399         g_object_unref (priv->account_manager);
400         g_object_unref (priv->event_manager);
401         g_object_unref (priv->ui_manager);
402         g_object_unref (priv->gsettings_ui);
403         g_object_unref (priv->window);
404 }
405
406 static void
407 empathy_status_icon_class_init (EmpathyStatusIconClass *klass)
408 {
409         GObjectClass *object_class = G_OBJECT_CLASS (klass);
410
411         object_class->finalize = status_icon_finalize;
412
413         g_type_class_add_private (object_class, sizeof (EmpathyStatusIconPriv));
414 }
415
416 static void
417 account_manager_prepared_cb (GObject *source_object,
418                              GAsyncResult *result,
419                              gpointer user_data)
420 {
421         GList *list, *l;
422         TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
423         EmpathyStatusIcon *icon = user_data;
424         GError *error = NULL;
425
426         if (!tp_proxy_prepare_finish (account_manager, result, &error)) {
427                 DEBUG ("Failed to prepare account manager: %s", error->message);
428                 g_error_free (error);
429                 return;
430         }
431
432         list = tp_account_manager_dup_valid_accounts (account_manager);
433         for (l = list; l != NULL; l = l->next) {
434                 tp_g_signal_connect_object (l->data, "status-changed",
435                                              G_CALLBACK (status_icon_status_changed_cb),
436                                              icon, 0);
437         }
438         g_list_free_full (list, g_object_unref);
439
440         status_icon_presence_changed_cb (icon);
441 }
442
443 static void
444 empathy_status_icon_init (EmpathyStatusIcon *icon)
445 {
446         EmpathyStatusIconPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (icon,
447                 EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIconPriv);
448
449         icon->priv = priv;
450         priv->icon = gtk_status_icon_new ();
451         priv->account_manager = tp_account_manager_dup ();
452         priv->event_manager = empathy_event_manager_dup_singleton ();
453
454         tp_proxy_prepare_async (priv->account_manager, NULL,
455             account_manager_prepared_cb, icon);
456
457         /* make icon listen and respond to MAIN_WINDOW_HIDDEN changes */
458         priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
459         g_signal_connect (priv->gsettings_ui,
460                           "changed::" EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN,
461                           G_CALLBACK (status_icon_notify_visibility_cb),
462                           icon);
463
464         status_icon_create_menu (icon);
465
466         g_signal_connect_swapped (priv->account_manager,
467                                   "most-available-presence-changed",
468                                   G_CALLBACK (status_icon_presence_changed_cb),
469                                   icon);
470         g_signal_connect (priv->event_manager, "event-added",
471                           G_CALLBACK (status_icon_event_added_cb),
472                           icon);
473         g_signal_connect (priv->event_manager, "event-removed",
474                           G_CALLBACK (status_icon_event_removed_cb),
475                           icon);
476         g_signal_connect (priv->event_manager, "event-updated",
477                           G_CALLBACK (status_icon_event_updated_cb),
478                           icon);
479         g_signal_connect (priv->icon, "activate",
480                           G_CALLBACK (status_icon_activate_cb),
481                           icon);
482         g_signal_connect (priv->icon, "popup-menu",
483                           G_CALLBACK (status_icon_popup_menu_cb),
484                           icon);
485 }
486
487 EmpathyStatusIcon *
488 empathy_status_icon_new (GtkWindow *window, gboolean hide_contact_list)
489 {
490         EmpathyStatusIconPriv *priv;
491         EmpathyStatusIcon     *icon;
492         gboolean               should_hide;
493
494         g_return_val_if_fail (GTK_IS_WINDOW (window), NULL);
495
496         icon = g_object_new (EMPATHY_TYPE_STATUS_ICON, NULL);
497         priv = GET_PRIV (icon);
498
499         priv->window = g_object_ref (window);
500
501         g_signal_connect_after (priv->window, "key-press-event",
502                           G_CALLBACK (status_icon_key_press_event_cb),
503                           icon);
504
505         g_signal_connect (priv->window, "delete-event",
506                           G_CALLBACK (status_icon_delete_event_cb),
507                           icon);
508
509         if (!hide_contact_list) {
510                 should_hide = g_settings_get_boolean (priv->gsettings_ui,
511                         EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN);
512         } else {
513                 should_hide = TRUE;
514         }
515
516         status_icon_set_visibility (icon, !should_hide, FALSE);
517
518         return icon;
519 }
520