]> git.0d.be Git - empathy.git/blob - libempathy-gtk/gossip-presence-chooser.c
556bcf1374f9f0996d84f6f72a57fdbe9d90eb0d
[empathy.git] / libempathy-gtk / gossip-presence-chooser.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2005-2007 Imendio AB
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: Richard Hult <richard@imendio.com>
21  *          Martyn Russell <martyn@imendio.com>
22  */
23
24 #include "config.h"
25
26 #include <string.h>
27 #include <stdlib.h>
28
29 #include <glib/gi18n.h>
30 #include <gtk/gtk.h>
31 #include <glade/glade.h>
32
33 #include <libtelepathy/tp-helpers.h>
34 #include <libmissioncontrol/mission-control.h>
35
36 #include <libempathy/gossip-utils.h>
37 #include <libempathy/gossip-debug.h>
38 #include <libempathy/empathy-marshal.h>
39
40 #include "gossip-ui-utils.h"
41 #include "empathy-images.h"
42 #include "gossip-presence-chooser.h"
43 #include "gossip-status-presets.h"
44
45 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_PRESENCE_CHOOSER, GossipPresenceChooserPriv))
46
47 #define DEBUG_DOMAIN "PresenceChooser"
48
49 /* Flashing delay for icons (milliseconds). */
50 #define FLASH_TIMEOUT 500
51
52 typedef struct {
53         MissionControl *mc;
54
55         GtkWidget      *hbox;
56         GtkWidget      *image;
57         GtkWidget      *label;
58         GtkWidget      *menu;
59
60         McPresence      last_state;
61
62         McPresence      flash_state_1;
63         McPresence      flash_state_2;
64         guint           flash_timeout_id;
65
66         /* The handle the kind of unnessecary scroll support. */
67         guint           scroll_timeout_id;
68         McPresence      scroll_state;
69         gchar          *scroll_status;
70 } GossipPresenceChooserPriv;
71
72 typedef struct {
73         McPresence   state;
74         const gchar *status;
75 } StateAndStatus;
76
77 /* States to be listed in the menu */
78 static McPresence states[] = {MC_PRESENCE_AVAILABLE,
79                               MC_PRESENCE_DO_NOT_DISTURB,
80                               MC_PRESENCE_AWAY};
81
82 static void            gossip_presence_chooser_class_init      (GossipPresenceChooserClass *klass);
83 static void            gossip_presence_chooser_init            (GossipPresenceChooser      *chooser);
84 static void            presence_chooser_finalize               (GObject                    *object);
85 static void            presence_chooser_presence_changed_cb    (MissionControl             *mc,
86                                                                 McPresence                  state,
87                                                                 GossipPresenceChooser      *chooser);
88 static void            presence_chooser_reset_scroll_timeout   (GossipPresenceChooser      *chooser);
89 static gboolean        presence_chooser_scroll_timeout_cb      (GossipPresenceChooser      *chooser);
90 static gboolean        presence_chooser_scroll_event_cb        (GossipPresenceChooser      *chooser,
91                                                                 GdkEventScroll             *event,
92                                                                 gpointer                    user_data);
93 static GList *         presence_chooser_get_presets            (GossipPresenceChooser      *chooser);
94 static StateAndStatus *presence_chooser_state_and_status_new   (McPresence                  state,
95                                                                 const gchar                *status);
96 static gboolean        presence_chooser_flash_timeout_cb       (GossipPresenceChooser      *chooser);
97 void                   gossip_presence_chooser_flash_start     (GossipPresenceChooser      *chooser,
98                                                                 McPresence                  state_1,
99                                                                 McPresence                  state_2);
100 void                   gossip_presence_chooser_flash_stop      (GossipPresenceChooser      *chooser,
101                                                                 McPresence                  state);
102 gboolean               gossip_presence_chooser_is_flashing     (GossipPresenceChooser      *chooser);
103 static gboolean        presence_chooser_button_press_event_cb  (GtkWidget                  *chooser,
104                                                                 GdkEventButton             *event,
105                                                                 gpointer                    user_data);
106 static void            presence_chooser_toggled_cb             (GtkWidget                  *chooser,
107                                                                 gpointer                    user_data);
108 static void            presence_chooser_menu_popup             (GossipPresenceChooser      *chooser);
109 static void            presence_chooser_menu_popdown           (GossipPresenceChooser      *chooser);
110 static void            presence_chooser_menu_selection_done_cb (GtkMenuShell               *menushell,
111                                                                 GossipPresenceChooser      *chooser);
112 static void            presence_chooser_menu_destroy_cb        (GtkWidget                  *menu,
113                                                                 GossipPresenceChooser      *chooser);
114 static void            presence_chooser_menu_detach            (GtkWidget                  *attach_widget,
115                                                                 GtkMenu                    *menu);
116 static void            presence_chooser_menu_align_func        (GtkMenu                    *menu,
117                                                                 gint                       *x,
118                                                                 gint                       *y,
119                                                                 gboolean                   *push_in,
120                                                                 GtkWidget                  *widget);
121 static void            presence_chooser_menu_add_item          (GtkWidget                  *menu,
122                                                                 const gchar                *str,
123                                                                 McPresence                  state,
124                                                                 gboolean                    custom);
125 static void            presence_chooser_clear_activate_cb      (GtkWidget                  *item,
126                                                                 gpointer                    user_data);
127 static void            presence_chooser_clear_response_cb      (GtkWidget                  *widget,
128                                                                 gint                        response,
129                                                                 gpointer                    user_data);
130 static void            presence_chooser_noncustom_activate_cb  (GtkWidget                  *item,
131                                                                 gpointer                    user_data);
132 static void            presence_chooser_set_state              (McPresence                  state,
133                                                                 const gchar                *status,
134                                                                 gboolean                    save);
135 static void            presence_chooser_custom_activate_cb     (GtkWidget                  *item,
136                                                                 gpointer                    user_data);
137 static void            presence_chooser_show_dialog            (McPresence                  state);
138 static void            presence_chooser_dialog_response_cb     (GtkWidget                  *dialog,
139                                                                 gint                        response,
140                                                                 gpointer                    user_data);
141
142 G_DEFINE_TYPE (GossipPresenceChooser, gossip_presence_chooser, GTK_TYPE_TOGGLE_BUTTON);
143
144 static void
145 gossip_presence_chooser_class_init (GossipPresenceChooserClass *klass)
146 {
147         GObjectClass *object_class = G_OBJECT_CLASS (klass);
148
149         object_class->finalize = presence_chooser_finalize;
150
151         g_type_class_add_private (object_class, sizeof (GossipPresenceChooserPriv));
152 }
153
154 static void
155 gossip_presence_chooser_init (GossipPresenceChooser *chooser)
156 {
157         GossipPresenceChooserPriv *priv;
158         GtkWidget                 *arrow;
159         GtkWidget                 *alignment;
160         McPresence                 state;
161
162         priv = GET_PRIV (chooser);
163
164         gtk_button_set_relief (GTK_BUTTON (chooser), GTK_RELIEF_NONE);
165         gtk_button_set_focus_on_click (GTK_BUTTON (chooser), FALSE);
166
167         alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
168         gtk_widget_show (alignment);
169         gtk_container_add (GTK_CONTAINER (chooser), alignment);
170         gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 1, 0);
171
172         priv->hbox = gtk_hbox_new (FALSE, 1);
173         gtk_widget_show (priv->hbox);
174         gtk_container_add (GTK_CONTAINER (alignment), priv->hbox);
175
176         priv->image = gtk_image_new ();
177         gtk_widget_show (priv->image);
178         gtk_box_pack_start (GTK_BOX (priv->hbox), priv->image, FALSE, TRUE, 0);
179
180         priv->label = gtk_label_new (NULL);
181         gtk_widget_show (priv->label);
182         gtk_box_pack_start (GTK_BOX (priv->hbox), priv->label, TRUE, TRUE, 0);
183         gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END);
184         gtk_misc_set_alignment (GTK_MISC (priv->label), 0, 0.5);
185         gtk_misc_set_padding (GTK_MISC (priv->label), 4, 1);
186
187         alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
188         gtk_widget_show (alignment);
189         gtk_box_pack_start (GTK_BOX (priv->hbox), alignment, FALSE, FALSE, 0);
190
191         arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
192         gtk_widget_show (arrow);
193         gtk_container_add (GTK_CONTAINER (alignment), arrow);
194
195         g_signal_connect (chooser, "toggled",
196                           G_CALLBACK (presence_chooser_toggled_cb),
197                           NULL);
198         g_signal_connect (chooser, "button-press-event",
199                           G_CALLBACK (presence_chooser_button_press_event_cb),
200                           NULL);
201         g_signal_connect (chooser, "scroll-event",
202                           G_CALLBACK (presence_chooser_scroll_event_cb),
203                           NULL);
204
205         priv->mc = mission_control_new (tp_get_bus ());
206         state = mission_control_get_presence_actual (priv->mc, NULL);
207         presence_chooser_presence_changed_cb (priv->mc, state, chooser);
208         dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->mc),
209                                      "PresenceStatusActual",
210                                      G_CALLBACK (presence_chooser_presence_changed_cb),
211                                      chooser, NULL);
212 }
213
214 static void
215 presence_chooser_finalize (GObject *object)
216 {
217         GossipPresenceChooserPriv *priv;
218
219         priv = GET_PRIV (object);
220
221         if (priv->flash_timeout_id) {
222                 g_source_remove (priv->flash_timeout_id);
223         }
224
225         if (priv->scroll_timeout_id) {
226                 g_source_remove (priv->scroll_timeout_id);
227         }
228
229         dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->mc),
230                                         "PresenceStatusActual",
231                                         G_CALLBACK (presence_chooser_presence_changed_cb),
232                                         object);
233         g_object_unref (priv->mc);
234
235         G_OBJECT_CLASS (gossip_presence_chooser_parent_class)->finalize (object);
236 }
237
238 GtkWidget *
239 gossip_presence_chooser_new (void)
240 {
241         GtkWidget *chooser;
242
243         chooser = g_object_new (GOSSIP_TYPE_PRESENCE_CHOOSER, NULL);
244
245         return chooser;
246 }
247
248 static void
249 presence_chooser_presence_changed_cb (MissionControl        *mc,
250                                       McPresence             state,
251                                       GossipPresenceChooser *chooser)
252 {
253         GossipPresenceChooserPriv *priv;
254         gchar                     *status;
255
256         priv = GET_PRIV (chooser);
257
258         status = mission_control_get_presence_message_actual (priv->mc, NULL);
259         if (G_STR_EMPTY (status)) {
260                 g_free (status);
261                 status = g_strdup (gossip_presence_state_get_default_status (state));
262         }
263
264         gossip_debug (DEBUG_DOMAIN, "Presence changed to %s (%d)",
265                       status, state);
266
267         presence_chooser_reset_scroll_timeout (chooser);
268         gossip_presence_chooser_flash_stop (chooser, state);
269         gtk_label_set_text (GTK_LABEL (priv->label), status);
270
271         g_free (status);
272 }
273
274 static void
275 presence_chooser_reset_scroll_timeout (GossipPresenceChooser *chooser)
276 {
277         GossipPresenceChooserPriv *priv;
278
279         priv = GET_PRIV (chooser);
280
281         if (priv->scroll_timeout_id) {
282                 g_source_remove (priv->scroll_timeout_id);
283                 priv->scroll_timeout_id = 0;
284         }
285
286         g_free (priv->scroll_status);
287         priv->scroll_status = NULL;
288 }
289
290 static gboolean
291 presence_chooser_scroll_timeout_cb (GossipPresenceChooser *chooser)
292 {
293         GossipPresenceChooserPriv *priv;
294
295         priv = GET_PRIV (chooser);
296
297         priv->scroll_timeout_id = 0;
298
299         gossip_debug (DEBUG_DOMAIN, "Setting presence to %s (%d)",
300                       priv->scroll_status, priv->scroll_state);
301
302         mission_control_set_presence (priv->mc,
303                                       priv->scroll_state,
304                                       priv->scroll_status,
305                                       NULL, NULL);
306
307         g_free (priv->scroll_status);
308         priv->scroll_status = NULL;
309
310         return FALSE;
311 }
312
313 static gboolean
314 presence_chooser_scroll_event_cb (GossipPresenceChooser *chooser,
315                                   GdkEventScroll        *event,
316                                   gpointer               user_data)
317 {
318         GossipPresenceChooserPriv *priv;
319         GList                     *list, *l;
320         const gchar               *current_status;
321         StateAndStatus            *sas;
322         gboolean                   match;
323
324         priv = GET_PRIV (chooser);
325
326         switch (event->direction) {
327         case GDK_SCROLL_UP:
328                 break;
329         case GDK_SCROLL_DOWN:
330                 break;
331         default:
332                 return FALSE;
333         }
334
335         current_status = gtk_label_get_text (GTK_LABEL (priv->label));
336
337         /* Get the list of presets, which in this context means all the items
338          * without a trailing "...".
339          */
340         list = presence_chooser_get_presets (chooser);
341         sas = NULL;
342         match = FALSE;
343         for (l = list; l; l = l->next) {
344                 sas = l->data;
345
346                 if (sas->state == priv->last_state &&
347                     strcmp (sas->status, current_status) == 0) {
348                         sas = NULL;
349                         match = TRUE;
350                         if (event->direction == GDK_SCROLL_UP) {
351                                 if (l->prev) {
352                                         sas = l->prev->data;
353                                 }
354                         }
355                         else if (event->direction == GDK_SCROLL_DOWN) {
356                                 if (l->next) {
357                                         sas = l->next->data;
358                                 }
359                         }
360                         break;
361                 }
362
363                 sas = NULL;
364         }
365
366         if (sas) {
367                 presence_chooser_reset_scroll_timeout (chooser);
368
369                 priv->scroll_status = g_strdup (sas->status);
370                 priv->scroll_state = sas->state;
371
372                 priv->scroll_timeout_id =
373                         g_timeout_add (500,
374                                        (GSourceFunc) presence_chooser_scroll_timeout_cb,
375                                        chooser);
376
377                 gossip_presence_chooser_flash_stop (chooser, sas->state);
378                 gtk_label_set_text (GTK_LABEL (priv->label), sas->status);      
379         }
380         else if (!match) {
381                 const gchar *status;
382                 /* If we didn't get any match at all, it means the last state
383                  * was a custom one. Just switch to the first one.
384                  */
385                 status = gossip_presence_state_get_default_status (states[0]);
386
387                 gossip_debug (DEBUG_DOMAIN, "Setting presence to %s (%d)",
388                               status, states[0]);
389
390                 presence_chooser_reset_scroll_timeout (chooser);
391                 mission_control_set_presence (priv->mc,
392                                               states[0],
393                                               status,
394                                               NULL, NULL);
395         }
396
397         g_list_foreach (list, (GFunc) g_free, NULL);
398         g_list_free (list);
399
400         return TRUE;
401 }
402
403 static GList *
404 presence_chooser_get_presets (GossipPresenceChooser *chooser)
405 {
406         GList      *list = NULL;
407         guint       i;
408
409         for (i = 0; i < G_N_ELEMENTS (states); i++) {
410                 GList          *presets, *p;
411                 StateAndStatus *sas;
412                 const gchar    *status;
413
414                 status = gossip_presence_state_get_default_status (states[i]);
415                 sas = presence_chooser_state_and_status_new (states[i], status);
416                 list = g_list_append (list, sas);
417         
418                 presets = gossip_status_presets_get (states[i], 5);
419                 for (p = presets; p; p = p->next) {
420                         sas = presence_chooser_state_and_status_new (states[i], p->data);
421                         list = g_list_append (list, sas);
422                 }
423                 g_list_free (presets);
424         }
425
426         return list;
427 }
428
429 static StateAndStatus *
430 presence_chooser_state_and_status_new (McPresence   state,
431                                        const gchar *status)
432 {
433         StateAndStatus *sas;
434
435         sas = g_new0 (StateAndStatus, 1);
436
437         sas->state = state;
438         sas->status = status;
439
440         return sas;
441 }
442
443 static gboolean
444 presence_chooser_flash_timeout_cb (GossipPresenceChooser *chooser)
445 {
446         GossipPresenceChooserPriv *priv;
447         McPresence                 state;
448         static gboolean            on = FALSE;
449
450         priv = GET_PRIV (chooser);
451
452         if (on) {
453                 state = priv->flash_state_1;
454         } else {
455                 state = priv->flash_state_2;
456         }
457
458         gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
459                                       gossip_icon_name_for_presence_state (state),
460                                       GTK_ICON_SIZE_MENU);
461
462         on = !on;
463
464         return TRUE;
465 }
466
467 void
468 gossip_presence_chooser_flash_start (GossipPresenceChooser *chooser,
469                                      McPresence             state_1,
470                                      McPresence             state_2)
471 {
472         GossipPresenceChooserPriv *priv;
473
474         g_return_if_fail (GOSSIP_IS_PRESENCE_CHOOSER (chooser));
475
476         priv = GET_PRIV (chooser);
477
478         if (priv->flash_timeout_id != 0) {
479                 return;
480         }
481
482         priv->flash_state_1 = state_1;
483         priv->flash_state_2 = state_2;
484
485         priv->flash_timeout_id = g_timeout_add (FLASH_TIMEOUT,
486                                                 (GSourceFunc) presence_chooser_flash_timeout_cb,
487                                                 chooser);
488 }
489
490 void
491 gossip_presence_chooser_flash_stop (GossipPresenceChooser *chooser,
492                                     McPresence             state)
493 {
494         GossipPresenceChooserPriv *priv;
495
496         g_return_if_fail (GOSSIP_IS_PRESENCE_CHOOSER (chooser));
497
498         priv = GET_PRIV (chooser);
499
500         if (priv->flash_timeout_id) {
501                 g_source_remove (priv->flash_timeout_id);
502                 priv->flash_timeout_id = 0;
503         }
504
505         gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
506                                       gossip_icon_name_for_presence_state (state),
507                                       GTK_ICON_SIZE_MENU);
508
509         priv->last_state = state;
510 }
511
512 gboolean
513 gossip_presence_chooser_is_flashing (GossipPresenceChooser *chooser)
514 {
515         GossipPresenceChooserPriv *priv;
516
517         g_return_val_if_fail (GOSSIP_IS_PRESENCE_CHOOSER (chooser), FALSE);
518
519         priv = GET_PRIV (chooser);
520
521         if (priv->flash_timeout_id) {
522                 return TRUE;
523         }
524
525         return FALSE;
526 }
527
528 static gboolean
529 presence_chooser_button_press_event_cb (GtkWidget      *chooser,
530                                         GdkEventButton *event,
531                                         gpointer        user_data)
532 {
533         if (event->button != 1 || event->type != GDK_BUTTON_PRESS) {
534                 return FALSE;
535         }
536
537         if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (chooser))) {
538                         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chooser), TRUE);
539                         return TRUE;
540                 }
541
542         return FALSE;
543 }
544
545 static void
546 presence_chooser_toggled_cb (GtkWidget *chooser,
547                              gpointer   user_data)
548 {
549         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (chooser))) {
550                 presence_chooser_menu_popup (GOSSIP_PRESENCE_CHOOSER (chooser));
551         } else {
552                 presence_chooser_menu_popdown (GOSSIP_PRESENCE_CHOOSER (chooser));
553         }
554 }
555
556 static void
557 presence_chooser_menu_popup (GossipPresenceChooser *chooser)
558 {
559         GossipPresenceChooserPriv *priv;
560         GtkWidget                 *menu;
561
562         priv = GET_PRIV (chooser);
563
564         if (priv->menu) {
565                 return;
566         }
567
568         menu = gossip_presence_chooser_create_menu ();
569
570         g_signal_connect_after (menu, "selection-done",
571                                 G_CALLBACK (presence_chooser_menu_selection_done_cb),
572                                 chooser);
573
574         g_signal_connect (menu, "destroy",
575                           G_CALLBACK (presence_chooser_menu_destroy_cb),
576                           chooser);
577
578         gtk_menu_attach_to_widget (GTK_MENU (menu),
579                                    GTK_WIDGET (chooser),
580                                    presence_chooser_menu_detach);
581
582         gtk_menu_popup (GTK_MENU (menu),
583                         NULL, NULL,
584                         (GtkMenuPositionFunc) presence_chooser_menu_align_func,
585                         chooser,
586                         1,
587                         gtk_get_current_event_time ());
588
589         priv->menu = menu;
590 }
591
592 static void
593 presence_chooser_menu_popdown (GossipPresenceChooser *chooser)
594 {
595         GossipPresenceChooserPriv *priv;
596
597         priv = GET_PRIV (chooser);
598
599         if (priv->menu) {
600                 gtk_widget_destroy (priv->menu);
601         }
602 }
603
604 static void
605 presence_chooser_menu_selection_done_cb (GtkMenuShell          *menushell,
606                                          GossipPresenceChooser *chooser)
607 {
608         gtk_widget_destroy (GTK_WIDGET (menushell));
609
610         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chooser), FALSE);
611 }
612
613 static void
614 presence_chooser_menu_destroy_cb (GtkWidget             *menu,
615                                   GossipPresenceChooser *chooser)
616 {
617         GossipPresenceChooserPriv *priv;
618
619         priv = GET_PRIV (chooser);
620
621         priv->menu = NULL;
622 }
623
624 static void
625 presence_chooser_menu_detach (GtkWidget *attach_widget,
626                               GtkMenu   *menu)
627 {
628         /* We don't need to do anything, but attaching the menu means
629          * we don't own the ref count and it is cleaned up properly.
630          */
631 }
632
633 static void
634 presence_chooser_menu_align_func (GtkMenu   *menu,
635                                   gint      *x,
636                                   gint      *y,
637                                   gboolean  *push_in,
638                                   GtkWidget *widget)
639 {
640         GtkRequisition  req;
641         GdkScreen      *screen;
642         gint            screen_height;
643
644         gtk_widget_size_request (GTK_WIDGET (menu), &req);
645
646         gdk_window_get_origin (widget->window, x, y);
647
648         *x += widget->allocation.x + 1;
649         *y += widget->allocation.y;
650
651         screen = gtk_widget_get_screen (GTK_WIDGET (menu));
652         screen_height = gdk_screen_get_height (screen);
653
654         if (req.height > screen_height) {
655                 /* Too big for screen height anyway. */
656                 *y = 0;
657                 return;
658         }
659
660         if ((*y + req.height + widget->allocation.height) > screen_height) {
661                 /* Can't put it below the button. */
662                 *y -= req.height;
663                 *y += 1;
664         } else {
665                 /* Put menu below button. */
666                 *y += widget->allocation.height;
667                 *y -= 1;
668         }
669
670         *push_in = FALSE;
671 }
672
673 GtkWidget *
674 gossip_presence_chooser_create_menu (void)
675 {
676         const gchar *status;
677         GtkWidget   *menu;
678         GtkWidget   *item;
679         GtkWidget   *image;
680         guint        i;
681
682         menu = gtk_menu_new ();
683
684         for (i = 0; i < G_N_ELEMENTS (states); i++) {
685                 GList       *list, *l;
686
687                 status = gossip_presence_state_get_default_status (states[i]);
688                 presence_chooser_menu_add_item (menu,
689                                                 status,
690                                                 states[i],
691                                                 FALSE);
692
693                 list = gossip_status_presets_get (states[i], 5);
694                 for (l = list; l; l = l->next) {
695                         presence_chooser_menu_add_item (menu,
696                                                         l->data,
697                                                         states[i],
698                                                         FALSE);
699                 }
700                 g_list_free (list);
701
702                 presence_chooser_menu_add_item (menu,
703                                                 _("Custom message..."),
704                                                 states[i],
705                                                 TRUE);
706
707                 /* Separator. */
708                 item = gtk_menu_item_new ();
709                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
710                 gtk_widget_show (item);
711         }
712
713         /* Offline to disconnect */
714         status = gossip_presence_state_get_default_status (MC_PRESENCE_OFFLINE);
715         presence_chooser_menu_add_item (menu,
716                                         status,
717                                         MC_PRESENCE_OFFLINE,
718                                         FALSE);
719         /* Separator. */
720         item = gtk_menu_item_new ();
721         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
722         gtk_widget_show (item);
723
724         /* Clear list */
725         item = gtk_image_menu_item_new_with_label (_("Clear List..."));
726         image = gtk_image_new_from_stock (GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU);
727         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
728         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
729         gtk_widget_show (image);
730         gtk_widget_show (item);
731
732         g_signal_connect (item,
733                           "activate",
734                           G_CALLBACK (presence_chooser_clear_activate_cb),
735                           NULL);
736
737         return menu;
738 }
739
740 static void
741 presence_chooser_menu_add_item (GtkWidget   *menu,
742                                 const gchar *str,
743                                 McPresence   state,
744                                 gboolean     custom)
745 {
746         GtkWidget   *item;
747         GtkWidget   *image;
748         const gchar *icon_name;
749
750         item = gtk_image_menu_item_new_with_label (str);
751         icon_name = gossip_icon_name_for_presence_state (state);
752
753         if (custom) {
754                 g_signal_connect (item, "activate",
755                                   G_CALLBACK (presence_chooser_custom_activate_cb),
756                                   NULL);
757         } else {
758                 g_signal_connect (item, "activate",
759                                   G_CALLBACK (presence_chooser_noncustom_activate_cb),
760                                   NULL);
761         }
762
763         image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
764         gtk_widget_show (image);
765
766         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
767         gtk_widget_show (item);
768
769         g_object_set_data_full (G_OBJECT (item),
770                                 "status", g_strdup (str),
771                                 (GDestroyNotify) g_free);
772
773         g_object_set_data (G_OBJECT (item), "state", GINT_TO_POINTER (state));
774
775         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
776 }
777
778 static void
779 presence_chooser_clear_activate_cb (GtkWidget *item,
780                                     gpointer   user_data)
781 {
782         GtkWidget *dialog;
783         GtkWidget *toplevel;
784         GtkWindow *parent = NULL;
785
786         toplevel = gtk_widget_get_toplevel (item);
787         if (GTK_WIDGET_TOPLEVEL (toplevel) &&
788             GTK_IS_WINDOW (toplevel)) {
789                 GtkWindow *window;
790                 gboolean   visible;
791
792                 window = GTK_WINDOW (toplevel);
793                 visible = gossip_window_get_is_visible (window);
794
795                 if (visible) {
796                         parent = window;
797                 }
798         }
799
800         dialog = gtk_message_dialog_new (GTK_WINDOW (parent),
801                                          0,
802                                          GTK_MESSAGE_QUESTION,
803                                          GTK_BUTTONS_NONE,
804                                          _("Are you sure you want to clear the list?"));
805
806         gtk_message_dialog_format_secondary_text (
807                 GTK_MESSAGE_DIALOG (dialog),
808                 _("This will remove any custom messages you have "
809                   "added to the list of preset status messages."));
810
811         gtk_dialog_add_buttons (GTK_DIALOG (dialog),
812                                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
813                                 _("Clear List"), GTK_RESPONSE_OK,
814                                 NULL);
815
816         gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), FALSE);
817
818         g_signal_connect (dialog, "response",
819                           G_CALLBACK (presence_chooser_clear_response_cb),
820                           NULL);
821
822         gtk_widget_show (dialog);
823 }
824
825 static void
826 presence_chooser_clear_response_cb (GtkWidget *widget,
827                                     gint       response,
828                                     gpointer   user_data)
829 {
830         if (response == GTK_RESPONSE_OK) {
831                 gossip_status_presets_reset ();
832         }
833
834         gtk_widget_destroy (widget);
835 }
836
837 static void
838 presence_chooser_noncustom_activate_cb (GtkWidget *item,
839                                         gpointer   user_data)
840 {
841         McPresence   state;
842         const gchar *status;
843
844         status = g_object_get_data (G_OBJECT (item), "status");
845         state = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "state"));
846
847         presence_chooser_set_state (state, status, FALSE);
848 }
849
850 static void
851 presence_chooser_set_state (McPresence   state,
852                             const gchar *status,
853                             gboolean     save)
854 {
855         const gchar    *default_status;
856         MissionControl *mc;
857
858         default_status = gossip_presence_state_get_default_status (state);
859
860         if (G_STR_EMPTY (status)) {
861                 status = default_status;
862         } else {
863                 /* Only store the value if it differs from the default ones. */
864                 if (save && strcmp (status, default_status) != 0) {
865                         gossip_status_presets_set_last (state, status);
866                 }
867         }
868
869         gossip_debug (DEBUG_DOMAIN, "Setting presence to %s (%d)",
870                       status, state);
871
872         mc = mission_control_new (tp_get_bus ());
873         mission_control_set_presence (mc, state, status, NULL, NULL);
874         g_object_unref (mc);
875 }
876
877 static void
878 presence_chooser_custom_activate_cb (GtkWidget *item,
879                                      gpointer   user_data)
880 {
881         McPresence state;
882
883         state = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "state"));
884
885         presence_chooser_show_dialog (state);
886 }
887
888 static void
889 presence_chooser_show_dialog (McPresence state)
890 {
891         static GtkWidget    *dialog = NULL;
892         static GtkListStore *store[LAST_MC_PRESENCE];
893         GladeXML            *glade;
894         GtkWidget           *image;
895         GtkWidget           *combo;
896         GtkWidget           *entry;
897         GtkWidget           *checkbutton;
898         const gchar         *default_status;
899
900         if (dialog) {
901                 gtk_widget_destroy (dialog);
902                 dialog = NULL;
903         } else {
904                 guint i;
905
906                 for (i = 0; i < LAST_MC_PRESENCE; i++) {
907                         store[i] = NULL;
908                 }
909         }
910
911         glade = gossip_glade_get_file ("gossip-presence-chooser.glade",
912                                        "status_message_dialog",
913                                        NULL,
914                                        "status_message_dialog", &dialog,
915                                        "comboentry_status", &combo,
916                                        "image_status", &image,
917                                        "checkbutton_add", &checkbutton,
918                                        NULL);
919
920         g_object_unref (glade);
921
922         g_signal_connect (dialog, "destroy",
923                           G_CALLBACK (gtk_widget_destroyed),
924                           &dialog);
925         g_signal_connect (dialog, "response",
926                           G_CALLBACK (presence_chooser_dialog_response_cb),
927                           NULL);
928
929         gtk_image_set_from_icon_name (GTK_IMAGE (image),
930                                       gossip_icon_name_for_presence_state (state),
931                                       GTK_ICON_SIZE_MENU);
932
933         if (!store[state]) {
934                 GList       *presets, *l;
935                 GtkTreeIter  iter;
936
937                 store[state] = gtk_list_store_new (1, G_TYPE_STRING);
938
939                 presets = gossip_status_presets_get (state, -1);
940                 for (l = presets; l; l = l->next) {
941                         gtk_list_store_append (store[state], &iter);
942                         gtk_list_store_set (store[state], &iter, 0, l->data, -1);
943                 }
944
945                 g_list_free (presets);
946         }
947
948         default_status = gossip_presence_state_get_default_status (state);
949
950         entry = GTK_BIN (combo)->child;
951         gtk_entry_set_text (GTK_ENTRY (entry), default_status);
952         gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
953         gtk_entry_set_width_chars (GTK_ENTRY (entry), 25);
954
955         gtk_combo_box_set_model (GTK_COMBO_BOX (combo), GTK_TREE_MODEL (store[state]));
956         gtk_combo_box_entry_set_text_column (GTK_COMBO_BOX_ENTRY (combo), 0);
957
958         /* FIXME: Set transian for a window ? */
959
960         g_object_set_data (G_OBJECT (dialog), "store", store[state]);
961         g_object_set_data (G_OBJECT (dialog), "entry", entry);
962         g_object_set_data (G_OBJECT (dialog), "checkbutton", checkbutton);
963         g_object_set_data (G_OBJECT (dialog), "state", GINT_TO_POINTER (state));
964
965         gtk_widget_show_all (dialog);
966 }
967
968 static void
969 presence_chooser_dialog_response_cb (GtkWidget *dialog,
970                                      gint       response,
971                                      gpointer   user_data)
972 {
973         if (response == GTK_RESPONSE_OK) {
974                 GtkWidget           *entry;
975                 GtkWidget           *checkbutton;
976                 GtkListStore        *store;
977                 GtkTreeModel        *model;
978                 GtkTreeIter          iter;
979                 McPresence           state;
980                 const gchar         *status;
981                 gboolean             save;
982                 gboolean             duplicate = FALSE;
983                 gboolean             has_next;
984
985                 entry = g_object_get_data (G_OBJECT (dialog), "entry");
986                 status = gtk_entry_get_text (GTK_ENTRY (entry));
987                 store = g_object_get_data (G_OBJECT (dialog), "store");
988                 model = GTK_TREE_MODEL (store);
989
990                 has_next = gtk_tree_model_get_iter_first (model, &iter);
991                 while (has_next) {
992                         gchar *str;
993
994                         gtk_tree_model_get (model, &iter,
995                                             0, &str,
996                                             -1);
997
998                         if (strcmp (status, str) == 0) {
999                                 g_free (str);
1000                                 duplicate = TRUE;
1001                                 break;
1002                         }
1003
1004                         g_free (str);
1005
1006                         has_next = gtk_tree_model_iter_next (model, &iter);
1007                 }
1008
1009                 if (!duplicate) {
1010                         gtk_list_store_append (store, &iter);
1011                         gtk_list_store_set (store, &iter, 0, status, -1);
1012                 }
1013
1014                 checkbutton = g_object_get_data (G_OBJECT (dialog), "checkbutton");
1015                 save = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbutton));
1016                 state = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dialog), "state"));
1017
1018                 presence_chooser_set_state (state, status, save);
1019         }
1020
1021         gtk_widget_destroy (dialog);
1022 }
1023