]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-presence-chooser.c
Tooltip is more usefull when editing status. Fixup
[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., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  USA
20  *
21  * Authors: Richard Hult <richard@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  *          Danielle Madeley <danielle.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/account-manager.h>
37 #include <telepathy-glib/util.h>
38
39 #include <libempathy/empathy-connectivity.h>
40 #include <libempathy/empathy-presence-manager.h>
41 #include <libempathy/empathy-utils.h>
42 #include <libempathy/empathy-status-presets.h>
43
44 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
45 #include <libempathy/empathy-debug.h>
46
47 #include "empathy-ui-utils.h"
48 #include "empathy-images.h"
49 #include "empathy-presence-chooser.h"
50 #include "empathy-status-preset-dialog.h"
51
52 /**
53  * SECTION:empathy-presence-chooser
54  * @title:EmpathyPresenceChooser
55  * @short_description: A widget used to change presence
56  * @include: libempathy-gtk/empathy-presence-chooser.h
57  *
58  * #EmpathyPresenceChooser is a widget which extends #GtkComboBoxEntry
59  * to change presence.
60  */
61
62 /**
63  * EmpathyAccountChooser:
64  * @parent: parent object
65  *
66  * Widget which extends #GtkComboBoxEntry to change presence.
67  */
68
69 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyPresenceChooser)
70
71 /* For custom message dialog */
72 enum {
73         COL_ICON,
74         COL_LABEL,
75         COL_PRESENCE,
76         COL_COUNT
77 };
78
79 /* For combobox's model */
80 enum {
81         COL_STATUS_TEXT,
82         COL_STATE_ICON_NAME,
83         COL_STATE,
84         COL_DISPLAY_MARKUP,
85         COL_STATUS_CUSTOMISABLE,
86         COL_TYPE,
87         N_COLUMNS
88 };
89
90 typedef enum  {
91         ENTRY_TYPE_BUILTIN,
92         ENTRY_TYPE_SAVED,
93         ENTRY_TYPE_CUSTOM,
94         ENTRY_TYPE_SEPARATOR,
95         ENTRY_TYPE_EDIT_CUSTOM,
96 } PresenceChooserEntryType;
97
98 typedef struct {
99         EmpathyPresenceManager *presence_mgr;
100         EmpathyConnectivity *connectivity;
101
102         gboolean     editing_status;
103         int          block_set_editing;
104         int          block_changed;
105         guint        focus_out_idle_source;
106
107         TpConnectionPresenceType state;
108         PresenceChooserEntryType previous_type;
109
110         TpAccountManager *account_manager;
111         GdkPixbuf *not_favorite_pixbuf;
112 } EmpathyPresenceChooserPriv;
113
114 /* States to be listed in the menu.
115  * Each state has a boolean telling if it can have custom message */
116 static struct { TpConnectionPresenceType state;
117          gboolean customisable;
118 } states[] = { { TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE } ,
119                          { TP_CONNECTION_PRESENCE_TYPE_BUSY, TRUE },
120                          { TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE },
121                          { TP_CONNECTION_PRESENCE_TYPE_HIDDEN, FALSE },
122                          { TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE},
123                          { TP_CONNECTION_PRESENCE_TYPE_UNSET, },
124                         };
125
126 static void            presence_chooser_constructed            (GObject                    *object);
127 static void            presence_chooser_finalize               (GObject                    *object);
128 static void            presence_chooser_presence_changed_cb    (EmpathyPresenceChooser      *chooser);
129 static void            presence_chooser_menu_add_item          (GtkWidget                  *menu,
130                                                                 const gchar                *str,
131                                                                 TpConnectionPresenceType                  state);
132 static void            presence_chooser_noncustom_activate_cb  (GtkWidget                  *item,
133                                                                 gpointer                    user_data);
134 static void            presence_chooser_set_state              (TpConnectionPresenceType                  state,
135                                                                 const gchar                *status);
136 static void            presence_chooser_custom_activate_cb     (GtkWidget                  *item,
137                                                                 gpointer                    user_data);
138
139 G_DEFINE_TYPE (EmpathyPresenceChooser, empathy_presence_chooser, GTK_TYPE_COMBO_BOX);
140
141 static void
142 empathy_presence_chooser_class_init (EmpathyPresenceChooserClass *klass)
143 {
144         GObjectClass *object_class = G_OBJECT_CLASS (klass);
145
146         object_class->constructed = presence_chooser_constructed;
147         object_class->finalize = presence_chooser_finalize;
148
149         g_type_class_add_private (object_class, sizeof (EmpathyPresenceChooserPriv));
150 }
151
152 static void
153 presence_chooser_create_model (EmpathyPresenceChooser *self)
154 {
155         GtkListStore *store;
156         char *custom_message;
157         int i;
158
159         store = gtk_list_store_new (N_COLUMNS,
160                                     G_TYPE_STRING,    /* COL_STATUS_TEXT */
161                                     G_TYPE_STRING,    /* COL_STATE_ICON_NAME */
162                                     G_TYPE_UINT,      /* COL_STATE */
163                                     G_TYPE_STRING,    /* COL_DISPLAY_MARKUP */
164                                     G_TYPE_BOOLEAN,   /* COL_STATUS_CUSTOMISABLE */
165                                     G_TYPE_INT);      /* COL_TYPE */
166
167         custom_message = g_strdup_printf ("<i>%s</i>", _("Custom Messageā€¦"));
168
169         for (i = 0; states[i].state != TP_CONNECTION_PRESENCE_TYPE_UNSET; i++) {
170                 GList       *list, *l;
171                 const char *status, *icon_name;
172
173                 status = empathy_presence_get_default_message (states[i].state);
174                 icon_name = empathy_icon_name_for_presence (states[i].state);
175
176                 gtk_list_store_insert_with_values (store, NULL, -1,
177                         COL_STATUS_TEXT, status,
178                         COL_STATE_ICON_NAME, icon_name,
179                         COL_STATE, states[i].state,
180                         COL_DISPLAY_MARKUP, status,
181                         COL_STATUS_CUSTOMISABLE, states[i].customisable,
182                         COL_TYPE, ENTRY_TYPE_BUILTIN,
183                         -1);
184
185                 if (states[i].customisable) {
186                         /* Set custom messages if wanted */
187                         list = empathy_status_presets_get (states[i].state, -1);
188                         list = g_list_sort (list, (GCompareFunc) g_utf8_collate);
189                         for (l = list; l; l = l->next) {
190                                 gtk_list_store_insert_with_values (store,
191                                         NULL, -1,
192                                         COL_STATUS_TEXT, l->data,
193                                         COL_STATE_ICON_NAME, icon_name,
194                                         COL_STATE, states[i].state,
195                                         COL_DISPLAY_MARKUP, l->data,
196                                         COL_STATUS_CUSTOMISABLE, TRUE,
197                                         COL_TYPE, ENTRY_TYPE_SAVED,
198                                         -1);
199                         }
200                         g_list_free (list);
201
202                         gtk_list_store_insert_with_values (store, NULL, -1,
203                                 COL_STATUS_TEXT, _("Custom Messageā€¦"),
204                                 COL_STATE_ICON_NAME, icon_name,
205                                 COL_STATE, states[i].state,
206                                 COL_DISPLAY_MARKUP, custom_message,
207                                 COL_STATUS_CUSTOMISABLE, TRUE,
208                                 COL_TYPE, ENTRY_TYPE_CUSTOM,
209                                 -1);
210                 }
211
212         }
213
214         /* add a separator */
215         gtk_list_store_insert_with_values (store, NULL, -1,
216                         COL_TYPE, ENTRY_TYPE_SEPARATOR,
217                         -1);
218
219         gtk_list_store_insert_with_values (store, NULL, -1,
220                 COL_STATUS_TEXT, _("Edit Custom Messagesā€¦"),
221                 COL_STATE_ICON_NAME, GTK_STOCK_EDIT,
222                 COL_DISPLAY_MARKUP, _("Edit Custom Messagesā€¦"),
223                 COL_TYPE, ENTRY_TYPE_EDIT_CUSTOM,
224                 -1);
225
226         g_free (custom_message);
227
228         gtk_combo_box_set_model (GTK_COMBO_BOX (self), GTK_TREE_MODEL (store));
229         g_object_unref (store);
230 }
231
232 static void
233 presence_chooser_popup_shown_cb (GObject *self,
234                                  GParamSpec *pspec,
235                                  gpointer user_data)
236 {
237         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
238         gboolean shown;
239
240         g_object_get (self, "popup-shown", &shown, NULL);
241         if (!shown) {
242                 return;
243         }
244
245         /* see presence_chooser_entry_focus_out_cb () for what this does */
246         if (priv->focus_out_idle_source != 0) {
247                 g_source_remove (priv->focus_out_idle_source);
248                 priv->focus_out_idle_source = 0;
249         }
250
251         presence_chooser_create_model (EMPATHY_PRESENCE_CHOOSER (self));
252 }
253
254 static PresenceChooserEntryType
255 presence_chooser_get_entry_type (EmpathyPresenceChooser *self)
256 {
257         GtkTreeIter iter;
258         PresenceChooserEntryType type = -1;
259
260         if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self), &iter)) {
261                 type = ENTRY_TYPE_CUSTOM;
262         }
263         else {
264                 GtkTreeModel *model;
265
266                 model = gtk_combo_box_get_model (GTK_COMBO_BOX (self));
267                 gtk_tree_model_get (model, &iter,
268                                     COL_TYPE, &type,
269                                     -1);
270         }
271
272         return type;
273 }
274
275 static TpConnectionPresenceType
276 get_state_and_status (EmpathyPresenceChooser *self,
277         gchar **status)
278 {
279         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
280         TpConnectionPresenceType state;
281         gchar *tmp;
282
283         state = tp_account_manager_get_most_available_presence (
284                 priv->account_manager, NULL, &tmp);
285         if (EMP_STR_EMPTY (tmp)) {
286                 /* no message, use the default message */
287                 g_free (tmp);
288                 tmp = g_strdup (empathy_presence_get_default_message (state));
289         }
290
291         if (status != NULL)
292                 *status = tmp;
293         else
294                 g_free (tmp);
295
296         return state;
297 }
298
299 static gboolean
300 presence_chooser_is_preset (EmpathyPresenceChooser *self)
301 {
302         TpConnectionPresenceType state;
303         char *status;
304         GList *presets, *l;
305         gboolean match = FALSE;
306
307         state = get_state_and_status (self, &status);
308
309         presets = empathy_status_presets_get (state, -1);
310         for (l = presets; l; l = l->next) {
311                 char *preset = (char *) l->data;
312
313                 if (!tp_strdiff (status, preset)) {
314                         match = TRUE;
315                         break;
316                 }
317         }
318
319         g_list_free (presets);
320
321         DEBUG ("is_preset(%i, %s) = %i", state, status, match);
322
323         g_free (status);
324         return match;
325 }
326
327 static void
328 presence_chooser_set_favorite_icon (EmpathyPresenceChooser *self)
329 {
330         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
331         GtkWidget *entry;
332         PresenceChooserEntryType type;
333
334         entry = gtk_bin_get_child (GTK_BIN (self));
335         type = presence_chooser_get_entry_type (self);
336
337         if (type == ENTRY_TYPE_CUSTOM || type == ENTRY_TYPE_SAVED) {
338                 if (presence_chooser_is_preset (self)) {
339                         /* saved entries can be removed from the list */
340                         gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
341                                            GTK_ENTRY_ICON_SECONDARY,
342                                            "emblem-favorite");
343                         gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry),
344                                          GTK_ENTRY_ICON_SECONDARY,
345                                          _("Click to remove this status as a favorite"));
346                 }
347                 else if (priv->not_favorite_pixbuf != NULL) {
348                         /* custom entries can be favorited */
349                         gtk_entry_set_icon_from_pixbuf (GTK_ENTRY (entry),
350                                            GTK_ENTRY_ICON_SECONDARY,
351                                            priv->not_favorite_pixbuf);
352                         gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry),
353                                          GTK_ENTRY_ICON_SECONDARY,
354                                          _("Click to make this status a favorite"));
355                 }
356         }
357         else {
358                 /* built-in entries cannot be favorited */
359                 gtk_entry_set_icon_from_stock (GTK_ENTRY (entry),
360                                            GTK_ENTRY_ICON_SECONDARY,
361                                            NULL);
362                 gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry),
363                                          GTK_ENTRY_ICON_SECONDARY,
364                                          NULL);
365         }
366 }
367
368 static void
369 presence_chooser_set_status_editing (EmpathyPresenceChooser *self,
370                                      gboolean editing)
371 {
372         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
373         GtkWidget *entry;
374
375         if (priv->block_set_editing) {
376                 return;
377         }
378
379         entry = gtk_bin_get_child (GTK_BIN (self));
380         if (editing) {
381                 gchar *tooltip_text;
382                 gchar *status;
383
384                 priv->editing_status = TRUE;
385
386                 get_state_and_status (self, &status);
387                 tooltip_text = g_strdup_printf ("<b>Current message: %s</b>\n"
388                     "<small><i>Press Enter to set the new message or Esc to cancel.</i></small>",
389                     status);
390                 gtk_widget_set_tooltip_markup (entry, tooltip_text);
391                 gtk_entry_set_icon_from_stock (GTK_ENTRY (entry),
392                                                GTK_ENTRY_ICON_SECONDARY,
393                                                GTK_STOCK_OK);
394                 gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry),
395                                                  GTK_ENTRY_ICON_SECONDARY,
396                                                  _("Set status"));
397                 gtk_entry_set_icon_sensitive (GTK_ENTRY (entry),
398                                               GTK_ENTRY_ICON_PRIMARY,
399                                               FALSE);
400                 g_free (status);
401                 g_free (tooltip_text);
402         } else {
403                 GtkWidget *window;
404
405                 presence_chooser_set_favorite_icon (self);
406                 gtk_entry_set_icon_sensitive (GTK_ENTRY (entry),
407                                               GTK_ENTRY_ICON_PRIMARY,
408                                               TRUE);
409
410                 /* attempt to get the toplevel for this widget */
411                 window = gtk_widget_get_toplevel (GTK_WIDGET (self));
412                 if (gtk_widget_is_toplevel (window) && GTK_IS_WINDOW (window)) {
413                         /* unset the focus */
414                         gtk_window_set_focus (GTK_WINDOW (window), NULL);
415                 }
416
417                 /* see presence_chooser_entry_focus_out_cb ()
418                  * for what this does */
419                 if (priv->focus_out_idle_source != 0) {
420                         g_source_remove (priv->focus_out_idle_source);
421                         priv->focus_out_idle_source = 0;
422                 }
423
424                 gtk_editable_set_position (GTK_EDITABLE (entry), 0);
425
426                 priv->editing_status = FALSE;
427         }
428 }
429
430 static void
431 mc_set_custom_state (EmpathyPresenceChooser *self)
432 {
433         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
434         GtkWidget *entry;
435         const char *status;
436
437         entry = gtk_bin_get_child (GTK_BIN (self));
438         /* update the status with MC */
439         status = gtk_entry_get_text (GTK_ENTRY (entry));
440
441         DEBUG ("Sending state to MC-> %d (%s)", priv->state, status);
442
443         empathy_presence_manager_set_presence (priv->presence_mgr, priv->state, status);
444 }
445
446 static void
447 ui_set_custom_state (EmpathyPresenceChooser *self,
448                      TpConnectionPresenceType state,
449                      const char *status)
450 {
451         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
452         GtkWidget *entry;
453         const char *icon_name;
454         const gchar *status_tooltip;
455
456         entry = gtk_bin_get_child (GTK_BIN (self));
457
458         priv->block_set_editing++;
459         priv->block_changed++;
460
461         icon_name = empathy_icon_name_for_presence (state);
462         gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
463                                            GTK_ENTRY_ICON_PRIMARY,
464                                            icon_name);
465         status_tooltip = status == NULL ? "" : status;
466         gtk_entry_set_text (GTK_ENTRY (entry), status_tooltip);
467         gtk_widget_set_tooltip_text (GTK_WIDGET (entry), status_tooltip);
468         presence_chooser_set_favorite_icon (self);
469
470         priv->block_changed--;
471         priv->block_set_editing--;
472 }
473
474 static void
475 presence_chooser_reset_status (EmpathyPresenceChooser *self)
476 {
477         /* recover the status that was unset */
478         presence_chooser_set_status_editing (self, FALSE);
479         presence_chooser_presence_changed_cb (self);
480 }
481
482 static void
483 presence_chooser_entry_icon_release_cb (EmpathyPresenceChooser *self,
484                                         GtkEntryIconPosition    icon_pos,
485                                         GdkEvent               *event,
486                                         GtkEntry               *entry)
487 {
488         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
489
490         if (priv->editing_status) {
491                 presence_chooser_set_status_editing (self, FALSE);
492                 mc_set_custom_state (self);
493         }
494         else {
495                 TpConnectionPresenceType state;
496                 char *status;
497
498                 state = get_state_and_status (self, &status);
499
500                 if (!empathy_status_presets_is_valid (state)) {
501                         /* It doesn't make sense to add such presence as favorite */
502                         g_free (status);
503                         return;
504                 }
505
506                 if (presence_chooser_is_preset (self)) {
507                         /* remove the entry */
508                         DEBUG ("REMOVING PRESET (%i, %s)", state, status);
509                         empathy_status_presets_remove (state, status);
510                 }
511                 else {
512                         /* save the entry */
513                         DEBUG ("SAVING PRESET (%i, %s)", state, status);
514                         empathy_status_presets_set_last (state, status);
515                 }
516
517                 /* update the icon */
518                 presence_chooser_set_favorite_icon (self);
519                 g_free (status);
520         }
521 }
522
523 static void
524 presence_chooser_entry_activate_cb (EmpathyPresenceChooser *self,
525                                     GtkEntry               *entry)
526 {
527         presence_chooser_set_status_editing (self, FALSE);
528         mc_set_custom_state (self);
529 }
530
531 static gboolean
532 presence_chooser_entry_key_press_event_cb (EmpathyPresenceChooser *self,
533                                            GdkEventKey            *event,
534                                            GtkWidget              *entry)
535 {
536         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
537
538         if (priv->editing_status && event->keyval == GDK_KEY_Escape) {
539                 /* the user pressed Escape, undo the editing */
540                 presence_chooser_reset_status (self);
541                 return TRUE;
542         }
543         else if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_Down) {
544                 /* ignore */
545                 return TRUE;
546         }
547
548         return FALSE; /* send this event elsewhere */
549 }
550
551 static gboolean
552 presence_chooser_entry_button_press_event_cb (EmpathyPresenceChooser *self,
553                                               GdkEventButton         *event,
554                                               GtkWidget              *entry)
555 {
556         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
557
558         if (!priv->editing_status &&
559             event->button == 1 &&
560             !gtk_widget_has_focus (entry)) {
561                 gtk_widget_grab_focus (entry);
562                 gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1);
563
564                 return TRUE;
565         }
566
567         return FALSE;
568 }
569
570 static void
571 presence_chooser_entry_changed_cb (EmpathyPresenceChooser *self,
572                                    GtkEntry               *entry)
573 {
574         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
575
576         if (priv->block_changed){
577                 return;
578         }
579
580         /* the combo is being edited to a custom entry */
581         if (!priv->editing_status) {
582                 presence_chooser_set_status_editing (self, TRUE);
583         }
584 }
585
586 static void
587 presence_chooser_changed_cb (GtkComboBox *self, gpointer user_data)
588 {
589         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
590         GtkTreeIter iter;
591         char *icon_name;
592         TpConnectionPresenceType new_state;
593         gboolean customisable = TRUE;
594         PresenceChooserEntryType type = -1;
595         GtkWidget *entry;
596         GtkTreeModel *model;
597
598         if (priv->block_changed ||
599             !gtk_combo_box_get_active_iter (self, &iter)) {
600                 return;
601         }
602
603         model = gtk_combo_box_get_model (self);
604         gtk_tree_model_get (model, &iter,
605                             COL_STATE_ICON_NAME, &icon_name,
606                             COL_STATE, &new_state,
607                             COL_STATUS_CUSTOMISABLE, &customisable,
608                             COL_TYPE, &type,
609                             -1);
610
611         entry = gtk_bin_get_child (GTK_BIN (self));
612
613         /* some types of status aren't editable, set the editability of the
614          * entry appropriately. Unless we're just about to reset it anyway,
615          * in which case, don't fiddle with it */
616         if (type != ENTRY_TYPE_EDIT_CUSTOM) {
617                 gtk_editable_set_editable (GTK_EDITABLE (entry), customisable);
618                 priv->state = new_state;
619         }
620
621         if (type == ENTRY_TYPE_EDIT_CUSTOM) {
622                 GtkWidget *window, *dialog;
623
624                 presence_chooser_reset_status (EMPATHY_PRESENCE_CHOOSER (self));
625
626                 /* attempt to get the toplevel for this widget */
627                 window = gtk_widget_get_toplevel (GTK_WIDGET (self));
628                 if (!gtk_widget_is_toplevel (window) || !GTK_IS_WINDOW (window)) {
629                         window = NULL;
630                 }
631
632                 dialog = empathy_status_preset_dialog_new (GTK_WINDOW (window));
633                 gtk_dialog_run (GTK_DIALOG (dialog));
634                 gtk_widget_destroy (dialog);
635         }
636         else if (type == ENTRY_TYPE_CUSTOM) {
637                 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
638                                                    GTK_ENTRY_ICON_PRIMARY,
639                                                    icon_name);
640
641                 /* preseed the status */
642                 if (priv->previous_type == ENTRY_TYPE_BUILTIN) {
643                         /* if their previous entry was a builtin, don't
644                          * preseed */
645                         gtk_entry_set_text (GTK_ENTRY (entry), "");
646                 } else {
647                         /* else preseed the text of their currently entered
648                          * status message */
649                         char *status;
650
651                         get_state_and_status (EMPATHY_PRESENCE_CHOOSER (self),
652                                 &status);
653                         gtk_entry_set_text (GTK_ENTRY (entry), status);
654                         g_free (status);
655                 }
656
657                 /* grab the focus */
658                 gtk_widget_grab_focus (entry);
659         } else {
660                 char *status;
661
662                 /* just in case we were setting a new status when
663                  * things were changed */
664                 presence_chooser_set_status_editing (
665                         EMPATHY_PRESENCE_CHOOSER (self),
666                         FALSE);
667                 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
668                                            GTK_ENTRY_ICON_PRIMARY,
669                                            icon_name);
670
671                 gtk_tree_model_get (model, &iter,
672                                     COL_STATUS_TEXT, &status,
673                                     -1);
674
675                 empathy_presence_manager_set_presence (priv->presence_mgr, priv->state, status);
676
677                 g_free (status);
678         }
679
680         if (type != ENTRY_TYPE_EDIT_CUSTOM) {
681                 priv->previous_type = type;
682         }
683         g_free (icon_name);
684 }
685
686 static gboolean
687 combo_row_separator_func (GtkTreeModel  *model,
688                           GtkTreeIter   *iter,
689                           gpointer       data)
690 {
691         PresenceChooserEntryType type;
692
693         gtk_tree_model_get (model, iter,
694                             COL_TYPE, &type,
695                             -1);
696
697         return (type == ENTRY_TYPE_SEPARATOR);
698 }
699
700 static gboolean
701 presence_chooser_entry_focus_out_idle_cb (gpointer user_data)
702 {
703         EmpathyPresenceChooser *chooser;
704         GtkWidget *entry;
705
706         DEBUG ("Autocommiting status message");
707
708         chooser = EMPATHY_PRESENCE_CHOOSER (user_data);
709         entry = gtk_bin_get_child (GTK_BIN (chooser));
710
711         presence_chooser_entry_activate_cb (chooser, GTK_ENTRY (entry));
712
713         return FALSE;
714 }
715
716 static gboolean
717 presence_chooser_entry_focus_out_cb (EmpathyPresenceChooser *chooser,
718                                      GdkEventFocus *event,
719                                      GtkEntry *entry)
720 {
721         EmpathyPresenceChooserPriv *priv = GET_PRIV (chooser);
722
723         if (priv->editing_status) {
724                 /* this seems a bit evil and maybe it will be fragile,
725                  * someone should think of a better way to do it.
726                  *
727                  * The entry has focused out, but we don't know where the focus
728                  * has gone. If it goes to the combo box, we don't want to
729                  * do anything. If it's gone anywhere else, we want to commit
730                  * the result.
731                  *
732                  * Thus we install this idle handler and store its source.
733                  * If the source is scheduled when the popup handler runs,
734                  * it will remove it, else the callback will commit the result.
735                  */
736                 priv->focus_out_idle_source = g_idle_add (
737                         presence_chooser_entry_focus_out_idle_cb,
738                         chooser);
739         }
740
741         gtk_editable_set_position (GTK_EDITABLE (entry), 0);
742
743         return FALSE;
744 }
745
746 static void
747 update_sensitivity_am_prepared_cb (GObject *source_object,
748                                    GAsyncResult *result,
749                                    gpointer user_data)
750 {
751         TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
752         EmpathyPresenceChooser *chooser = user_data;
753         EmpathyPresenceChooserPriv *priv = GET_PRIV (chooser);
754         gboolean sensitive = FALSE;
755         GList *accounts, *l;
756         GError *error = NULL;
757
758         if (!tp_proxy_prepare_finish (manager, result, &error)) {
759                 DEBUG ("Failed to prepare account manager: %s", error->message);
760                 g_error_free (error);
761                 return;
762         }
763
764         accounts = tp_account_manager_get_valid_accounts (manager);
765
766         for (l = accounts ; l != NULL ; l = g_list_next (l)) {
767                 TpAccount *a = TP_ACCOUNT (l->data);
768
769                 if (tp_account_is_enabled (a)) {
770                         sensitive = TRUE;
771                         break;
772                 }
773         }
774
775         g_list_free (accounts);
776
777         if (!empathy_connectivity_is_online (priv->connectivity))
778                 sensitive = FALSE;
779
780         gtk_widget_set_sensitive (GTK_WIDGET (chooser), sensitive);
781
782         presence_chooser_presence_changed_cb (chooser);
783 }
784
785 static void
786 presence_chooser_update_sensitivity (EmpathyPresenceChooser *chooser)
787 {
788         EmpathyPresenceChooserPriv *priv = GET_PRIV (chooser);
789
790         tp_proxy_prepare_async (priv->account_manager, NULL,
791                                           update_sensitivity_am_prepared_cb,
792                                           chooser);
793 }
794
795 static void
796 presence_chooser_account_manager_account_validity_changed_cb (
797         TpAccountManager *manager,
798         TpAccount *account,
799         gboolean valid,
800         EmpathyPresenceChooser *chooser)
801 {
802         presence_chooser_update_sensitivity (chooser);
803 }
804
805 static void
806 presence_chooser_account_manager_account_changed_cb (
807         TpAccountManager *manager,
808         TpAccount *account,
809         EmpathyPresenceChooser *chooser)
810 {
811         presence_chooser_update_sensitivity (chooser);
812 }
813
814 static void
815 presence_chooser_connectivity_state_change (EmpathyConnectivity *connectivity,
816                                             gboolean new_online,
817                                             EmpathyPresenceChooser *chooser)
818 {
819         presence_chooser_update_sensitivity (chooser);
820 }
821
822 /* Create a greyed version of the 'favorite' icon */
823 static GdkPixbuf *
824 create_not_favorite_pixbuf (void)
825 {
826         GdkPixbuf *favorite, *result;
827
828         favorite = empathy_pixbuf_from_icon_name ("emblem-favorite",
829                                                   GTK_ICON_SIZE_MENU);
830
831         if (favorite == NULL)
832                 return NULL;
833
834         result = gdk_pixbuf_copy (favorite);
835         gdk_pixbuf_saturate_and_pixelate (favorite, result, 1.0, TRUE);
836
837         g_object_unref (favorite);
838         return result;
839 }
840
841 static void
842 icon_theme_changed_cb (GtkIconTheme *icon_theme,
843                        EmpathyPresenceChooser *self)
844 {
845         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
846
847         /* Theme has changed, recreate the not-favorite icon */
848         if (priv->not_favorite_pixbuf != NULL)
849                 g_object_unref (priv->not_favorite_pixbuf);
850         priv->not_favorite_pixbuf = create_not_favorite_pixbuf ();
851
852         /* Update the icon */
853         presence_chooser_set_favorite_icon (self);
854 }
855
856 static void
857 empathy_presence_chooser_init (EmpathyPresenceChooser *chooser)
858 {
859         EmpathyPresenceChooserPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chooser,
860                 EMPATHY_TYPE_PRESENCE_CHOOSER, EmpathyPresenceChooserPriv);
861
862         chooser->priv = priv;
863
864         /* Create the not-favorite icon */
865         priv->not_favorite_pixbuf = create_not_favorite_pixbuf ();
866 }
867
868 static void
869 presence_chooser_constructed (GObject *object)
870 {
871         EmpathyPresenceChooser *chooser = EMPATHY_PRESENCE_CHOOSER (object);
872         EmpathyPresenceChooserPriv *priv = chooser->priv;
873         GtkWidget *entry;
874         GtkCellRenderer *renderer;
875         const gchar *status_tooltip;
876
877         tp_g_signal_connect_object (gtk_icon_theme_get_default (), "changed",
878                                      G_CALLBACK (icon_theme_changed_cb),
879                                      chooser, 0);
880
881         presence_chooser_create_model (chooser);
882
883         gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (chooser),
884                                              COL_STATUS_TEXT);
885         gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (chooser),
886                                               combo_row_separator_func,
887                                               NULL, NULL);
888
889         entry = gtk_bin_get_child (GTK_BIN (chooser));
890         gtk_entry_set_icon_activatable (GTK_ENTRY (entry),
891                                         GTK_ENTRY_ICON_PRIMARY,
892                                         FALSE);
893
894         g_signal_connect_swapped (entry, "icon-release",
895                 G_CALLBACK (presence_chooser_entry_icon_release_cb),
896                 chooser);
897         g_signal_connect_swapped (entry, "activate",
898                 G_CALLBACK (presence_chooser_entry_activate_cb),
899                 chooser);
900         g_signal_connect_swapped (entry, "key-press-event",
901                 G_CALLBACK (presence_chooser_entry_key_press_event_cb),
902                 chooser);
903         g_signal_connect_swapped (entry, "button-press-event",
904                 G_CALLBACK (presence_chooser_entry_button_press_event_cb),
905                 chooser);
906
907         gtk_cell_layout_clear (GTK_CELL_LAYOUT (chooser));
908
909         renderer = gtk_cell_renderer_pixbuf_new ();
910         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser), renderer, FALSE);
911         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser), renderer,
912                                         "icon-name", COL_STATE_ICON_NAME,
913                                         NULL);
914         g_object_set (renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
915
916         renderer = gtk_cell_renderer_text_new ();
917         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser), renderer, TRUE);
918         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser), renderer,
919                                         "markup", COL_DISPLAY_MARKUP,
920                                         NULL);
921         g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
922
923         g_signal_connect (chooser, "notify::popup-shown",
924                         G_CALLBACK (presence_chooser_popup_shown_cb), NULL);
925         g_signal_connect (chooser, "changed",
926                         G_CALLBACK (presence_chooser_changed_cb), NULL);
927         g_signal_connect_swapped (entry, "changed",
928                         G_CALLBACK (presence_chooser_entry_changed_cb),
929                         chooser);
930         g_signal_connect_swapped (entry, "focus-out-event",
931                         G_CALLBACK (presence_chooser_entry_focus_out_cb),
932                         chooser);
933
934         priv->presence_mgr = empathy_presence_manager_dup_singleton ();
935
936         priv->account_manager = tp_account_manager_dup ();
937         g_signal_connect_swapped (priv->account_manager,
938                 "most-available-presence-changed",
939                 G_CALLBACK (presence_chooser_presence_changed_cb),
940                 chooser);
941
942         tp_g_signal_connect_object (priv->account_manager, "account-validity-changed",
943                 G_CALLBACK (presence_chooser_account_manager_account_validity_changed_cb),
944                 chooser, 0);
945         tp_g_signal_connect_object (priv->account_manager, "account-removed",
946                 G_CALLBACK (presence_chooser_account_manager_account_changed_cb),
947                 chooser, 0);
948         tp_g_signal_connect_object (priv->account_manager, "account-enabled",
949                 G_CALLBACK (presence_chooser_account_manager_account_changed_cb),
950                 chooser, 0);
951         tp_g_signal_connect_object (priv->account_manager, "account-disabled",
952                 G_CALLBACK (presence_chooser_account_manager_account_changed_cb),
953                 chooser, 0);
954
955         status_tooltip = gtk_entry_get_text (GTK_ENTRY (entry));
956         gtk_widget_set_tooltip_text (GTK_WIDGET (chooser), status_tooltip);
957
958         priv->connectivity = empathy_connectivity_dup_singleton ();
959         tp_g_signal_connect_object (priv->connectivity,
960                 "state-change",
961                 G_CALLBACK (presence_chooser_connectivity_state_change),
962                 chooser, 0);
963
964         presence_chooser_update_sensitivity (chooser);
965 }
966
967 static void
968 presence_chooser_finalize (GObject *object)
969 {
970         EmpathyPresenceChooserPriv *priv;
971
972         priv = GET_PRIV (object);
973
974         if (priv->focus_out_idle_source) {
975                 g_source_remove (priv->focus_out_idle_source);
976         }
977
978         if (priv->account_manager != NULL)
979                 g_object_unref (priv->account_manager);
980
981         g_signal_handlers_disconnect_by_func (priv->presence_mgr,
982                                               presence_chooser_presence_changed_cb,
983                                               object);
984         g_object_unref (priv->presence_mgr);
985
986         g_object_unref (priv->connectivity);
987         if (priv->not_favorite_pixbuf != NULL)
988                 g_object_unref (priv->not_favorite_pixbuf);
989
990         G_OBJECT_CLASS (empathy_presence_chooser_parent_class)->finalize (object);
991 }
992
993 /**
994  * empathy_presence_chooser_new:
995  *
996  * Creates a new #EmpathyPresenceChooser widget.
997  *
998  * Return value: A new #EmpathyPresenceChooser widget
999  */
1000 GtkWidget *
1001 empathy_presence_chooser_new (void)
1002 {
1003         /* FIXME, why can't this go in init ()? */
1004         return g_object_new (EMPATHY_TYPE_PRESENCE_CHOOSER,
1005                 "has-entry", TRUE,
1006                 NULL);
1007 }
1008
1009 static void
1010 presence_chooser_presence_changed_cb (EmpathyPresenceChooser *chooser)
1011 {
1012         EmpathyPresenceChooserPriv *priv;
1013         TpConnectionPresenceType    state;
1014         gchar                      *status;
1015         GtkTreeModel               *model;
1016         GtkTreeIter                 iter;
1017         gboolean valid, match_state = FALSE, match = FALSE;
1018         GtkWidget                  *entry;
1019
1020         priv = GET_PRIV (chooser);
1021
1022         if (priv->editing_status) {
1023                 return;
1024         }
1025
1026         state = get_state_and_status (chooser, &status);
1027         priv->state = state;
1028
1029         /* An unset presence here doesn't make any sense. Force it to appear as
1030          * offline. */
1031         if (state == TP_CONNECTION_PRESENCE_TYPE_UNSET) {
1032                 state = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
1033         }
1034
1035         /* look through the model and attempt to find a matching state */
1036         model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser));
1037         for (valid = gtk_tree_model_get_iter_first (model, &iter);
1038              valid;
1039              valid = gtk_tree_model_iter_next (model, &iter)) {
1040                 int m_type;
1041                 TpConnectionPresenceType m_state;
1042                 char *m_status;
1043
1044                 gtk_tree_model_get (model, &iter,
1045                                 COL_STATE, &m_state,
1046                                 COL_TYPE, &m_type,
1047                                 -1);
1048
1049                 if (m_type == ENTRY_TYPE_CUSTOM ||
1050                     m_type == ENTRY_TYPE_SEPARATOR ||
1051                     m_type == ENTRY_TYPE_EDIT_CUSTOM) {
1052                         continue;
1053                 }
1054                 else if (!match_state && state == m_state) {
1055                         /* we are now in the section that can contain our
1056                          * match */
1057                         match_state = TRUE;
1058                 }
1059                 else if (match_state && state != m_state) {
1060                         /* we have passed the section that can contain our
1061                          * match */
1062                         break;
1063                 }
1064
1065                 gtk_tree_model_get (model, &iter,
1066                                 COL_STATUS_TEXT, &m_status,
1067                                 -1);
1068
1069                 match = !tp_strdiff (status, m_status);
1070
1071                 g_free (m_status);
1072
1073                 if (match) break;
1074
1075         }
1076
1077         if (match) {
1078                 priv->block_changed++;
1079                 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser), &iter);
1080                 presence_chooser_set_favorite_icon (chooser);
1081                 priv->block_changed--;
1082         }
1083         else {
1084                 ui_set_custom_state (chooser, state, status);
1085         }
1086
1087         entry = gtk_bin_get_child (GTK_BIN (chooser));
1088         gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
1089               GTK_ENTRY_ICON_PRIMARY,
1090               empathy_icon_name_for_presence (state));
1091         gtk_widget_set_tooltip_text (GTK_WIDGET (entry), status);
1092
1093         entry = gtk_bin_get_child (GTK_BIN (chooser));
1094         gtk_editable_set_editable (GTK_EDITABLE (entry),
1095             state != TP_CONNECTION_PRESENCE_TYPE_OFFLINE);
1096
1097         g_free (status);
1098 }
1099
1100 /**
1101  * empathy_presence_chooser_create_menu:
1102  *
1103  * Creates a new #GtkMenu allowing users to change their presence from a menu.
1104  *
1105  * Return value: a new #GtkMenu for changing presence in a menu.
1106  */
1107 GtkWidget *
1108 empathy_presence_chooser_create_menu (void)
1109 {
1110         const gchar *status;
1111         GtkWidget   *menu;
1112         GtkWidget   *item;
1113         GtkWidget   *image;
1114         guint        i;
1115
1116         menu = gtk_menu_new ();
1117
1118         for (i = 0; states[i].state != TP_CONNECTION_PRESENCE_TYPE_UNSET; i++) {
1119                 GList       *list, *l;
1120
1121                 status = empathy_presence_get_default_message (states[i].state);
1122                 presence_chooser_menu_add_item (menu,
1123                                                 status,
1124                                                 states[i].state);
1125
1126                 if (states[i].customisable) {
1127                         /* Set custom messages if wanted */
1128                         list = empathy_status_presets_get (states[i].state, 5);
1129                         for (l = list; l; l = l->next) {
1130                                 presence_chooser_menu_add_item (menu,
1131                                                                 l->data,
1132                                                                 states[i].state);
1133                         }
1134                         g_list_free (list);
1135                 }
1136
1137         }
1138
1139         /* Separator */
1140         item = gtk_menu_item_new ();
1141         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1142         gtk_widget_show (item);
1143
1144         /* Custom messages */
1145         item = gtk_image_menu_item_new_with_label (_("Custom messagesā€¦"));
1146         image = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
1147         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1148         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1149         gtk_widget_show (image);
1150         gtk_widget_show (item);
1151
1152         g_signal_connect (item,
1153                           "activate",
1154                           G_CALLBACK (presence_chooser_custom_activate_cb),
1155                           NULL);
1156
1157         return menu;
1158 }
1159
1160 static void
1161 presence_chooser_menu_add_item (GtkWidget   *menu,
1162                                 const gchar *str,
1163                                 TpConnectionPresenceType state)
1164 {
1165         GtkWidget   *item;
1166         GtkWidget   *image;
1167         const gchar *icon_name;
1168
1169         item = gtk_image_menu_item_new_with_label (str);
1170         icon_name = empathy_icon_name_for_presence (state);
1171
1172         g_signal_connect (item, "activate",
1173                           G_CALLBACK (presence_chooser_noncustom_activate_cb),
1174                           NULL);
1175
1176         image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
1177         gtk_widget_show (image);
1178
1179         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1180         gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (item), TRUE);
1181         gtk_widget_show (item);
1182
1183         g_object_set_data_full (G_OBJECT (item),
1184                                 "status", g_strdup (str),
1185                                 (GDestroyNotify) g_free);
1186
1187         g_object_set_data (G_OBJECT (item), "state", GINT_TO_POINTER (state));
1188
1189         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1190 }
1191
1192 static void
1193 presence_chooser_noncustom_activate_cb (GtkWidget *item,
1194                                         gpointer   user_data)
1195 {
1196         TpConnectionPresenceType state;
1197         const gchar *status;
1198
1199         status = g_object_get_data (G_OBJECT (item), "status");
1200         state = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "state"));
1201
1202         presence_chooser_set_state (state, status);
1203 }
1204
1205 static void
1206 presence_chooser_set_state (TpConnectionPresenceType state,
1207                             const gchar *status)
1208 {
1209         EmpathyPresenceManager *presence_mgr;
1210
1211         presence_mgr = empathy_presence_manager_dup_singleton ();
1212         empathy_presence_manager_set_presence (presence_mgr, state, status);
1213         g_object_unref (presence_mgr);
1214 }
1215
1216 static void
1217 presence_chooser_custom_activate_cb (GtkWidget *item,
1218                                      gpointer   user_data)
1219 {
1220         GtkWidget *dialog;
1221
1222         dialog = empathy_status_preset_dialog_new (NULL);
1223         gtk_dialog_run (GTK_DIALOG (dialog));
1224         gtk_widget_destroy (dialog);
1225 }