]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-presence-chooser.c
8826ed63d424d4521382d420fca77ec228800634
[empathy.git] / libempathy-gtk / empathy-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  * Copyright (C) 2009 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Authors: Richard Hult <richard@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  *          Davyd Madeley <davyd.madeley@collabora.co.uk>
25  */
26
27 #include "config.h"
28
29 #include <string.h>
30 #include <stdlib.h>
31
32 #include <glib/gi18n-lib.h>
33 #include <gtk/gtk.h>
34 #include <glade/glade.h>
35 #include <gdk/gdkkeysyms.h>
36
37 #include <telepathy-glib/util.h>
38 #include <libmissioncontrol/mc-enum-types.h>
39
40 #include <libempathy/empathy-idle.h>
41 #include <libempathy/empathy-utils.h>
42 #include <libempathy/empathy-status-presets.h>
43
44 // FIXME - what's the correct debug flag?
45 #define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER
46 #include <libempathy/empathy-debug.h>
47
48 #include "empathy-ui-utils.h"
49 #include "empathy-images.h"
50 #include "empathy-presence-chooser.h"
51
52 /* Flashing delay for icons (milliseconds). */
53 #define FLASH_TIMEOUT 500
54
55 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyPresenceChooser)
56 typedef struct {
57         EmpathyIdle *idle;
58
59         gboolean     editing_status;
60         int          block_set_editing;
61         int          block_changed;
62
63         McPresence   state;
64
65         McPresence   flash_state_1;
66         McPresence   flash_state_2;
67         guint        flash_timeout_id;
68 } EmpathyPresenceChooserPriv;
69
70 typedef struct {
71         GtkWidget    *dialog;
72         GtkWidget    *checkbutton_save;
73         GtkWidget    *comboboxentry_message;
74         GtkWidget    *entry_message;
75         GtkWidget    *combobox_status;
76         GtkTreeModel *model_status;
77 } CustomMessageDialog;
78
79 enum {
80         COL_ICON,
81         COL_LABEL,
82         COL_PRESENCE,
83         COL_COUNT
84 };
85
86 static CustomMessageDialog *message_dialog = NULL;
87 /* States to be listed in the menu.
88  * Each state has a boolean telling if it can have custom message */
89 static guint states[] = {MC_PRESENCE_AVAILABLE, TRUE,
90                          MC_PRESENCE_DO_NOT_DISTURB, TRUE,
91                          MC_PRESENCE_AWAY, TRUE,
92                          MC_PRESENCE_HIDDEN, FALSE,
93                          MC_PRESENCE_OFFLINE, FALSE};
94
95 static void            presence_chooser_finalize               (GObject                    *object);
96 static void            presence_chooser_presence_changed_cb    (EmpathyPresenceChooser      *chooser);
97 static gboolean        presence_chooser_flash_timeout_cb       (EmpathyPresenceChooser      *chooser);
98 static void            presence_chooser_flash_start            (EmpathyPresenceChooser      *chooser,
99                                                                 McPresence                  state_1,
100                                                                 McPresence                  state_2);
101 static void            presence_chooser_flash_stop             (EmpathyPresenceChooser      *chooser,
102                                                                 McPresence                  state);
103 static void            presence_chooser_menu_add_item          (GtkWidget                  *menu,
104                                                                 const gchar                *str,
105                                                                 McPresence                  state);
106 static void            presence_chooser_noncustom_activate_cb  (GtkWidget                  *item,
107                                                                 gpointer                    user_data);
108 static void            presence_chooser_set_state              (McPresence                  state,
109                                                                 const gchar                *status);
110 static void            presence_chooser_custom_activate_cb     (GtkWidget                  *item,
111                                                                 gpointer                    user_data);
112 static void            presence_chooser_dialog_show            (GtkWindow                  *parent);
113
114 G_DEFINE_TYPE (EmpathyPresenceChooser, empathy_presence_chooser, GTK_TYPE_COMBO_BOX_ENTRY);
115
116 static void
117 empathy_presence_chooser_class_init (EmpathyPresenceChooserClass *klass)
118 {
119         GObjectClass *object_class = G_OBJECT_CLASS (klass);
120
121         object_class->finalize = presence_chooser_finalize;
122
123         g_type_class_add_private (object_class, sizeof (EmpathyPresenceChooserPriv));
124 }
125
126 enum
127 {
128         COL_STATE_ICON_NAME,
129         COL_STATE,
130         COL_STATUS_TEXT,
131         COL_DISPLAY_MARKUP,
132         COL_TYPE,
133         N_COLUMNS
134 };
135
136 enum
137 {
138         ENTRY_TYPE_BUILTIN,
139         ENTRY_TYPE_SAVED,
140         ENTRY_TYPE_CUSTOM,
141         ENTRY_TYPE_SEPARATOR,
142         ENTRY_TYPE_EDIT_CUSTOM,
143 };
144
145 static GtkTreeModel *
146 create_model (void)
147 {
148         GtkListStore *store = gtk_list_store_new (N_COLUMNS,
149                         G_TYPE_STRING,          /* COL_STATE_ICON_NAME */
150                         MC_TYPE_PRESENCE,       /* COL_STATE */
151                         G_TYPE_STRING,          /* COL_STATUS_TEXT */
152                         G_TYPE_STRING,          /* COL_DISPLAY_MARKUP */
153                         G_TYPE_INT);            /* COL_TYPE */
154         
155         GtkTreeIter iter;
156         
157         int i;
158         for (i = 0; i < G_N_ELEMENTS (states); i += 2) {
159                 GList       *list, *l;
160
161                 const char *status = empathy_presence_get_default_message (states[i]);
162                 const char *icon_name = empathy_icon_name_for_presence (states[i]);
163
164                 gtk_list_store_append (store, &iter);
165                 gtk_list_store_set (store, &iter,
166                                 COL_STATE_ICON_NAME, icon_name,
167                                 COL_STATE, states[i],
168                                 COL_STATUS_TEXT, status,
169                                 COL_DISPLAY_MARKUP, status,
170                                 COL_TYPE, ENTRY_TYPE_BUILTIN,
171                                 -1);
172
173                 if (states[i+1]) {
174                         /* Set custom messages if wanted */
175                         list = empathy_status_presets_get (states[i], 5);
176                         for (l = list; l; l = l->next) {
177                                 gtk_list_store_append (store, &iter);
178                                 gtk_list_store_set (store, &iter,
179                                                 COL_STATE_ICON_NAME, icon_name,
180                                                 COL_STATE, states[i],
181                                                 COL_STATUS_TEXT, l->data,
182                                                 COL_DISPLAY_MARKUP, l->data,
183                                                 COL_TYPE, ENTRY_TYPE_SAVED,
184                                                 -1);
185                         }
186                         g_list_free (list);
187                 
188                         gtk_list_store_append (store, &iter);
189                         gtk_list_store_set (store, &iter,
190                                         COL_STATE_ICON_NAME, icon_name,
191                                         COL_STATE, states[i],
192                                         COL_STATUS_TEXT, "",
193                                         COL_DISPLAY_MARKUP, "<i>Custom Message...</i>",
194                                         COL_TYPE, ENTRY_TYPE_CUSTOM,
195                                         -1);
196                 }
197
198         }
199         
200         /* add a separator */
201         gtk_list_store_append (store, &iter);
202         gtk_list_store_set (store, &iter,
203                         COL_TYPE, ENTRY_TYPE_SEPARATOR,
204                         -1);
205         
206         gtk_list_store_append (store, &iter);
207         gtk_list_store_set (store, &iter,
208                         COL_STATE_ICON_NAME, GTK_STOCK_EDIT,
209                         COL_STATUS_TEXT, "",
210                         COL_DISPLAY_MARKUP, "Edit Custom Messages...",
211                         COL_TYPE, ENTRY_TYPE_EDIT_CUSTOM,
212                         -1);
213
214         return GTK_TREE_MODEL (store);
215 }
216
217 static void
218 popup_shown_cb (GObject *self, GParamSpec *pspec, gpointer user_data)
219 {
220         gboolean shown;
221         g_object_get (self, "popup-shown", &shown, NULL);
222
223         if (!shown) return;
224
225         GtkTreeModel *model = create_model ();
226
227         gtk_combo_box_set_model (GTK_COMBO_BOX (self), GTK_TREE_MODEL (model));
228         
229         g_object_unref (model);
230 }
231
232 static void
233 set_status_editing (EmpathyPresenceChooser *self, gboolean editing)
234 {
235         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
236         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (self));
237
238         if (priv->block_set_editing) return;
239
240         if (editing)
241         {
242                 priv->editing_status = TRUE;
243                 gtk_entry_set_icon_from_stock (GTK_ENTRY (entry),
244                                 GTK_ENTRY_ICON_SECONDARY,
245                                 GTK_STOCK_OK);
246                 gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry),
247                                 GTK_ENTRY_ICON_SECONDARY,
248                                 _("Set status"));
249                 gtk_entry_set_icon_sensitive (GTK_ENTRY (entry),
250                                 GTK_ENTRY_ICON_PRIMARY,
251                                 FALSE);
252         }
253         else
254         {
255                 gtk_entry_set_icon_from_stock (GTK_ENTRY (entry),
256                                 GTK_ENTRY_ICON_SECONDARY,
257                                 NULL);
258                 gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry),
259                                 GTK_ENTRY_ICON_SECONDARY,
260                                 NULL);
261                 gtk_entry_set_icon_sensitive (GTK_ENTRY (entry),
262                                 GTK_ENTRY_ICON_PRIMARY,
263                                 TRUE);
264
265                 /* attempt to get the toplevel for this widget */
266                 GtkWidget *window = gtk_widget_get_toplevel (GTK_WIDGET (self));
267                 if (GTK_WIDGET_TOPLEVEL (window) && GTK_IS_WINDOW (window))
268                 {
269                         /* unset the focus */
270                         gtk_window_set_focus (GTK_WINDOW (window), NULL);
271                 }
272                 gtk_editable_set_position (GTK_EDITABLE (entry), 0);
273
274                 priv->editing_status = FALSE;
275         }
276 }
277
278 static void
279 mc_set_custom_state (EmpathyPresenceChooser *self)
280 {
281         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
282         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (self));
283
284         /* update the status with MC */
285         const char *status = gtk_entry_get_text (GTK_ENTRY (entry));
286         DEBUG ("Sending state to MC-> %s (%s)\n",
287                         g_enum_get_value (g_type_class_peek (MC_TYPE_PRESENCE),
288                                 priv->state)->value_name,
289                         status);
290         empathy_idle_set_presence (priv->idle, priv->state, status);
291 }
292
293 static void
294 ui_set_custom_state (EmpathyPresenceChooser *self,
295                            McPresence state,
296                            const char *status)
297 {
298         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
299         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (self));
300         const char *icon_name;
301
302         priv->block_set_editing++;
303         priv->block_changed++;
304
305         icon_name = empathy_icon_name_for_presence (state);
306         gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
307                         GTK_ENTRY_ICON_PRIMARY,
308                         icon_name);
309         gtk_entry_set_text (GTK_ENTRY (entry), status);
310
311         priv->block_changed--;
312         priv->block_set_editing--;
313 }
314
315 static void
316 entry_icon_release_cb (EmpathyPresenceChooser   *self,
317                        GtkEntryIconPosition      icon_pos,
318                        GdkEvent         *event,
319                        GtkEntry         *entry)
320 {
321         set_status_editing (self, FALSE);
322         mc_set_custom_state (self);
323 }
324
325 static void
326 entry_activate_cb (EmpathyPresenceChooser       *self,
327                    GtkEntry                     *entry)
328 {
329         set_status_editing (self, FALSE);
330         mc_set_custom_state (self);
331 }
332
333 static gboolean
334 entry_key_press_event_cb (EmpathyPresenceChooser        *self,
335                           GdkEventKey                   *event,
336                           GtkWidget                     *entry)
337 {
338         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
339
340         if (priv->editing_status && event->keyval == GDK_Escape)
341         {
342                 /* the user pressed Escape, undo the editing */
343                 set_status_editing (self, FALSE);
344                 presence_chooser_presence_changed_cb (self);
345
346                 return TRUE;
347         }
348
349         return FALSE; /* send this event elsewhere */
350 }
351
352 static void
353 changed_cb (GtkComboBox *self, gpointer user_data)
354 {
355         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
356
357         if (priv->block_changed) return;
358
359         GtkTreeIter iter;
360         char *icon_name;
361         int type = -1;
362
363         GtkTreeModel *model = gtk_combo_box_get_model (self);
364         if (!gtk_combo_box_get_active_iter (self, &iter))
365         {
366                 /* the combo is being edited to a custom entry */
367                 if (!priv->editing_status)
368                 {
369                         set_status_editing (EMPATHY_PRESENCE_CHOOSER (self), TRUE);
370                 }
371                 return;
372         }
373
374         gtk_tree_model_get (model, &iter,
375                         COL_STATE_ICON_NAME, &icon_name,
376                         COL_STATE, &priv->state,
377                         COL_TYPE, &type,
378                         -1);
379
380         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (self));
381
382         if (type == ENTRY_TYPE_EDIT_CUSTOM)
383         {
384                 /* recover the status that was unset because COL_STATUS_TEXT
385                  * is "". Unfortunately if you try and set COL_STATUS_TEXT to
386                  * NULL, it generates a g_critical. I wonder if there is a
387                  * better way around this. */
388                 const char *status = empathy_idle_get_status (priv->idle);
389                 priv->block_set_editing++;
390                 gtk_entry_set_text (GTK_ENTRY (entry), status);
391                 priv->block_set_editing--;
392
393                 /* attempt to get the toplevel for this widget */
394                 GtkWidget *window = gtk_widget_get_toplevel (GTK_WIDGET (self));
395                 if (!GTK_WIDGET_TOPLEVEL (window) || !GTK_IS_WINDOW (window))
396                 {
397                         window = NULL;
398                 }
399
400                 presence_chooser_dialog_show (GTK_WINDOW (window));
401         }
402         else if (type == ENTRY_TYPE_CUSTOM)
403         {
404                 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
405                                 GTK_ENTRY_ICON_PRIMARY,
406                                 icon_name);
407
408                 /* preseed the status */
409                 const char *status = empathy_idle_get_status (priv->idle);
410                 priv->block_set_editing++;
411                 gtk_entry_set_text (GTK_ENTRY (entry), status);
412                 priv->block_set_editing--;
413
414                 /* grab the focus */
415                 gtk_widget_grab_focus (entry);
416
417                 set_status_editing (EMPATHY_PRESENCE_CHOOSER (self), TRUE);
418         }
419         else
420         {
421                 char *status;
422                 /* just in case we were setting a new status when
423                  * things were changed */
424                 set_status_editing (EMPATHY_PRESENCE_CHOOSER (self), FALSE);
425
426                 gtk_tree_model_get (model, &iter,
427                                 COL_STATUS_TEXT, &status,
428                                 -1);
429
430                 empathy_idle_set_presence (priv->idle, priv->state, status);
431
432                 g_free (status);
433         }
434
435         g_free (icon_name);
436 }
437
438 static gboolean
439 combo_row_separator_func (GtkTreeModel  *model,
440                           GtkTreeIter   *iter,
441                           gpointer       data)
442 {
443         int type;
444         gtk_tree_model_get (model, iter,
445                         COL_TYPE, &type,
446                         -1);
447
448         return (type == ENTRY_TYPE_SEPARATOR);
449 }
450
451 static void
452 empathy_presence_chooser_init (EmpathyPresenceChooser *chooser)
453 {
454         EmpathyPresenceChooserPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chooser,
455                 EMPATHY_TYPE_PRESENCE_CHOOSER, EmpathyPresenceChooserPriv);
456
457         chooser->priv = priv;
458         
459         GtkTreeModel *model = create_model ();
460
461         gtk_combo_box_set_model (GTK_COMBO_BOX (chooser), GTK_TREE_MODEL (model));
462         gtk_combo_box_entry_set_text_column (GTK_COMBO_BOX_ENTRY (chooser),
463                         COL_STATUS_TEXT);
464         gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (chooser),
465                         combo_row_separator_func,
466                         NULL, NULL);
467         
468         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (chooser));
469         gtk_entry_set_icon_activatable (GTK_ENTRY (entry),
470                         GTK_ENTRY_ICON_PRIMARY, FALSE);
471         g_signal_connect_object (entry, "icon-release",
472                         G_CALLBACK (entry_icon_release_cb), chooser,
473                         G_CONNECT_SWAPPED);
474         g_signal_connect_object (entry, "activate",
475                         G_CALLBACK (entry_activate_cb), chooser,
476                         G_CONNECT_SWAPPED);
477         g_signal_connect_object (entry, "key-press-event",
478                         G_CALLBACK (entry_key_press_event_cb), chooser,
479                         G_CONNECT_SWAPPED);
480         // FIXME - should this also happen when the user presses TAB ?
481
482         GtkCellRenderer *renderer;
483         gtk_cell_layout_clear (GTK_CELL_LAYOUT (chooser));
484
485         renderer = gtk_cell_renderer_pixbuf_new ();
486         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser), renderer, FALSE);
487         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser), renderer,
488                         "icon-name", COL_STATE_ICON_NAME,
489                         NULL);
490         g_object_set (renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
491
492         renderer = gtk_cell_renderer_text_new ();
493         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser), renderer, TRUE);
494         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser), renderer,
495                         "markup", COL_DISPLAY_MARKUP,
496                         NULL);
497
498         g_object_unref (model);
499
500         g_signal_connect (chooser, "notify::popup-shown",
501                         G_CALLBACK (popup_shown_cb), NULL);
502         g_signal_connect (chooser, "changed",
503                         G_CALLBACK (changed_cb), NULL);
504         g_signal_connect_swapped (entry, "changed",
505                         G_CALLBACK (changed_cb), chooser);
506
507         priv->idle = empathy_idle_dup_singleton ();
508         presence_chooser_presence_changed_cb (chooser);
509         g_signal_connect_swapped (priv->idle, "notify",
510                                   G_CALLBACK (presence_chooser_presence_changed_cb),
511                                   chooser);
512
513         g_object_set (chooser,
514                         // FIXME: this string sucks
515                         "tooltip-text", _("Set your presence and current status"),
516                         NULL);
517 }
518
519 static void
520 presence_chooser_finalize (GObject *object)
521 {
522         EmpathyPresenceChooserPriv *priv;
523
524         priv = GET_PRIV (object);
525
526         if (priv->flash_timeout_id) {
527                 g_source_remove (priv->flash_timeout_id);
528         }
529
530         g_signal_handlers_disconnect_by_func (priv->idle,
531                                               presence_chooser_presence_changed_cb,
532                                               object);
533         g_object_unref (priv->idle);
534
535         G_OBJECT_CLASS (empathy_presence_chooser_parent_class)->finalize (object);
536 }
537
538 GtkWidget *
539 empathy_presence_chooser_new (void)
540 {
541         GtkWidget *chooser;
542
543         chooser = g_object_new (EMPATHY_TYPE_PRESENCE_CHOOSER, NULL);
544
545         return chooser;
546 }
547
548 static void
549 presence_chooser_presence_changed_cb (EmpathyPresenceChooser *chooser)
550 {
551         EmpathyPresenceChooserPriv *priv;
552         McPresence                 state;
553         McPresence                 flash_state;
554         const gchar               *status;
555
556         priv = GET_PRIV (chooser);
557
558         priv->state = state = empathy_idle_get_state (priv->idle);
559         status = empathy_idle_get_status (priv->idle);
560         flash_state = empathy_idle_get_flash_state (priv->idle);
561
562         /* look through the model and attempt to find a matching state */
563         GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser));
564         GtkTreeIter iter;
565         gboolean valid, match_state = FALSE, match = FALSE;
566         for (valid = gtk_tree_model_get_iter_first (model, &iter);
567              valid;
568              valid = gtk_tree_model_iter_next (model, &iter))
569         {
570                 int m_type;
571                 McPresence m_state;
572                 char *m_status;
573
574                 gtk_tree_model_get (model, &iter,
575                                 COL_STATE, &m_state,
576                                 COL_TYPE, &m_type,
577                                 -1);
578
579                 if (m_type == ENTRY_TYPE_CUSTOM ||
580                     m_type == ENTRY_TYPE_SEPARATOR ||
581                     m_type == ENTRY_TYPE_EDIT_CUSTOM)
582                 {
583                         continue;
584                 }
585                 else if (!match_state && state == m_state)
586                 {
587                         /* we are now in the section that can contain our
588                          * match */
589                         match_state = TRUE;
590                 }
591                 else if (match_state && state != m_state)
592                 {
593                         /* we have passed the section that can contain our
594                          * match */
595                         break;
596                 }
597
598                 gtk_tree_model_get (model, &iter,
599                                 COL_STATUS_TEXT, &m_status,
600                                 -1);
601
602                 match = !strcmp (status, m_status);
603
604                 g_free (m_status);
605
606                 if (match) break;
607
608         }
609
610         if (match)
611         {
612                 priv->block_changed++;
613                 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser), &iter);
614                 priv->block_changed--;
615         }
616         else
617         {
618                 // FIXME - do we insert the match into the menu?
619                 ui_set_custom_state (chooser, state, status);
620         }
621
622         if (flash_state != MC_PRESENCE_UNSET) {
623                 presence_chooser_flash_start (chooser, state, flash_state);
624         } else {
625                 presence_chooser_flash_stop (chooser, state);
626         }
627 }
628
629 static gboolean
630 presence_chooser_flash_timeout_cb (EmpathyPresenceChooser *chooser)
631 {
632         EmpathyPresenceChooserPriv *priv;
633         McPresence                 state;
634         static gboolean            on = FALSE;
635
636         priv = GET_PRIV (chooser);
637
638         if (on) {
639                 state = priv->flash_state_1;
640         } else {
641                 state = priv->flash_state_2;
642         }
643
644         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (chooser));
645         gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
646                         GTK_ENTRY_ICON_PRIMARY,
647                         empathy_icon_name_for_presence (state));
648
649         on = !on;
650
651         return TRUE;
652 }
653
654 static void
655 presence_chooser_flash_start (EmpathyPresenceChooser *chooser,
656                               McPresence             state_1,
657                               McPresence             state_2)
658 {
659         EmpathyPresenceChooserPriv *priv;
660
661         g_return_if_fail (EMPATHY_IS_PRESENCE_CHOOSER (chooser));
662
663         priv = GET_PRIV (chooser);
664
665         priv->flash_state_1 = state_1;
666         priv->flash_state_2 = state_2;
667
668         if (!priv->flash_timeout_id) {
669                 priv->flash_timeout_id = g_timeout_add (FLASH_TIMEOUT,
670                                                         (GSourceFunc) presence_chooser_flash_timeout_cb,
671                                                         chooser);
672         }
673 }
674
675 static void
676 presence_chooser_flash_stop (EmpathyPresenceChooser *chooser,
677                              McPresence             state)
678 {
679         EmpathyPresenceChooserPriv *priv;
680
681         g_return_if_fail (EMPATHY_IS_PRESENCE_CHOOSER (chooser));
682
683         priv = GET_PRIV (chooser);
684
685         if (priv->flash_timeout_id) {
686                 g_source_remove (priv->flash_timeout_id);
687                 priv->flash_timeout_id = 0;
688         }
689         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (chooser));
690         
691         gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
692                         GTK_ENTRY_ICON_PRIMARY,
693                         empathy_icon_name_for_presence (state));
694
695         // FIXME - what does this do?
696         // priv->last_state = state;
697 }
698
699 GtkWidget *
700 empathy_presence_chooser_create_menu (void)
701 {
702         const gchar *status;
703         GtkWidget   *menu;
704         GtkWidget   *item;
705         GtkWidget   *image;
706         guint        i;
707
708         menu = gtk_menu_new ();
709
710         for (i = 0; i < G_N_ELEMENTS (states); i += 2) {
711                 GList       *list, *l;
712
713                 status = empathy_presence_get_default_message (states[i]);
714                 presence_chooser_menu_add_item (menu,
715                                                 status,
716                                                 states[i]);
717
718                 if (states[i+1]) {
719                         /* Set custom messages if wanted */
720                         list = empathy_status_presets_get (states[i], 5);
721                         for (l = list; l; l = l->next) {
722                                 presence_chooser_menu_add_item (menu,
723                                                                 l->data,
724                                                                 states[i]);
725                         }
726                         g_list_free (list);
727                 }
728
729         }
730
731         /* Separator. */
732         item = gtk_menu_item_new ();
733         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
734         gtk_widget_show (item);
735
736         /* Custom messages */
737         item = gtk_image_menu_item_new_with_label (_("Custom messages..."));
738         image = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
739         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
740         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
741         gtk_widget_show (image);
742         gtk_widget_show (item);
743
744         g_signal_connect (item,
745                           "activate",
746                           G_CALLBACK (presence_chooser_custom_activate_cb),
747                           NULL);
748
749         return menu;
750 }
751
752 static void
753 presence_chooser_menu_add_item (GtkWidget   *menu,
754                                 const gchar *str,
755                                 McPresence   state)
756 {
757         GtkWidget   *item;
758         GtkWidget   *image;
759         const gchar *icon_name;
760
761         item = gtk_image_menu_item_new_with_label (str);
762         icon_name = empathy_icon_name_for_presence (state);
763
764         g_signal_connect (item, "activate",
765                           G_CALLBACK (presence_chooser_noncustom_activate_cb),
766                           NULL);
767
768         image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
769         gtk_widget_show (image);
770
771         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
772         gtk_widget_show (item);
773
774         g_object_set_data_full (G_OBJECT (item),
775                                 "status", g_strdup (str),
776                                 (GDestroyNotify) g_free);
777
778         g_object_set_data (G_OBJECT (item), "state", GINT_TO_POINTER (state));
779
780         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
781 }
782
783 static void
784 presence_chooser_noncustom_activate_cb (GtkWidget *item,
785                                         gpointer   user_data)
786 {
787         McPresence   state;
788         const gchar *status;
789
790         status = g_object_get_data (G_OBJECT (item), "status");
791         state = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "state"));
792
793         presence_chooser_set_state (state, status);
794 }
795
796 static void
797 presence_chooser_set_state (McPresence   state,
798                             const gchar *status)
799 {
800         EmpathyIdle *idle;
801
802         idle = empathy_idle_dup_singleton ();
803         empathy_idle_set_presence (idle, state, status);
804         g_object_unref (idle);
805 }
806
807 static void
808 presence_chooser_custom_activate_cb (GtkWidget *item,
809                                      gpointer   user_data)
810 {
811         presence_chooser_dialog_show (NULL);
812 }
813
814 static McPresence
815 presence_chooser_dialog_get_selected (CustomMessageDialog *dialog)
816 {
817         GtkTreeModel *model;
818         GtkTreeIter   iter;
819         McPresence    presence = LAST_MC_PRESENCE;
820
821         model = gtk_combo_box_get_model (GTK_COMBO_BOX (dialog->combobox_status));
822         if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (dialog->combobox_status), &iter)) {
823                 gtk_tree_model_get (model, &iter,
824                                     COL_PRESENCE, &presence,
825                                     -1);
826         }
827
828         return presence;
829 }
830
831 static void
832 presence_chooser_dialog_status_changed_cb (GtkWidget           *widget,
833                                            CustomMessageDialog *dialog)
834 {
835         GtkListStore *store;
836         GtkTreeIter   iter;
837         McPresence    presence = LAST_MC_PRESENCE;
838         GList        *messages, *l;
839
840         presence = presence_chooser_dialog_get_selected (dialog);
841
842         store = gtk_list_store_new (1, G_TYPE_STRING);
843         messages = empathy_status_presets_get (presence, -1);
844         for (l = messages; l; l = l->next) {
845                 gtk_list_store_append (store, &iter);
846                 gtk_list_store_set (store, &iter, 0, l->data, -1);
847         }
848
849         gtk_entry_set_text (GTK_ENTRY (dialog->entry_message),
850                             messages ? messages->data : "");
851
852         g_list_free (messages);
853
854         gtk_combo_box_set_model (GTK_COMBO_BOX (dialog->comboboxentry_message),
855                                  GTK_TREE_MODEL (store));
856
857         g_object_unref (store);
858 }
859
860 static void
861 presence_chooser_dialog_message_changed_cb (GtkWidget           *widget,
862                                             CustomMessageDialog *dialog)
863 {
864         McPresence   presence;
865         GList       *messages, *l;
866         const gchar *text;
867         gboolean     found = FALSE;
868
869         presence = presence_chooser_dialog_get_selected (dialog);
870         text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_message));
871
872         messages = empathy_status_presets_get (presence, -1);
873         for (l = messages; l; l = l->next) {
874                 if (!tp_strdiff (text, l->data)) {
875                         found = TRUE;
876                         break;
877                 }
878         }
879         g_list_free (messages);
880
881         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->checkbutton_save),
882                                       found);
883 }
884
885 static void
886 presence_chooser_dialog_save_toggled_cb (GtkWidget           *widget,
887                                          CustomMessageDialog *dialog)
888 {
889         gboolean     active;
890         McPresence   state;
891         const gchar *text;
892
893         active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->checkbutton_save));
894         state = presence_chooser_dialog_get_selected (dialog);
895         text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_message));
896
897         if (active) {
898                 empathy_status_presets_set_last (state, text);
899         } else {
900                 empathy_status_presets_remove (state, text);
901         }
902 }
903
904 static void
905 presence_chooser_dialog_setup (CustomMessageDialog *dialog)
906 {
907         GtkListStore    *store;
908         GtkCellRenderer *renderer;
909         GtkTreeIter      iter;
910         guint            i;
911
912         store = gtk_list_store_new (COL_COUNT,
913                                     G_TYPE_STRING,     /* Icon name */
914                                     G_TYPE_STRING,     /* Label     */
915                                     MC_TYPE_PRESENCE); /* Presence   */
916         gtk_combo_box_set_model (GTK_COMBO_BOX (dialog->combobox_status),
917                                  GTK_TREE_MODEL (store));
918
919         renderer = gtk_cell_renderer_pixbuf_new ();
920         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (dialog->combobox_status), renderer, FALSE);
921         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (dialog->combobox_status), renderer,
922                                         "icon-name", COL_ICON,
923                                         NULL);
924         g_object_set (renderer, "stock-size", GTK_ICON_SIZE_BUTTON, NULL);
925
926         renderer = gtk_cell_renderer_text_new ();
927         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (dialog->combobox_status), renderer, TRUE);
928         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (dialog->combobox_status), renderer,
929                                         "text", COL_LABEL,
930                                         NULL);
931
932         for (i = 0; i < G_N_ELEMENTS (states); i += 2) {
933                 if (!states[i+1]) {
934                         continue;
935                 }
936
937                 gtk_list_store_append (store, &iter);
938                 gtk_list_store_set (store, &iter,
939                                     COL_ICON, empathy_icon_name_for_presence (states[i]),
940                                     COL_LABEL, empathy_presence_get_default_message (states[i]),
941                                     COL_PRESENCE, states[i],
942                                     -1);
943         }
944
945         gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->combobox_status), 0);
946 }
947
948 static void
949 presence_chooser_dialog_response_cb (GtkWidget           *widget,
950                                      gint                 response,
951                                      CustomMessageDialog *dialog)
952 {
953         if (response == GTK_RESPONSE_APPLY) {
954                 McPresence   state;
955                 const gchar *text;
956
957                 state = presence_chooser_dialog_get_selected (dialog);
958                 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_message));
959
960                 presence_chooser_set_state (state, text);
961         }
962
963         gtk_widget_destroy (widget);
964 }
965
966 static void
967 presence_chooser_dialog_destroy_cb (GtkWidget           *widget,
968                                     CustomMessageDialog *dialog)
969 {
970
971         g_free (dialog);
972         message_dialog = NULL;
973 }
974
975 static void
976 presence_chooser_dialog_show (GtkWindow *parent)
977 {
978         GladeXML *glade;
979         gchar    *filename;
980
981         if (message_dialog) {
982                 gtk_window_present (GTK_WINDOW (message_dialog->dialog));
983                 return;
984         }
985
986         message_dialog = g_new0 (CustomMessageDialog, 1);
987
988         filename = empathy_file_lookup ("empathy-presence-chooser.glade",
989                                         "libempathy-gtk");
990         glade = empathy_glade_get_file (filename,
991                                        "custom_message_dialog",
992                                        NULL,
993                                        "custom_message_dialog", &message_dialog->dialog,
994                                        "checkbutton_save", &message_dialog->checkbutton_save,
995                                        "comboboxentry_message", &message_dialog->comboboxentry_message,
996                                        "combobox_status", &message_dialog->combobox_status,
997                                        NULL);
998         g_free (filename);
999
1000         empathy_glade_connect (glade,
1001                                message_dialog,
1002                                "custom_message_dialog", "destroy", presence_chooser_dialog_destroy_cb,
1003                                "custom_message_dialog", "response", presence_chooser_dialog_response_cb,
1004                                "combobox_status", "changed", presence_chooser_dialog_status_changed_cb,
1005                                "checkbutton_save", "toggled", presence_chooser_dialog_save_toggled_cb,
1006                                NULL);
1007
1008         g_object_unref (glade);
1009
1010         /* Setup the message combobox */
1011         message_dialog->entry_message = GTK_BIN (message_dialog->comboboxentry_message)->child;
1012         gtk_entry_set_activates_default (GTK_ENTRY (message_dialog->entry_message), TRUE);
1013         gtk_entry_set_width_chars (GTK_ENTRY (message_dialog->entry_message), 25);
1014         g_signal_connect (message_dialog->entry_message, "changed",
1015                           G_CALLBACK (presence_chooser_dialog_message_changed_cb),
1016                           message_dialog);
1017
1018         presence_chooser_dialog_setup (message_dialog);
1019
1020         gtk_combo_box_entry_set_text_column (GTK_COMBO_BOX_ENTRY (message_dialog->comboboxentry_message), 0);
1021
1022         if (parent)
1023         {
1024                 gtk_window_set_transient_for (
1025                                 GTK_WINDOW (message_dialog->dialog),
1026                                 parent);
1027         }
1028
1029         gtk_widget_show_all (message_dialog->dialog);
1030 }
1031