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