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