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