]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-presence-chooser.c
d362280106de7394f508df7d628862149c184fa3
[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
273                 priv->editing_status = FALSE;
274         }
275 }
276
277 static void
278 mc_set_custom_state (EmpathyPresenceChooser *self)
279 {
280         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
281         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (self));
282
283         /* update the status with MC */
284         const char *status = gtk_entry_get_text (GTK_ENTRY (entry));
285         DEBUG ("Sending state to MC-> %s (%s)\n",
286                         g_enum_get_value (g_type_class_peek (MC_TYPE_PRESENCE),
287                                 priv->state)->value_name,
288                         status);
289         empathy_idle_set_presence (priv->idle, priv->state, status);
290 }
291
292 static void
293 ui_set_custom_state (EmpathyPresenceChooser *self,
294                            McPresence state,
295                            const char *status)
296 {
297         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
298         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (self));
299         const char *icon_name;
300
301         priv->block_set_editing++;
302         priv->block_changed++;
303
304         icon_name = empathy_icon_name_for_presence (state);
305         gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
306                         GTK_ENTRY_ICON_PRIMARY,
307                         icon_name);
308         gtk_entry_set_text (GTK_ENTRY (entry), status);
309
310         priv->block_changed--;
311         priv->block_set_editing--;
312 }
313
314 static void
315 entry_icon_release_cb (EmpathyPresenceChooser   *self,
316                        GtkEntryIconPosition      icon_pos,
317                        GdkEvent         *event,
318                        GtkEntry         *entry)
319 {
320         set_status_editing (self, FALSE);
321         mc_set_custom_state (self);
322 }
323
324 static void
325 entry_activate_cb (EmpathyPresenceChooser       *self,
326                    GtkEntry                     *entry)
327 {
328         set_status_editing (self, FALSE);
329         mc_set_custom_state (self);
330 }
331
332 static gboolean
333 entry_key_press_event_cb (EmpathyPresenceChooser        *self,
334                           GdkEventKey                   *event,
335                           GtkWidget                     *entry)
336 {
337         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
338
339         if (priv->editing_status && event->keyval == GDK_Escape)
340         {
341                 /* the user pressed Escape, undo the editing */
342                 set_status_editing (self, FALSE);
343                 presence_chooser_presence_changed_cb (self);
344
345                 return TRUE;
346         }
347
348         return FALSE; /* send this event elsewhere */
349 }
350
351 static void
352 changed_cb (GtkComboBox *self, gpointer user_data)
353 {
354         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
355
356         if (priv->block_changed) return;
357
358         GtkTreeIter iter;
359         char *icon_name;
360         int type = -1;
361
362         GtkTreeModel *model = gtk_combo_box_get_model (self);
363         if (!gtk_combo_box_get_active_iter (self, &iter))
364         {
365                 /* the combo is being edited to a custom entry */
366                 if (!priv->editing_status)
367                 {
368                         set_status_editing (EMPATHY_PRESENCE_CHOOSER (self), TRUE);
369                 }
370                 return;
371         }
372
373         gtk_tree_model_get (model, &iter,
374                         COL_STATE_ICON_NAME, &icon_name,
375                         COL_STATE, &priv->state,
376                         COL_TYPE, &type,
377                         -1);
378
379         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (self));
380
381         if (type == ENTRY_TYPE_EDIT_CUSTOM)
382         {
383                 /* recover the status that was unset because COL_STATUS_TEXT
384                  * is "". Unfortunately if you try and set COL_STATUS_TEXT to
385                  * NULL, it generates a g_critical. I wonder if there is a
386                  * better way around this. */
387                 const char *status = empathy_idle_get_status (priv->idle);
388                 priv->block_set_editing++;
389                 gtk_entry_set_text (GTK_ENTRY (entry), status);
390                 priv->block_set_editing--;
391
392                 /* attempt to get the toplevel for this widget */
393                 GtkWidget *window = gtk_widget_get_toplevel (GTK_WIDGET (self));
394                 if (!GTK_WIDGET_TOPLEVEL (window) || !GTK_IS_WINDOW (window))
395                 {
396                         window = NULL;
397                 }
398
399                 presence_chooser_dialog_show (GTK_WINDOW (window));
400         }
401         else if (type == ENTRY_TYPE_CUSTOM)
402         {
403                 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
404                                 GTK_ENTRY_ICON_PRIMARY,
405                                 icon_name);
406
407                 /* grab the focus */
408                 gtk_widget_grab_focus (entry);
409
410                 set_status_editing (EMPATHY_PRESENCE_CHOOSER (self), TRUE);
411         }
412         else
413         {
414                 char *status;
415                 /* just in case we were setting a new status when
416                  * things were changed */
417                 set_status_editing (EMPATHY_PRESENCE_CHOOSER (self), FALSE);
418
419                 gtk_tree_model_get (model, &iter,
420                                 COL_STATUS_TEXT, &status,
421                                 -1);
422
423                 empathy_idle_set_presence (priv->idle, priv->state, status);
424
425                 g_free (status);
426         }
427
428         g_free (icon_name);
429 }
430
431 static gboolean
432 combo_row_separator_func (GtkTreeModel  *model,
433                           GtkTreeIter   *iter,
434                           gpointer       data)
435 {
436         int type;
437         gtk_tree_model_get (model, iter,
438                         COL_TYPE, &type,
439                         -1);
440
441         return (type == ENTRY_TYPE_SEPARATOR);
442 }
443
444 static void
445 empathy_presence_chooser_init (EmpathyPresenceChooser *chooser)
446 {
447         EmpathyPresenceChooserPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chooser,
448                 EMPATHY_TYPE_PRESENCE_CHOOSER, EmpathyPresenceChooserPriv);
449
450         chooser->priv = priv;
451         
452         GtkTreeModel *model = create_model ();
453
454         gtk_combo_box_set_model (GTK_COMBO_BOX (chooser), GTK_TREE_MODEL (model));
455         gtk_combo_box_entry_set_text_column (GTK_COMBO_BOX_ENTRY (chooser),
456                         COL_STATUS_TEXT);
457         gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (chooser),
458                         combo_row_separator_func,
459                         NULL, NULL);
460         
461         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (chooser));
462         gtk_entry_set_icon_activatable (GTK_ENTRY (entry),
463                         GTK_ENTRY_ICON_PRIMARY, FALSE);
464         g_signal_connect_object (entry, "icon-release",
465                         G_CALLBACK (entry_icon_release_cb), chooser,
466                         G_CONNECT_SWAPPED);
467         g_signal_connect_object (entry, "activate",
468                         G_CALLBACK (entry_activate_cb), chooser,
469                         G_CONNECT_SWAPPED);
470         g_signal_connect_object (entry, "key-press-event",
471                         G_CALLBACK (entry_key_press_event_cb), chooser,
472                         G_CONNECT_SWAPPED);
473         // FIXME - should this also happen when the user presses TAB ?
474
475         GtkCellRenderer *renderer;
476         gtk_cell_layout_clear (GTK_CELL_LAYOUT (chooser));
477
478         renderer = gtk_cell_renderer_pixbuf_new ();
479         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser), renderer, FALSE);
480         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser), renderer,
481                         "icon-name", COL_STATE_ICON_NAME,
482                         NULL);
483         g_object_set (renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
484
485         renderer = gtk_cell_renderer_text_new ();
486         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser), renderer, TRUE);
487         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser), renderer,
488                         "markup", COL_DISPLAY_MARKUP,
489                         NULL);
490
491         g_object_unref (model);
492
493         g_signal_connect (chooser, "notify::popup-shown",
494                         G_CALLBACK (popup_shown_cb), NULL);
495         g_signal_connect (chooser, "changed",
496                         G_CALLBACK (changed_cb), NULL);
497
498         priv->idle = empathy_idle_dup_singleton ();
499         presence_chooser_presence_changed_cb (chooser);
500         g_signal_connect_swapped (priv->idle, "notify",
501                                   G_CALLBACK (presence_chooser_presence_changed_cb),
502                                   chooser);
503
504         g_object_set (chooser,
505                         // FIXME: this string sucks
506                         "tooltip-text", _("Set your presence and current status"),
507                         NULL);
508 }
509
510 static void
511 presence_chooser_finalize (GObject *object)
512 {
513         EmpathyPresenceChooserPriv *priv;
514
515         priv = GET_PRIV (object);
516
517         if (priv->flash_timeout_id) {
518                 g_source_remove (priv->flash_timeout_id);
519         }
520
521         g_signal_handlers_disconnect_by_func (priv->idle,
522                                               presence_chooser_presence_changed_cb,
523                                               object);
524         g_object_unref (priv->idle);
525
526         G_OBJECT_CLASS (empathy_presence_chooser_parent_class)->finalize (object);
527 }
528
529 GtkWidget *
530 empathy_presence_chooser_new (void)
531 {
532         GtkWidget *chooser;
533
534         chooser = g_object_new (EMPATHY_TYPE_PRESENCE_CHOOSER, NULL);
535
536         return chooser;
537 }
538
539 static void
540 presence_chooser_presence_changed_cb (EmpathyPresenceChooser *chooser)
541 {
542         EmpathyPresenceChooserPriv *priv;
543         McPresence                 state;
544         McPresence                 flash_state;
545         const gchar               *status;
546
547         priv = GET_PRIV (chooser);
548
549         priv->state = state = empathy_idle_get_state (priv->idle);
550         status = empathy_idle_get_status (priv->idle);
551         flash_state = empathy_idle_get_flash_state (priv->idle);
552
553         /* look through the model and attempt to find a matching state */
554         GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser));
555         GtkTreeIter iter;
556         gboolean valid, match_state = FALSE, match = FALSE;
557         for (valid = gtk_tree_model_get_iter_first (model, &iter);
558              valid;
559              valid = gtk_tree_model_iter_next (model, &iter))
560         {
561                 int m_type;
562                 McPresence m_state;
563                 char *m_status;
564
565                 gtk_tree_model_get (model, &iter,
566                                 COL_STATE, &m_state,
567                                 COL_TYPE, &m_type,
568                                 -1);
569
570                 if (m_type == ENTRY_TYPE_CUSTOM ||
571                     m_type == ENTRY_TYPE_SEPARATOR ||
572                     m_type == ENTRY_TYPE_EDIT_CUSTOM)
573                 {
574                         continue;
575                 }
576                 else if (!match_state && state == m_state)
577                 {
578                         /* we are now in the section that can contain our
579                          * match */
580                         match_state = TRUE;
581                 }
582                 else if (match_state && state != m_state)
583                 {
584                         /* we have passed the section that can contain our
585                          * match */
586                         break;
587                 }
588
589                 gtk_tree_model_get (model, &iter,
590                                 COL_STATUS_TEXT, &m_status,
591                                 -1);
592
593                 match = !strcmp (status, m_status);
594
595                 g_free (m_status);
596
597                 if (match) break;
598
599         }
600
601         if (match)
602         {
603                 priv->block_changed++;
604                 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser), &iter);
605                 priv->block_changed--;
606         }
607         else
608         {
609                 // FIXME - do we insert the match into the menu?
610                 ui_set_custom_state (chooser, state, status);
611         }
612
613         if (flash_state != MC_PRESENCE_UNSET) {
614                 presence_chooser_flash_start (chooser, state, flash_state);
615         } else {
616                 presence_chooser_flash_stop (chooser, state);
617         }
618 }
619
620 static gboolean
621 presence_chooser_flash_timeout_cb (EmpathyPresenceChooser *chooser)
622 {
623         EmpathyPresenceChooserPriv *priv;
624         McPresence                 state;
625         static gboolean            on = FALSE;
626
627         priv = GET_PRIV (chooser);
628
629         if (on) {
630                 state = priv->flash_state_1;
631         } else {
632                 state = priv->flash_state_2;
633         }
634
635         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (chooser));
636         gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
637                         GTK_ENTRY_ICON_PRIMARY,
638                         empathy_icon_name_for_presence (state));
639
640         on = !on;
641
642         return TRUE;
643 }
644
645 static void
646 presence_chooser_flash_start (EmpathyPresenceChooser *chooser,
647                               McPresence             state_1,
648                               McPresence             state_2)
649 {
650         EmpathyPresenceChooserPriv *priv;
651
652         g_return_if_fail (EMPATHY_IS_PRESENCE_CHOOSER (chooser));
653
654         priv = GET_PRIV (chooser);
655
656         priv->flash_state_1 = state_1;
657         priv->flash_state_2 = state_2;
658
659         if (!priv->flash_timeout_id) {
660                 priv->flash_timeout_id = g_timeout_add (FLASH_TIMEOUT,
661                                                         (GSourceFunc) presence_chooser_flash_timeout_cb,
662                                                         chooser);
663         }
664 }
665
666 static void
667 presence_chooser_flash_stop (EmpathyPresenceChooser *chooser,
668                              McPresence             state)
669 {
670         EmpathyPresenceChooserPriv *priv;
671
672         g_return_if_fail (EMPATHY_IS_PRESENCE_CHOOSER (chooser));
673
674         priv = GET_PRIV (chooser);
675
676         if (priv->flash_timeout_id) {
677                 g_source_remove (priv->flash_timeout_id);
678                 priv->flash_timeout_id = 0;
679         }
680         GtkWidget *entry = gtk_bin_get_child (GTK_BIN (chooser));
681         
682         gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
683                         GTK_ENTRY_ICON_PRIMARY,
684                         empathy_icon_name_for_presence (state));
685
686         // FIXME - what does this do?
687         // priv->last_state = state;
688 }
689
690 GtkWidget *
691 empathy_presence_chooser_create_menu (void)
692 {
693         const gchar *status;
694         GtkWidget   *menu;
695         GtkWidget   *item;
696         GtkWidget   *image;
697         guint        i;
698
699         menu = gtk_menu_new ();
700
701         for (i = 0; i < G_N_ELEMENTS (states); i += 2) {
702                 GList       *list, *l;
703
704                 status = empathy_presence_get_default_message (states[i]);
705                 presence_chooser_menu_add_item (menu,
706                                                 status,
707                                                 states[i]);
708
709                 if (states[i+1]) {
710                         /* Set custom messages if wanted */
711                         list = empathy_status_presets_get (states[i], 5);
712                         for (l = list; l; l = l->next) {
713                                 presence_chooser_menu_add_item (menu,
714                                                                 l->data,
715                                                                 states[i]);
716                         }
717                         g_list_free (list);
718                 }
719
720         }
721
722         /* Separator. */
723         item = gtk_menu_item_new ();
724         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
725         gtk_widget_show (item);
726
727         /* Custom messages */
728         item = gtk_image_menu_item_new_with_label (_("Custom messages..."));
729         image = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
730         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
731         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
732         gtk_widget_show (image);
733         gtk_widget_show (item);
734
735         g_signal_connect (item,
736                           "activate",
737                           G_CALLBACK (presence_chooser_custom_activate_cb),
738                           NULL);
739
740         return menu;
741 }
742
743 static void
744 presence_chooser_menu_add_item (GtkWidget   *menu,
745                                 const gchar *str,
746                                 McPresence   state)
747 {
748         GtkWidget   *item;
749         GtkWidget   *image;
750         const gchar *icon_name;
751
752         item = gtk_image_menu_item_new_with_label (str);
753         icon_name = empathy_icon_name_for_presence (state);
754
755         g_signal_connect (item, "activate",
756                           G_CALLBACK (presence_chooser_noncustom_activate_cb),
757                           NULL);
758
759         image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
760         gtk_widget_show (image);
761
762         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
763         gtk_widget_show (item);
764
765         g_object_set_data_full (G_OBJECT (item),
766                                 "status", g_strdup (str),
767                                 (GDestroyNotify) g_free);
768
769         g_object_set_data (G_OBJECT (item), "state", GINT_TO_POINTER (state));
770
771         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
772 }
773
774 static void
775 presence_chooser_noncustom_activate_cb (GtkWidget *item,
776                                         gpointer   user_data)
777 {
778         McPresence   state;
779         const gchar *status;
780
781         status = g_object_get_data (G_OBJECT (item), "status");
782         state = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "state"));
783
784         presence_chooser_set_state (state, status);
785 }
786
787 static void
788 presence_chooser_set_state (McPresence   state,
789                             const gchar *status)
790 {
791         EmpathyIdle *idle;
792
793         idle = empathy_idle_dup_singleton ();
794         empathy_idle_set_presence (idle, state, status);
795         g_object_unref (idle);
796 }
797
798 static void
799 presence_chooser_custom_activate_cb (GtkWidget *item,
800                                      gpointer   user_data)
801 {
802         presence_chooser_dialog_show (NULL);
803 }
804
805 static McPresence
806 presence_chooser_dialog_get_selected (CustomMessageDialog *dialog)
807 {
808         GtkTreeModel *model;
809         GtkTreeIter   iter;
810         McPresence    presence = LAST_MC_PRESENCE;
811
812         model = gtk_combo_box_get_model (GTK_COMBO_BOX (dialog->combobox_status));
813         if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (dialog->combobox_status), &iter)) {
814                 gtk_tree_model_get (model, &iter,
815                                     COL_PRESENCE, &presence,
816                                     -1);
817         }
818
819         return presence;
820 }
821
822 static void
823 presence_chooser_dialog_status_changed_cb (GtkWidget           *widget,
824                                            CustomMessageDialog *dialog)
825 {
826         GtkListStore *store;
827         GtkTreeIter   iter;
828         McPresence    presence = LAST_MC_PRESENCE;
829         GList        *messages, *l;
830
831         presence = presence_chooser_dialog_get_selected (dialog);
832
833         store = gtk_list_store_new (1, G_TYPE_STRING);
834         messages = empathy_status_presets_get (presence, -1);
835         for (l = messages; l; l = l->next) {
836                 gtk_list_store_append (store, &iter);
837                 gtk_list_store_set (store, &iter, 0, l->data, -1);
838         }
839
840         gtk_entry_set_text (GTK_ENTRY (dialog->entry_message),
841                             messages ? messages->data : "");
842
843         g_list_free (messages);
844
845         gtk_combo_box_set_model (GTK_COMBO_BOX (dialog->comboboxentry_message),
846                                  GTK_TREE_MODEL (store));
847
848         g_object_unref (store);
849 }
850
851 static void
852 presence_chooser_dialog_message_changed_cb (GtkWidget           *widget,
853                                             CustomMessageDialog *dialog)
854 {
855         McPresence   presence;
856         GList       *messages, *l;
857         const gchar *text;
858         gboolean     found = FALSE;
859
860         presence = presence_chooser_dialog_get_selected (dialog);
861         text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_message));
862
863         messages = empathy_status_presets_get (presence, -1);
864         for (l = messages; l; l = l->next) {
865                 if (!tp_strdiff (text, l->data)) {
866                         found = TRUE;
867                         break;
868                 }
869         }
870         g_list_free (messages);
871
872         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->checkbutton_save),
873                                       found);
874 }
875
876 static void
877 presence_chooser_dialog_save_toggled_cb (GtkWidget           *widget,
878                                          CustomMessageDialog *dialog)
879 {
880         gboolean     active;
881         McPresence   state;
882         const gchar *text;
883
884         active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->checkbutton_save));
885         state = presence_chooser_dialog_get_selected (dialog);
886         text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_message));
887
888         if (active) {
889                 empathy_status_presets_set_last (state, text);
890         } else {
891                 empathy_status_presets_remove (state, text);
892         }
893 }
894
895 static void
896 presence_chooser_dialog_setup (CustomMessageDialog *dialog)
897 {
898         GtkListStore    *store;
899         GtkCellRenderer *renderer;
900         GtkTreeIter      iter;
901         guint            i;
902
903         store = gtk_list_store_new (COL_COUNT,
904                                     G_TYPE_STRING,     /* Icon name */
905                                     G_TYPE_STRING,     /* Label     */
906                                     MC_TYPE_PRESENCE); /* Presence   */
907         gtk_combo_box_set_model (GTK_COMBO_BOX (dialog->combobox_status),
908                                  GTK_TREE_MODEL (store));
909
910         renderer = gtk_cell_renderer_pixbuf_new ();
911         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (dialog->combobox_status), renderer, FALSE);
912         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (dialog->combobox_status), renderer,
913                                         "icon-name", COL_ICON,
914                                         NULL);
915         g_object_set (renderer, "stock-size", GTK_ICON_SIZE_BUTTON, NULL);
916
917         renderer = gtk_cell_renderer_text_new ();
918         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (dialog->combobox_status), renderer, TRUE);
919         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (dialog->combobox_status), renderer,
920                                         "text", COL_LABEL,
921                                         NULL);
922
923         for (i = 0; i < G_N_ELEMENTS (states); i += 2) {
924                 if (!states[i+1]) {
925                         continue;
926                 }
927
928                 gtk_list_store_append (store, &iter);
929                 gtk_list_store_set (store, &iter,
930                                     COL_ICON, empathy_icon_name_for_presence (states[i]),
931                                     COL_LABEL, empathy_presence_get_default_message (states[i]),
932                                     COL_PRESENCE, states[i],
933                                     -1);
934         }
935
936         gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->combobox_status), 0);
937 }
938
939 static void
940 presence_chooser_dialog_response_cb (GtkWidget           *widget,
941                                      gint                 response,
942                                      CustomMessageDialog *dialog)
943 {
944         if (response == GTK_RESPONSE_APPLY) {
945                 McPresence   state;
946                 const gchar *text;
947
948                 state = presence_chooser_dialog_get_selected (dialog);
949                 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_message));
950
951                 presence_chooser_set_state (state, text);
952         }
953
954         gtk_widget_destroy (widget);
955 }
956
957 static void
958 presence_chooser_dialog_destroy_cb (GtkWidget           *widget,
959                                     CustomMessageDialog *dialog)
960 {
961
962         g_free (dialog);
963         message_dialog = NULL;
964 }
965
966 static void
967 presence_chooser_dialog_show (GtkWindow *parent)
968 {
969         GladeXML *glade;
970         gchar    *filename;
971
972         if (message_dialog) {
973                 gtk_window_present (GTK_WINDOW (message_dialog->dialog));
974                 return;
975         }
976
977         message_dialog = g_new0 (CustomMessageDialog, 1);
978
979         filename = empathy_file_lookup ("empathy-presence-chooser.glade",
980                                         "libempathy-gtk");
981         glade = empathy_glade_get_file (filename,
982                                        "custom_message_dialog",
983                                        NULL,
984                                        "custom_message_dialog", &message_dialog->dialog,
985                                        "checkbutton_save", &message_dialog->checkbutton_save,
986                                        "comboboxentry_message", &message_dialog->comboboxentry_message,
987                                        "combobox_status", &message_dialog->combobox_status,
988                                        NULL);
989         g_free (filename);
990
991         empathy_glade_connect (glade,
992                                message_dialog,
993                                "custom_message_dialog", "destroy", presence_chooser_dialog_destroy_cb,
994                                "custom_message_dialog", "response", presence_chooser_dialog_response_cb,
995                                "combobox_status", "changed", presence_chooser_dialog_status_changed_cb,
996                                "checkbutton_save", "toggled", presence_chooser_dialog_save_toggled_cb,
997                                NULL);
998
999         g_object_unref (glade);
1000
1001         /* Setup the message combobox */
1002         message_dialog->entry_message = GTK_BIN (message_dialog->comboboxentry_message)->child;
1003         gtk_entry_set_activates_default (GTK_ENTRY (message_dialog->entry_message), TRUE);
1004         gtk_entry_set_width_chars (GTK_ENTRY (message_dialog->entry_message), 25);
1005         g_signal_connect (message_dialog->entry_message, "changed",
1006                           G_CALLBACK (presence_chooser_dialog_message_changed_cb),
1007                           message_dialog);
1008
1009         presence_chooser_dialog_setup (message_dialog);
1010
1011         gtk_combo_box_entry_set_text_column (GTK_COMBO_BOX_ENTRY (message_dialog->comboboxentry_message), 0);
1012
1013         if (parent)
1014         {
1015                 gtk_window_set_transient_for (
1016                                 GTK_WINDOW (message_dialog->dialog),
1017                                 parent);
1018         }
1019
1020         gtk_widget_show_all (message_dialog->dialog);
1021 }
1022