]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-presence-chooser.c
When editing custom messages, preseed the entry with the current status
[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
505         priv->idle = empathy_idle_dup_singleton ();
506         presence_chooser_presence_changed_cb (chooser);
507         g_signal_connect_swapped (priv->idle, "notify",
508                                   G_CALLBACK (presence_chooser_presence_changed_cb),
509                                   chooser);
510
511         g_object_set (chooser,
512                         // FIXME: this string sucks
513                         "tooltip-text", _("Set your presence and current status"),
514                         NULL);
515 }
516
517 static void
518 presence_chooser_finalize (GObject *object)
519 {
520         EmpathyPresenceChooserPriv *priv;
521
522         priv = GET_PRIV (object);
523
524         if (priv->flash_timeout_id) {
525                 g_source_remove (priv->flash_timeout_id);
526         }
527
528         g_signal_handlers_disconnect_by_func (priv->idle,
529                                               presence_chooser_presence_changed_cb,
530                                               object);
531         g_object_unref (priv->idle);
532
533         G_OBJECT_CLASS (empathy_presence_chooser_parent_class)->finalize (object);
534 }
535
536 GtkWidget *
537 empathy_presence_chooser_new (void)
538 {
539         GtkWidget *chooser;
540
541         chooser = g_object_new (EMPATHY_TYPE_PRESENCE_CHOOSER, NULL);
542
543         return chooser;
544 }
545
546 static void
547 presence_chooser_presence_changed_cb (EmpathyPresenceChooser *chooser)
548 {
549         EmpathyPresenceChooserPriv *priv;
550         McPresence                 state;
551         McPresence                 flash_state;
552         const gchar               *status;
553
554         priv = GET_PRIV (chooser);
555
556         priv->state = state = empathy_idle_get_state (priv->idle);
557         status = empathy_idle_get_status (priv->idle);
558         flash_state = empathy_idle_get_flash_state (priv->idle);
559
560         /* look through the model and attempt to find a matching state */
561         GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser));
562         GtkTreeIter iter;
563         gboolean valid, match_state = FALSE, match = FALSE;
564         for (valid = gtk_tree_model_get_iter_first (model, &iter);
565              valid;
566              valid = gtk_tree_model_iter_next (model, &iter))
567         {
568                 int m_type;
569                 McPresence m_state;
570                 char *m_status;
571
572                 gtk_tree_model_get (model, &iter,
573                                 COL_STATE, &m_state,
574                                 COL_TYPE, &m_type,
575                                 -1);
576
577                 if (m_type == ENTRY_TYPE_CUSTOM ||
578                     m_type == ENTRY_TYPE_SEPARATOR ||
579                     m_type == ENTRY_TYPE_EDIT_CUSTOM)
580                 {
581                         continue;
582                 }
583                 else if (!match_state && state == m_state)
584                 {
585                         /* we are now in the section that can contain our
586                          * match */
587                         match_state = TRUE;
588                 }
589                 else if (match_state && state != m_state)
590                 {
591                         /* we have passed the section that can contain our
592                          * match */
593                         break;
594                 }
595
596                 gtk_tree_model_get (model, &iter,
597                                 COL_STATUS_TEXT, &m_status,
598                                 -1);
599
600                 match = !strcmp (status, m_status);
601
602                 g_free (m_status);
603
604                 if (match) break;
605
606         }
607
608         if (match)
609         {
610                 priv->block_changed++;
611                 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser), &iter);
612                 priv->block_changed--;
613         }
614         else
615         {
616                 // FIXME - do we insert the match into the menu?
617                 ui_set_custom_state (chooser, state, status);
618         }
619
620         if (flash_state != MC_PRESENCE_UNSET) {
621                 presence_chooser_flash_start (chooser, state, flash_state);
622         } else {
623                 presence_chooser_flash_stop (chooser, state);
624         }
625 }
626
627 static gboolean
628 presence_chooser_flash_timeout_cb (EmpathyPresenceChooser *chooser)
629 {
630         EmpathyPresenceChooserPriv *priv;
631         McPresence                 state;
632         static gboolean            on = FALSE;
633
634         priv = GET_PRIV (chooser);
635
636         if (on) {
637                 state = priv->flash_state_1;
638         } else {
639                 state = priv->flash_state_2;
640         }
641
642         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (chooser));
643         gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
644                         GTK_ENTRY_ICON_PRIMARY,
645                         empathy_icon_name_for_presence (state));
646
647         on = !on;
648
649         return TRUE;
650 }
651
652 static void
653 presence_chooser_flash_start (EmpathyPresenceChooser *chooser,
654                               McPresence             state_1,
655                               McPresence             state_2)
656 {
657         EmpathyPresenceChooserPriv *priv;
658
659         g_return_if_fail (EMPATHY_IS_PRESENCE_CHOOSER (chooser));
660
661         priv = GET_PRIV (chooser);
662
663         priv->flash_state_1 = state_1;
664         priv->flash_state_2 = state_2;
665
666         if (!priv->flash_timeout_id) {
667                 priv->flash_timeout_id = g_timeout_add (FLASH_TIMEOUT,
668                                                         (GSourceFunc) presence_chooser_flash_timeout_cb,
669                                                         chooser);
670         }
671 }
672
673 static void
674 presence_chooser_flash_stop (EmpathyPresenceChooser *chooser,
675                              McPresence             state)
676 {
677         EmpathyPresenceChooserPriv *priv;
678
679         g_return_if_fail (EMPATHY_IS_PRESENCE_CHOOSER (chooser));
680
681         priv = GET_PRIV (chooser);
682
683         if (priv->flash_timeout_id) {
684                 g_source_remove (priv->flash_timeout_id);
685                 priv->flash_timeout_id = 0;
686         }
687         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (chooser));
688         
689         gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
690                         GTK_ENTRY_ICON_PRIMARY,
691                         empathy_icon_name_for_presence (state));
692
693         // FIXME - what does this do?
694         // priv->last_state = state;
695 }
696
697 GtkWidget *
698 empathy_presence_chooser_create_menu (void)
699 {
700         const gchar *status;
701         GtkWidget   *menu;
702         GtkWidget   *item;
703         GtkWidget   *image;
704         guint        i;
705
706         menu = gtk_menu_new ();
707
708         for (i = 0; i < G_N_ELEMENTS (states); i += 2) {
709                 GList       *list, *l;
710
711                 status = empathy_presence_get_default_message (states[i]);
712                 presence_chooser_menu_add_item (menu,
713                                                 status,
714                                                 states[i]);
715
716                 if (states[i+1]) {
717                         /* Set custom messages if wanted */
718                         list = empathy_status_presets_get (states[i], 5);
719                         for (l = list; l; l = l->next) {
720                                 presence_chooser_menu_add_item (menu,
721                                                                 l->data,
722                                                                 states[i]);
723                         }
724                         g_list_free (list);
725                 }
726
727         }
728
729         /* Separator. */
730         item = gtk_menu_item_new ();
731         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
732         gtk_widget_show (item);
733
734         /* Custom messages */
735         item = gtk_image_menu_item_new_with_label (_("Custom messages..."));
736         image = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
737         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
738         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
739         gtk_widget_show (image);
740         gtk_widget_show (item);
741
742         g_signal_connect (item,
743                           "activate",
744                           G_CALLBACK (presence_chooser_custom_activate_cb),
745                           NULL);
746
747         return menu;
748 }
749
750 static void
751 presence_chooser_menu_add_item (GtkWidget   *menu,
752                                 const gchar *str,
753                                 McPresence   state)
754 {
755         GtkWidget   *item;
756         GtkWidget   *image;
757         const gchar *icon_name;
758
759         item = gtk_image_menu_item_new_with_label (str);
760         icon_name = empathy_icon_name_for_presence (state);
761
762         g_signal_connect (item, "activate",
763                           G_CALLBACK (presence_chooser_noncustom_activate_cb),
764                           NULL);
765
766         image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
767         gtk_widget_show (image);
768
769         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
770         gtk_widget_show (item);
771
772         g_object_set_data_full (G_OBJECT (item),
773                                 "status", g_strdup (str),
774                                 (GDestroyNotify) g_free);
775
776         g_object_set_data (G_OBJECT (item), "state", GINT_TO_POINTER (state));
777
778         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
779 }
780
781 static void
782 presence_chooser_noncustom_activate_cb (GtkWidget *item,
783                                         gpointer   user_data)
784 {
785         McPresence   state;
786         const gchar *status;
787
788         status = g_object_get_data (G_OBJECT (item), "status");
789         state = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "state"));
790
791         presence_chooser_set_state (state, status);
792 }
793
794 static void
795 presence_chooser_set_state (McPresence   state,
796                             const gchar *status)
797 {
798         EmpathyIdle *idle;
799
800         idle = empathy_idle_dup_singleton ();
801         empathy_idle_set_presence (idle, state, status);
802         g_object_unref (idle);
803 }
804
805 static void
806 presence_chooser_custom_activate_cb (GtkWidget *item,
807                                      gpointer   user_data)
808 {
809         presence_chooser_dialog_show (NULL);
810 }
811
812 static McPresence
813 presence_chooser_dialog_get_selected (CustomMessageDialog *dialog)
814 {
815         GtkTreeModel *model;
816         GtkTreeIter   iter;
817         McPresence    presence = LAST_MC_PRESENCE;
818
819         model = gtk_combo_box_get_model (GTK_COMBO_BOX (dialog->combobox_status));
820         if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (dialog->combobox_status), &iter)) {
821                 gtk_tree_model_get (model, &iter,
822                                     COL_PRESENCE, &presence,
823                                     -1);
824         }
825
826         return presence;
827 }
828
829 static void
830 presence_chooser_dialog_status_changed_cb (GtkWidget           *widget,
831                                            CustomMessageDialog *dialog)
832 {
833         GtkListStore *store;
834         GtkTreeIter   iter;
835         McPresence    presence = LAST_MC_PRESENCE;
836         GList        *messages, *l;
837
838         presence = presence_chooser_dialog_get_selected (dialog);
839
840         store = gtk_list_store_new (1, G_TYPE_STRING);
841         messages = empathy_status_presets_get (presence, -1);
842         for (l = messages; l; l = l->next) {
843                 gtk_list_store_append (store, &iter);
844                 gtk_list_store_set (store, &iter, 0, l->data, -1);
845         }
846
847         gtk_entry_set_text (GTK_ENTRY (dialog->entry_message),
848                             messages ? messages->data : "");
849
850         g_list_free (messages);
851
852         gtk_combo_box_set_model (GTK_COMBO_BOX (dialog->comboboxentry_message),
853                                  GTK_TREE_MODEL (store));
854
855         g_object_unref (store);
856 }
857
858 static void
859 presence_chooser_dialog_message_changed_cb (GtkWidget           *widget,
860                                             CustomMessageDialog *dialog)
861 {
862         McPresence   presence;
863         GList       *messages, *l;
864         const gchar *text;
865         gboolean     found = FALSE;
866
867         presence = presence_chooser_dialog_get_selected (dialog);
868         text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_message));
869
870         messages = empathy_status_presets_get (presence, -1);
871         for (l = messages; l; l = l->next) {
872                 if (!tp_strdiff (text, l->data)) {
873                         found = TRUE;
874                         break;
875                 }
876         }
877         g_list_free (messages);
878
879         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->checkbutton_save),
880                                       found);
881 }
882
883 static void
884 presence_chooser_dialog_save_toggled_cb (GtkWidget           *widget,
885                                          CustomMessageDialog *dialog)
886 {
887         gboolean     active;
888         McPresence   state;
889         const gchar *text;
890
891         active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->checkbutton_save));
892         state = presence_chooser_dialog_get_selected (dialog);
893         text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_message));
894
895         if (active) {
896                 empathy_status_presets_set_last (state, text);
897         } else {
898                 empathy_status_presets_remove (state, text);
899         }
900 }
901
902 static void
903 presence_chooser_dialog_setup (CustomMessageDialog *dialog)
904 {
905         GtkListStore    *store;
906         GtkCellRenderer *renderer;
907         GtkTreeIter      iter;
908         guint            i;
909
910         store = gtk_list_store_new (COL_COUNT,
911                                     G_TYPE_STRING,     /* Icon name */
912                                     G_TYPE_STRING,     /* Label     */
913                                     MC_TYPE_PRESENCE); /* Presence   */
914         gtk_combo_box_set_model (GTK_COMBO_BOX (dialog->combobox_status),
915                                  GTK_TREE_MODEL (store));
916
917         renderer = gtk_cell_renderer_pixbuf_new ();
918         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (dialog->combobox_status), renderer, FALSE);
919         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (dialog->combobox_status), renderer,
920                                         "icon-name", COL_ICON,
921                                         NULL);
922         g_object_set (renderer, "stock-size", GTK_ICON_SIZE_BUTTON, NULL);
923
924         renderer = gtk_cell_renderer_text_new ();
925         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (dialog->combobox_status), renderer, TRUE);
926         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (dialog->combobox_status), renderer,
927                                         "text", COL_LABEL,
928                                         NULL);
929
930         for (i = 0; i < G_N_ELEMENTS (states); i += 2) {
931                 if (!states[i+1]) {
932                         continue;
933                 }
934
935                 gtk_list_store_append (store, &iter);
936                 gtk_list_store_set (store, &iter,
937                                     COL_ICON, empathy_icon_name_for_presence (states[i]),
938                                     COL_LABEL, empathy_presence_get_default_message (states[i]),
939                                     COL_PRESENCE, states[i],
940                                     -1);
941         }
942
943         gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->combobox_status), 0);
944 }
945
946 static void
947 presence_chooser_dialog_response_cb (GtkWidget           *widget,
948                                      gint                 response,
949                                      CustomMessageDialog *dialog)
950 {
951         if (response == GTK_RESPONSE_APPLY) {
952                 McPresence   state;
953                 const gchar *text;
954
955                 state = presence_chooser_dialog_get_selected (dialog);
956                 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_message));
957
958                 presence_chooser_set_state (state, text);
959         }
960
961         gtk_widget_destroy (widget);
962 }
963
964 static void
965 presence_chooser_dialog_destroy_cb (GtkWidget           *widget,
966                                     CustomMessageDialog *dialog)
967 {
968
969         g_free (dialog);
970         message_dialog = NULL;
971 }
972
973 static void
974 presence_chooser_dialog_show (GtkWindow *parent)
975 {
976         GladeXML *glade;
977         gchar    *filename;
978
979         if (message_dialog) {
980                 gtk_window_present (GTK_WINDOW (message_dialog->dialog));
981                 return;
982         }
983
984         message_dialog = g_new0 (CustomMessageDialog, 1);
985
986         filename = empathy_file_lookup ("empathy-presence-chooser.glade",
987                                         "libempathy-gtk");
988         glade = empathy_glade_get_file (filename,
989                                        "custom_message_dialog",
990                                        NULL,
991                                        "custom_message_dialog", &message_dialog->dialog,
992                                        "checkbutton_save", &message_dialog->checkbutton_save,
993                                        "comboboxentry_message", &message_dialog->comboboxentry_message,
994                                        "combobox_status", &message_dialog->combobox_status,
995                                        NULL);
996         g_free (filename);
997
998         empathy_glade_connect (glade,
999                                message_dialog,
1000                                "custom_message_dialog", "destroy", presence_chooser_dialog_destroy_cb,
1001                                "custom_message_dialog", "response", presence_chooser_dialog_response_cb,
1002                                "combobox_status", "changed", presence_chooser_dialog_status_changed_cb,
1003                                "checkbutton_save", "toggled", presence_chooser_dialog_save_toggled_cb,
1004                                NULL);
1005
1006         g_object_unref (glade);
1007
1008         /* Setup the message combobox */
1009         message_dialog->entry_message = GTK_BIN (message_dialog->comboboxentry_message)->child;
1010         gtk_entry_set_activates_default (GTK_ENTRY (message_dialog->entry_message), TRUE);
1011         gtk_entry_set_width_chars (GTK_ENTRY (message_dialog->entry_message), 25);
1012         g_signal_connect (message_dialog->entry_message, "changed",
1013                           G_CALLBACK (presence_chooser_dialog_message_changed_cb),
1014                           message_dialog);
1015
1016         presence_chooser_dialog_setup (message_dialog);
1017
1018         gtk_combo_box_entry_set_text_column (GTK_COMBO_BOX_ENTRY (message_dialog->comboboxentry_message), 0);
1019
1020         if (parent)
1021         {
1022                 gtk_window_set_transient_for (
1023                                 GTK_WINDOW (message_dialog->dialog),
1024                                 parent);
1025         }
1026
1027         gtk_widget_show_all (message_dialog->dialog);
1028 }
1029