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