]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-status-icon.c
Prepare for slack time when coming back from auto away. Not yet fully
[empathy.git] / libempathy-gtk / empathy-status-icon.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007 Collabora Ltd.
4  *
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.
9  *
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.
14  *
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., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  * 
20  * Authors: Xavier Claessens <xclaesse@gmail.com>
21  */
22
23 #include <config.h>
24
25 #include <string.h>
26
27 #include <gtk/gtk.h>
28 #include <glade/glade.h>
29 #include <glib/gi18n.h>
30
31 #include <libmissioncontrol/mission-control.h>
32
33 #include <libempathy/empathy-contact-list.h>
34 #include <libempathy/empathy-contact-manager.h>
35 #include <libempathy/gossip-contact.h>
36 #include <libempathy/gossip-debug.h>
37 #include <libempathy/gossip-utils.h>
38 #include <libempathy/gossip-conf.h>
39 #include <libempathy/empathy-idle.h>
40
41 #include "empathy-status-icon.h"
42 #include "empathy-contact-dialogs.h"
43 #include "gossip-presence-chooser.h"
44 #include "gossip-preferences.h"
45 #include "gossip-ui-utils.h"
46 #include "gossip-accounts-dialog.h"
47
48
49 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
50                        EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIconPriv))
51
52 #define DEBUG_DOMAIN "StatusIcon"
53
54 /* Number of ms to wait when blinking */
55 #define BLINK_TIMEOUT 500
56
57 typedef struct _StatusIconEvent StatusIconEvent;
58
59 struct _EmpathyStatusIconPriv {
60         GtkStatusIcon         *icon;
61         EmpathyContactManager *manager;
62         EmpathyIdle           *idle;
63         GList                 *events;
64         GList                 *current_event;
65         StatusIconEvent       *flash_state_event;
66         guint                  blink_timeout;
67
68         GtkWindow             *window;
69         GtkWidget             *popup_menu;
70         GtkWidget             *show_window_item;
71         GtkWidget             *message_item;
72         GtkWidget             *status_item;
73 };
74
75 typedef void (*EventActivatedFunc) (StatusIconEvent *event);
76
77 struct _StatusIconEvent {
78         gchar              *icon_name;
79         gchar              *message;
80         EventActivatedFunc  func;
81         gpointer            user_data;
82 };
83
84
85 static void       empathy_status_icon_class_init  (EmpathyStatusIconClass *klass);
86 static void       empathy_status_icon_init        (EmpathyStatusIcon      *icon);
87 static void       status_icon_finalize            (GObject                *object);
88 static void       status_icon_idle_notify_cb      (EmpathyStatusIcon      *icon);
89 static void       status_icon_update_tooltip      (EmpathyStatusIcon      *icon);
90 static void       status_icon_set_from_state      (EmpathyStatusIcon      *icon);
91 static void       status_icon_toggle_visibility   (EmpathyStatusIcon      *icon);
92 static void       status_icon_activate_cb         (GtkStatusIcon          *status_icon,
93                                                    EmpathyStatusIcon      *icon);
94 static gboolean   status_icon_delete_event_cb     (GtkWidget              *widget,
95                                                    GdkEvent               *event,
96                                                    EmpathyStatusIcon      *icon);
97 static void       status_icon_popup_menu_cb       (GtkStatusIcon          *status_icon,
98                                                    guint                   button,
99                                                    guint                   activate_time,
100                                                    EmpathyStatusIcon      *icon);
101 static void       status_icon_create_menu         (EmpathyStatusIcon      *icon);
102 static void       status_icon_new_message_cb      (GtkWidget              *widget,
103                                                    EmpathyStatusIcon      *icon);
104 static void       status_icon_quit_cb             (GtkWidget              *window,
105                                                    EmpathyStatusIcon      *icon);
106 static void       status_icon_show_hide_window_cb (GtkWidget              *widget,
107                                                    EmpathyStatusIcon      *icon);
108 static void       status_icon_local_pending_cb    (EmpathyContactManager  *manager,
109                                                    GossipContact          *contact,
110                                                    gchar                  *message,
111                                                    EmpathyStatusIcon      *icon);
112 static void       status_icon_event_subscribe_cb  (StatusIconEvent        *event);
113 static void       status_icon_event_flash_state_cb (StatusIconEvent       *event);
114 static StatusIconEvent * status_icon_event_new    (EmpathyStatusIcon      *icon,
115                                                    const gchar            *icon_name,
116                                                    const gchar            *message);
117 static void       status_icon_event_remove        (EmpathyStatusIcon      *icon,
118                                                    StatusIconEvent        *event);
119 static gboolean   status_icon_event_timeout_cb    (EmpathyStatusIcon      *icon);
120 static void       status_icon_event_free          (StatusIconEvent        *event);
121
122 G_DEFINE_TYPE (EmpathyStatusIcon, empathy_status_icon, G_TYPE_OBJECT);
123
124 static void
125 empathy_status_icon_class_init (EmpathyStatusIconClass *klass)
126 {
127         GObjectClass *object_class = G_OBJECT_CLASS (klass);
128
129         object_class->finalize = status_icon_finalize;
130
131         g_type_class_add_private (object_class, sizeof (EmpathyStatusIconPriv));
132 }
133
134 static void
135 empathy_status_icon_init (EmpathyStatusIcon *icon)
136 {
137         EmpathyStatusIconPriv *priv;
138         GList                 *pending, *l;
139
140         priv = GET_PRIV (icon);
141
142         priv->icon = gtk_status_icon_new ();
143         priv->idle = empathy_idle_new ();
144         priv->manager = empathy_contact_manager_new ();
145
146         status_icon_create_menu (icon);
147         status_icon_idle_notify_cb (icon);
148
149         g_signal_connect_swapped (priv->idle, "notify",
150                                   G_CALLBACK (status_icon_idle_notify_cb),
151                                   icon);
152         g_signal_connect (priv->icon, "activate",
153                           G_CALLBACK (status_icon_activate_cb),
154                           icon);
155         g_signal_connect (priv->icon, "popup-menu",
156                           G_CALLBACK (status_icon_popup_menu_cb),
157                           icon);
158         g_signal_connect (priv->manager, "local-pending",
159                           G_CALLBACK (status_icon_local_pending_cb),
160                           icon);
161
162         pending = empathy_contact_list_get_local_pending (EMPATHY_CONTACT_LIST (priv->manager));
163         for (l = pending; l; l = l->next) {
164                 EmpathyContactListInfo *info;
165
166                 info = l->data;
167                 status_icon_local_pending_cb (priv->manager,
168                                               info->contact,
169                                               info->message,
170                                               icon);
171         }
172         g_list_free (pending);
173 }
174
175 static void
176 status_icon_finalize (GObject *object)
177 {
178         EmpathyStatusIconPriv *priv;
179
180         priv = GET_PRIV (object);
181
182         g_list_foreach (priv->events, (GFunc) status_icon_event_free, NULL);
183         g_list_free (priv->events);
184
185         if (priv->blink_timeout) {
186                 g_source_remove (priv->blink_timeout);
187         }
188
189         g_object_unref (priv->icon);
190         g_object_unref (priv->window);
191         g_object_unref (priv->idle);
192         g_object_unref (priv->manager);
193 }
194
195 EmpathyStatusIcon *
196 empathy_status_icon_new (GtkWindow *window)
197 {
198         EmpathyStatusIconPriv *priv;
199         EmpathyStatusIcon     *icon;
200         gboolean               should_hide;
201         gboolean               visible;
202
203         g_return_val_if_fail (GTK_IS_WINDOW (window), NULL);
204
205         icon = g_object_new (EMPATHY_TYPE_STATUS_ICON, NULL);
206         priv = GET_PRIV (icon);
207
208         priv->window = g_object_ref (window);
209
210         g_signal_connect (priv->window, "delete-event",
211                           G_CALLBACK (status_icon_delete_event_cb),
212                           icon);
213
214         gossip_conf_get_bool (gossip_conf_get (),
215                               GOSSIP_PREFS_UI_MAIN_WINDOW_HIDDEN,
216                               &should_hide);
217         visible = gossip_window_get_is_visible (window);
218
219         if ((!should_hide && !visible) || (should_hide && visible)) {
220                 status_icon_toggle_visibility (icon);
221         }
222
223         return icon;
224 }
225
226 static void
227 status_icon_idle_notify_cb (EmpathyStatusIcon *icon)
228 {
229         EmpathyStatusIconPriv *priv;
230         McPresence             flash_state;
231
232         priv = GET_PRIV (icon);
233
234         flash_state = empathy_idle_get_flash_state (priv->idle);
235         if (flash_state != MC_PRESENCE_UNSET) {
236                 const gchar *icon_name;
237
238                 icon_name = gossip_icon_name_for_presence_state (flash_state);
239                 if (!priv->flash_state_event) {
240                         /* We are now flashing */
241                         priv->flash_state_event = status_icon_event_new (icon, icon_name, NULL);
242                         priv->flash_state_event->user_data = icon;
243                         priv->flash_state_event->func = status_icon_event_flash_state_cb;
244
245                 } else {
246                         /* We are still flashing but with another state */
247                         g_free (priv->flash_state_event->icon_name);
248                         priv->flash_state_event->icon_name = g_strdup (icon_name);
249                 }
250         }
251         else if (priv->flash_state_event) {
252                 /* We are no more flashing */
253                 status_icon_event_remove (icon, priv->flash_state_event);
254                 priv->flash_state_event = NULL;
255         }
256
257         if (!priv->current_event) {
258                 status_icon_set_from_state (icon);
259         }
260
261         status_icon_update_tooltip (icon);
262 }
263
264 static void
265 status_icon_update_tooltip (EmpathyStatusIcon *icon)
266 {
267         EmpathyStatusIconPriv *priv;
268         const gchar           *tooltip = NULL;
269
270         priv = GET_PRIV (icon);
271
272         if (priv->events) {
273                 StatusIconEvent *event;
274
275                 event = priv->events->data;
276                 tooltip = event->message;
277         }
278
279         if (!tooltip) {
280                 tooltip = empathy_idle_get_status (priv->idle);
281         }
282
283         gtk_status_icon_set_tooltip (priv->icon, tooltip);      
284 }
285
286 static void
287 status_icon_set_from_state (EmpathyStatusIcon *icon)
288 {
289         EmpathyStatusIconPriv *priv;
290         McPresence             state;
291         const gchar           *icon_name;
292
293         priv = GET_PRIV (icon);
294
295         state = empathy_idle_get_state (priv->idle);
296         icon_name = gossip_icon_name_for_presence_state (state);
297         gtk_status_icon_set_from_icon_name (priv->icon, icon_name);
298 }
299
300 static void
301 status_icon_toggle_visibility (EmpathyStatusIcon *icon)
302 {
303         EmpathyStatusIconPriv *priv;
304         gboolean               visible;
305
306         priv = GET_PRIV (icon);
307
308         visible = gossip_window_get_is_visible (GTK_WINDOW (priv->window));
309
310         if (visible) {
311                 gtk_widget_hide (GTK_WIDGET (priv->window));
312                 gossip_conf_set_bool (gossip_conf_get (),
313                                       GOSSIP_PREFS_UI_MAIN_WINDOW_HIDDEN, TRUE);
314         } else {
315                 GList *accounts;
316
317                 gossip_window_present (GTK_WINDOW (priv->window), TRUE);
318                 gossip_conf_set_bool (gossip_conf_get (),
319                                       GOSSIP_PREFS_UI_MAIN_WINDOW_HIDDEN, FALSE);
320         
321                 /* Show the accounts dialog if there is no enabled accounts */
322                 accounts = mc_accounts_list_by_enabled (TRUE);
323                 if (accounts) {
324                         mc_accounts_list_free (accounts);
325                 } else {
326                         gossip_debug (DEBUG_DOMAIN,
327                                       "No enabled account, Showing account dialog");
328                         gossip_accounts_dialog_show (GTK_WINDOW (priv->window));
329                 }
330         }
331 }
332
333 static void
334 status_icon_activate_cb (GtkStatusIcon     *status_icon,
335                          EmpathyStatusIcon *icon)
336 {
337         EmpathyStatusIconPriv *priv;
338
339         priv = GET_PRIV (icon);
340
341         if (priv->events) {
342                 status_icon_event_remove (icon, priv->events->data);
343         } else {
344                 status_icon_toggle_visibility (icon);
345         }
346 }
347
348 static gboolean
349 status_icon_delete_event_cb (GtkWidget         *widget,
350                              GdkEvent          *event,
351                              EmpathyStatusIcon *icon)
352 {
353         status_icon_toggle_visibility (icon);
354
355         return TRUE;
356 }
357
358 static void
359 status_icon_popup_menu_cb (GtkStatusIcon     *status_icon,
360                            guint              button,
361                            guint              activate_time,
362                            EmpathyStatusIcon *icon)
363 {
364         EmpathyStatusIconPriv *priv;
365         GtkWidget             *submenu;
366         gboolean               show;
367
368         priv = GET_PRIV (icon);
369
370         show = gossip_window_get_is_visible (GTK_WINDOW (priv->window));
371
372         g_signal_handlers_block_by_func (priv->show_window_item,
373                                          status_icon_show_hide_window_cb,
374                                          icon);
375         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->show_window_item),
376                                         show);
377         g_signal_handlers_unblock_by_func (priv->show_window_item,
378                                            status_icon_show_hide_window_cb,
379                                            icon);
380
381         submenu = gossip_presence_chooser_create_menu ();
382         gtk_menu_item_set_submenu (GTK_MENU_ITEM (priv->status_item),
383                                    submenu);
384
385         gtk_menu_popup (GTK_MENU (priv->popup_menu),
386                         NULL, NULL,
387                         gtk_status_icon_position_menu,
388                         priv->icon,
389                         button,
390                         activate_time);
391 }
392
393 static void
394 status_icon_create_menu (EmpathyStatusIcon *icon)
395 {
396         EmpathyStatusIconPriv *priv;
397         GladeXML              *glade;
398
399         priv = GET_PRIV (icon);
400
401         glade = gossip_glade_get_file ("empathy-status-icon.glade",
402                                        "tray_menu",
403                                        NULL,
404                                        "tray_menu", &priv->popup_menu,
405                                        "tray_show_list", &priv->show_window_item,
406                                        "tray_new_message", &priv->message_item,
407                                        "tray_status", &priv->status_item,
408                                        NULL);
409
410         gossip_glade_connect (glade,
411                               icon,
412                               "tray_new_message", "activate", status_icon_new_message_cb,
413                               "tray_quit", "activate", status_icon_quit_cb,
414                               NULL);
415
416         g_signal_connect (priv->show_window_item, "toggled",
417                           G_CALLBACK (status_icon_show_hide_window_cb),
418                           icon);
419
420         g_object_unref (glade);
421 }
422
423 static void
424 status_icon_new_message_cb (GtkWidget         *widget,
425                             EmpathyStatusIcon *icon)
426 {
427         EmpathyStatusIconPriv *priv;
428
429         priv = GET_PRIV (icon);
430
431         //gossip_new_message_dialog_show (GTK_WINDOW (priv->window));
432 }
433
434 static void
435 status_icon_quit_cb (GtkWidget         *window,
436                      EmpathyStatusIcon *icon)
437 {
438         gtk_main_quit ();
439 }
440
441 static void
442 status_icon_show_hide_window_cb (GtkWidget         *widget,
443                                  EmpathyStatusIcon *icon)
444 {
445         status_icon_toggle_visibility (icon);
446 }
447
448 static void
449 status_icon_local_pending_cb (EmpathyContactManager *manager,
450                               GossipContact         *contact,
451                               gchar                 *message,
452                               EmpathyStatusIcon     *icon)
453 {
454         EmpathyStatusIconPriv *priv;
455         StatusIconEvent       *event;
456         gchar                 *str;
457         GList                 *l;
458
459         priv = GET_PRIV (icon);
460
461         for (l = priv->events; l; l = l->next) {
462                 if (gossip_contact_equal (contact, ((StatusIconEvent*)l->data)->user_data)) {
463                         return;
464                 }
465         }
466
467         str = g_strdup_printf (_("Subscription requested for %s\n"
468                                  "Message: %s"),
469                                gossip_contact_get_name (contact),
470                                message);
471
472         event = status_icon_event_new (icon, GTK_STOCK_DIALOG_QUESTION, str);
473         event->user_data = g_object_ref (contact);
474         event->func = status_icon_event_subscribe_cb;
475
476         g_free (str);
477 }
478
479 static void
480 status_icon_event_subscribe_cb (StatusIconEvent *event)
481 {
482         GossipContact *contact;
483
484         contact = GOSSIP_CONTACT (event->user_data);
485
486         empathy_subscription_dialog_show (contact, NULL);
487
488         g_object_unref (contact);
489 }
490
491 static void
492 status_icon_event_flash_state_cb (StatusIconEvent *event)
493 {
494         EmpathyStatusIconPriv *priv;
495
496         priv = GET_PRIV (event->user_data);
497
498         empathy_idle_set_flash_state (priv->idle, MC_PRESENCE_UNSET);
499 }
500
501
502 static StatusIconEvent *
503 status_icon_event_new (EmpathyStatusIcon *icon,
504                        const gchar       *icon_name,
505                        const gchar       *message)
506 {
507         EmpathyStatusIconPriv *priv;
508         StatusIconEvent       *event;
509
510         priv = GET_PRIV (icon);
511
512         event = g_slice_new0 (StatusIconEvent);
513         event->icon_name = g_strdup (icon_name);        
514         event->message = g_strdup (message);
515
516         priv->events = g_list_append (priv->events, event);
517         if (!priv->blink_timeout) {
518                 priv->current_event = NULL;
519                 priv->blink_timeout = g_timeout_add (BLINK_TIMEOUT,
520                                                      (GSourceFunc) status_icon_event_timeout_cb,
521                                                      icon);
522                 status_icon_event_timeout_cb (icon);
523         }
524
525         return event;
526 }
527
528 static void
529 status_icon_event_remove (EmpathyStatusIcon *icon,
530                           StatusIconEvent   *event)
531 {
532         EmpathyStatusIconPriv *priv;
533
534         priv = GET_PRIV (icon);
535
536         if (event->func) {
537                 event->func (event);
538         }
539         priv->events = g_list_remove (priv->events, event);
540         status_icon_event_free (event);
541         priv->current_event = NULL;
542         status_icon_update_tooltip (icon);
543         status_icon_set_from_state (icon);
544
545         if (priv->events) {
546                 return;
547         }
548
549         if (priv->blink_timeout) {
550                 g_source_remove (priv->blink_timeout);
551                 priv->blink_timeout = 0;
552         }
553 }
554
555 static gboolean
556 status_icon_event_timeout_cb (EmpathyStatusIcon *icon)
557 {
558         EmpathyStatusIconPriv *priv;
559
560         priv = GET_PRIV (icon);
561
562         if (priv->current_event) {
563                 priv->current_event = priv->current_event->next;
564         } else {
565                 priv->current_event = priv->events;
566         }
567
568         if (!priv->current_event) {
569                 status_icon_set_from_state (icon);
570         } else {
571                 StatusIconEvent *event;
572
573                 event = priv->current_event->data;
574                 gtk_status_icon_set_from_icon_name (priv->icon, event->icon_name);
575         }
576         status_icon_update_tooltip (icon);
577
578         return TRUE;
579 }
580
581 static void
582 status_icon_event_free (StatusIconEvent *event)
583 {
584         g_free (event->icon_name);
585         g_free (event->message);
586         g_slice_free (StatusIconEvent, event);
587 }
588