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