]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-presence-chooser.c
Merge branch 'gnome-3-4'
[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-presence-manager.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 /**
52  * SECTION:empathy-presence-chooser
53  * @title:EmpathyPresenceChooser
54  * @short_description: A widget used to change presence
55  * @include: libempathy-gtk/empathy-presence-chooser.h
56  *
57  * #EmpathyPresenceChooser is a widget which extends #GtkComboBoxEntry
58  * to change presence.
59  */
60
61 /**
62  * EmpathyAccountChooser:
63  * @parent: parent object
64  *
65  * Widget which extends #GtkComboBoxEntry to change presence.
66  */
67
68 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyPresenceChooser)
69
70 /* For custom message dialog */
71 enum {
72         COL_ICON,
73         COL_LABEL,
74         COL_PRESENCE,
75         COL_COUNT
76 };
77
78 /* For combobox's model */
79 enum {
80         COL_STATUS_TEXT,
81         COL_STATE_ICON_NAME,
82         COL_STATE,
83         COL_DISPLAY_MARKUP,
84         COL_STATUS_CUSTOMISABLE,
85         COL_TYPE,
86         N_COLUMNS
87 };
88
89 typedef enum  {
90         ENTRY_TYPE_BUILTIN,
91         ENTRY_TYPE_SAVED,
92         ENTRY_TYPE_CUSTOM,
93         ENTRY_TYPE_SEPARATOR,
94         ENTRY_TYPE_EDIT_CUSTOM,
95 } PresenceChooserEntryType;
96
97 typedef struct {
98         EmpathyPresenceManager *presence_mgr;
99         GNetworkMonitor *connectivity;
100
101         gboolean     editing_status;
102         int          block_set_editing;
103         int          block_changed;
104         guint        focus_out_idle_source;
105
106         TpConnectionPresenceType state;
107         PresenceChooserEntryType previous_type;
108
109         TpAccountManager *account_manager;
110         GdkPixbuf *not_favorite_pixbuf;
111 } EmpathyPresenceChooserPriv;
112
113 /* States to be listed in the menu.
114  * Each state has a boolean telling if it can have custom message */
115 static struct { TpConnectionPresenceType state;
116          gboolean customisable;
117 } states[] = { { TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE } ,
118                          { TP_CONNECTION_PRESENCE_TYPE_BUSY, TRUE },
119                          { TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE },
120                          { TP_CONNECTION_PRESENCE_TYPE_HIDDEN, FALSE },
121                          { TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE},
122                          { TP_CONNECTION_PRESENCE_TYPE_UNSET, },
123                         };
124
125 static void            presence_chooser_constructed            (GObject                    *object);
126 static void            presence_chooser_finalize               (GObject                    *object);
127 static void            presence_chooser_presence_changed_cb    (EmpathyPresenceChooser      *chooser);
128 static void            presence_chooser_menu_add_item          (GtkWidget                  *menu,
129                                                                 const gchar                *str,
130                                                                 TpConnectionPresenceType                  state);
131 static void            presence_chooser_noncustom_activate_cb  (GtkWidget                  *item,
132                                                                 gpointer                    user_data);
133 static void            presence_chooser_set_state              (TpConnectionPresenceType                  state,
134                                                                 const gchar                *status);
135 static void            presence_chooser_custom_activate_cb     (GtkWidget                  *item,
136                                                                 gpointer                    user_data);
137
138 G_DEFINE_TYPE (EmpathyPresenceChooser, empathy_presence_chooser, GTK_TYPE_COMBO_BOX);
139
140 static void
141 empathy_presence_chooser_class_init (EmpathyPresenceChooserClass *klass)
142 {
143         GObjectClass *object_class = G_OBJECT_CLASS (klass);
144
145         object_class->constructed = presence_chooser_constructed;
146         object_class->finalize = presence_chooser_finalize;
147
148         g_type_class_add_private (object_class, sizeof (EmpathyPresenceChooserPriv));
149 }
150
151 static void
152 presence_chooser_create_model (EmpathyPresenceChooser *self)
153 {
154         GtkListStore *store;
155         char *custom_message;
156         int i;
157
158         store = gtk_list_store_new (N_COLUMNS,
159                                     G_TYPE_STRING,    /* COL_STATUS_TEXT */
160                                     G_TYPE_STRING,    /* COL_STATE_ICON_NAME */
161                                     G_TYPE_UINT,      /* COL_STATE */
162                                     G_TYPE_STRING,    /* COL_DISPLAY_MARKUP */
163                                     G_TYPE_BOOLEAN,   /* COL_STATUS_CUSTOMISABLE */
164                                     G_TYPE_INT);      /* COL_TYPE */
165
166         custom_message = g_strdup_printf ("<i>%s</i>", _("Custom Message…"));
167
168         for (i = 0; states[i].state != TP_CONNECTION_PRESENCE_TYPE_UNSET; i++) {
169                 GList       *list, *l;
170                 const char *status, *icon_name;
171
172                 status = empathy_presence_get_default_message (states[i].state);
173                 icon_name = empathy_icon_name_for_presence (states[i].state);
174
175                 gtk_list_store_insert_with_values (store, NULL, -1,
176                         COL_STATUS_TEXT, status,
177                         COL_STATE_ICON_NAME, icon_name,
178                         COL_STATE, states[i].state,
179                         COL_DISPLAY_MARKUP, status,
180                         COL_STATUS_CUSTOMISABLE, states[i].customisable,
181                         COL_TYPE, ENTRY_TYPE_BUILTIN,
182                         -1);
183
184                 if (states[i].customisable) {
185                         /* Set custom messages if wanted */
186                         list = empathy_status_presets_get (states[i].state, -1);
187                         list = g_list_sort (list, (GCompareFunc) g_utf8_collate);
188                         for (l = list; l; l = l->next) {
189                                 gtk_list_store_insert_with_values (store,
190                                         NULL, -1,
191                                         COL_STATUS_TEXT, l->data,
192                                         COL_STATE_ICON_NAME, icon_name,
193                                         COL_STATE, states[i].state,
194                                         COL_DISPLAY_MARKUP, l->data,
195                                         COL_STATUS_CUSTOMISABLE, TRUE,
196                                         COL_TYPE, ENTRY_TYPE_SAVED,
197                                         -1);
198                         }
199                         g_list_free (list);
200
201                         gtk_list_store_insert_with_values (store, NULL, -1,
202                                 COL_STATUS_TEXT, _("Custom Message…"),
203                                 COL_STATE_ICON_NAME, icon_name,
204                                 COL_STATE, states[i].state,
205                                 COL_DISPLAY_MARKUP, custom_message,
206                                 COL_STATUS_CUSTOMISABLE, TRUE,
207                                 COL_TYPE, ENTRY_TYPE_CUSTOM,
208                                 -1);
209                 }
210
211         }
212
213         /* add a separator */
214         gtk_list_store_insert_with_values (store, NULL, -1,
215                         COL_TYPE, ENTRY_TYPE_SEPARATOR,
216                         -1);
217
218         gtk_list_store_insert_with_values (store, NULL, -1,
219                 COL_STATUS_TEXT, _("Edit Custom Messages…"),
220                 COL_STATE_ICON_NAME, GTK_STOCK_EDIT,
221                 COL_DISPLAY_MARKUP, _("Edit Custom Messages…"),
222                 COL_TYPE, ENTRY_TYPE_EDIT_CUSTOM,
223                 -1);
224
225         g_free (custom_message);
226
227         gtk_combo_box_set_model (GTK_COMBO_BOX (self), GTK_TREE_MODEL (store));
228         g_object_unref (store);
229 }
230
231 static void
232 presence_chooser_popup_shown_cb (GObject *self,
233                                  GParamSpec *pspec,
234                                  gpointer user_data)
235 {
236         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
237         gboolean shown;
238
239         g_object_get (self, "popup-shown", &shown, NULL);
240         if (!shown) {
241                 return;
242         }
243
244         /* see presence_chooser_entry_focus_out_cb () for what this does */
245         if (priv->focus_out_idle_source != 0) {
246                 g_source_remove (priv->focus_out_idle_source);
247                 priv->focus_out_idle_source = 0;
248         }
249
250         presence_chooser_create_model (EMPATHY_PRESENCE_CHOOSER (self));
251 }
252
253 static PresenceChooserEntryType
254 presence_chooser_get_entry_type (EmpathyPresenceChooser *self)
255 {
256         GtkTreeIter iter;
257         PresenceChooserEntryType type = -1;
258
259         if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self), &iter)) {
260                 type = ENTRY_TYPE_CUSTOM;
261         }
262         else {
263                 GtkTreeModel *model;
264
265                 model = gtk_combo_box_get_model (GTK_COMBO_BOX (self));
266                 gtk_tree_model_get (model, &iter,
267                                     COL_TYPE, &type,
268                                     -1);
269         }
270
271         return type;
272 }
273
274 static TpConnectionPresenceType
275 get_state_and_status (EmpathyPresenceChooser *self,
276         gchar **status)
277 {
278         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
279         TpConnectionPresenceType state;
280         gchar *tmp;
281
282         state = tp_account_manager_get_most_available_presence (
283                 priv->account_manager, NULL, &tmp);
284         if (EMP_STR_EMPTY (tmp)) {
285                 /* no message, use the default message */
286                 g_free (tmp);
287                 tmp = g_strdup (empathy_presence_get_default_message (state));
288         }
289
290         if (status != NULL)
291                 *status = tmp;
292         else
293                 g_free (tmp);
294
295         return state;
296 }
297
298 static gboolean
299 presence_chooser_is_preset (EmpathyPresenceChooser *self)
300 {
301         TpConnectionPresenceType state;
302         char *status;
303         GList *presets, *l;
304         gboolean match = FALSE;
305
306         state = get_state_and_status (self, &status);
307
308         presets = empathy_status_presets_get (state, -1);
309         for (l = presets; l; l = l->next) {
310                 char *preset = (char *) l->data;
311
312                 if (!tp_strdiff (status, preset)) {
313                         match = TRUE;
314                         break;
315                 }
316         }
317
318         g_list_free (presets);
319
320         DEBUG ("is_preset(%i, %s) = %i", state, status, match);
321
322         g_free (status);
323         return match;
324 }
325
326 static void
327 presence_chooser_set_favorite_icon (EmpathyPresenceChooser *self)
328 {
329         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
330         GtkWidget *entry;
331         PresenceChooserEntryType type;
332
333         entry = gtk_bin_get_child (GTK_BIN (self));
334         type = presence_chooser_get_entry_type (self);
335
336         if (type == ENTRY_TYPE_CUSTOM || type == ENTRY_TYPE_SAVED) {
337                 if (presence_chooser_is_preset (self)) {
338                         /* saved entries can be removed from the list */
339                         gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
340                                            GTK_ENTRY_ICON_SECONDARY,
341                                            "emblem-favorite");
342                         gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry),
343                                          GTK_ENTRY_ICON_SECONDARY,
344                                          _("Click to remove this status as a favorite"));
345                 }
346                 else if (priv->not_favorite_pixbuf != NULL) {
347                         /* custom entries can be favorited */
348                         gtk_entry_set_icon_from_pixbuf (GTK_ENTRY (entry),
349                                            GTK_ENTRY_ICON_SECONDARY,
350                                            priv->not_favorite_pixbuf);
351                         gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry),
352                                          GTK_ENTRY_ICON_SECONDARY,
353                                          _("Click to make this status a favorite"));
354                 }
355         }
356         else {
357                 /* built-in entries cannot be favorited */
358                 gtk_entry_set_icon_from_stock (GTK_ENTRY (entry),
359                                            GTK_ENTRY_ICON_SECONDARY,
360                                            NULL);
361                 gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry),
362                                          GTK_ENTRY_ICON_SECONDARY,
363                                          NULL);
364         }
365 }
366
367 static void
368 presence_chooser_set_status_editing (EmpathyPresenceChooser *self,
369                                      gboolean editing)
370 {
371         EmpathyPresenceChooserPriv *priv = GET_PRIV (self);
372         GtkWidget *entry;
373
374         if (priv->block_set_editing) {
375                 return;
376         }
377
378         entry = gtk_bin_get_child (GTK_BIN (self));
379         if (editing) {
380                 gchar *tooltip_text;
381                 gchar *status;
382
383                 priv->editing_status = TRUE;
384
385                 get_state_and_status (self, &status);
386                 /* Translators: %s is a status message like 'At the pub' for example */
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 (!g_network_monitor_get_network_available (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_network_change (GNetworkMonitor *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 = g_network_monitor_get_default ();
959         g_object_ref (priv->connectivity);
960
961         tp_g_signal_connect_object (priv->connectivity,
962                 "network-changed",
963                 G_CALLBACK (presence_chooser_network_change),
964                 chooser, 0);
965
966         presence_chooser_update_sensitivity (chooser);
967 }
968
969 static void
970 presence_chooser_finalize (GObject *object)
971 {
972         EmpathyPresenceChooserPriv *priv;
973
974         priv = GET_PRIV (object);
975
976         if (priv->focus_out_idle_source) {
977                 g_source_remove (priv->focus_out_idle_source);
978         }
979
980         if (priv->account_manager != NULL)
981                 g_object_unref (priv->account_manager);
982
983         g_signal_handlers_disconnect_by_func (priv->presence_mgr,
984                                               presence_chooser_presence_changed_cb,
985                                               object);
986         g_object_unref (priv->presence_mgr);
987
988         g_object_unref (priv->connectivity);
989         if (priv->not_favorite_pixbuf != NULL)
990                 g_object_unref (priv->not_favorite_pixbuf);
991
992         G_OBJECT_CLASS (empathy_presence_chooser_parent_class)->finalize (object);
993 }
994
995 /**
996  * empathy_presence_chooser_new:
997  *
998  * Creates a new #EmpathyPresenceChooser widget.
999  *
1000  * Return value: A new #EmpathyPresenceChooser widget
1001  */
1002 GtkWidget *
1003 empathy_presence_chooser_new (void)
1004 {
1005         /* FIXME, why can't this go in init ()? */
1006         return g_object_new (EMPATHY_TYPE_PRESENCE_CHOOSER,
1007                 "has-entry", TRUE,
1008                 NULL);
1009 }
1010
1011 static void
1012 presence_chooser_presence_changed_cb (EmpathyPresenceChooser *chooser)
1013 {
1014         EmpathyPresenceChooserPriv *priv;
1015         TpConnectionPresenceType    state;
1016         gchar                      *status;
1017         GtkTreeModel               *model;
1018         GtkTreeIter                 iter;
1019         gboolean valid, match_state = FALSE, match = FALSE;
1020         GtkWidget                  *entry;
1021
1022         priv = GET_PRIV (chooser);
1023
1024         if (priv->editing_status) {
1025                 return;
1026         }
1027
1028         state = get_state_and_status (chooser, &status);
1029         priv->state = state;
1030
1031         /* An unset presence here doesn't make any sense. Force it to appear as
1032          * offline. */
1033         if (state == TP_CONNECTION_PRESENCE_TYPE_UNSET) {
1034                 state = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
1035         }
1036
1037         /* look through the model and attempt to find a matching state */
1038         model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser));
1039         for (valid = gtk_tree_model_get_iter_first (model, &iter);
1040              valid;
1041              valid = gtk_tree_model_iter_next (model, &iter)) {
1042                 int m_type;
1043                 TpConnectionPresenceType m_state;
1044                 char *m_status;
1045
1046                 gtk_tree_model_get (model, &iter,
1047                                 COL_STATE, &m_state,
1048                                 COL_TYPE, &m_type,
1049                                 -1);
1050
1051                 if (m_type == ENTRY_TYPE_CUSTOM ||
1052                     m_type == ENTRY_TYPE_SEPARATOR ||
1053                     m_type == ENTRY_TYPE_EDIT_CUSTOM) {
1054                         continue;
1055                 }
1056                 else if (!match_state && state == m_state) {
1057                         /* we are now in the section that can contain our
1058                          * match */
1059                         match_state = TRUE;
1060                 }
1061                 else if (match_state && state != m_state) {
1062                         /* we have passed the section that can contain our
1063                          * match */
1064                         break;
1065                 }
1066
1067                 gtk_tree_model_get (model, &iter,
1068                                 COL_STATUS_TEXT, &m_status,
1069                                 -1);
1070
1071                 match = !tp_strdiff (status, m_status);
1072
1073                 g_free (m_status);
1074
1075                 if (match) break;
1076
1077         }
1078
1079         if (match) {
1080                 priv->block_changed++;
1081                 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser), &iter);
1082                 presence_chooser_set_favorite_icon (chooser);
1083                 priv->block_changed--;
1084         }
1085         else {
1086                 ui_set_custom_state (chooser, state, status);
1087         }
1088
1089         entry = gtk_bin_get_child (GTK_BIN (chooser));
1090         gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
1091               GTK_ENTRY_ICON_PRIMARY,
1092               empathy_icon_name_for_presence (state));
1093         gtk_widget_set_tooltip_text (GTK_WIDGET (entry), status);
1094
1095         entry = gtk_bin_get_child (GTK_BIN (chooser));
1096         gtk_editable_set_editable (GTK_EDITABLE (entry),
1097             state != TP_CONNECTION_PRESENCE_TYPE_OFFLINE);
1098
1099         g_free (status);
1100 }
1101
1102 /**
1103  * empathy_presence_chooser_create_menu:
1104  *
1105  * Creates a new #GtkMenu allowing users to change their presence from a menu.
1106  *
1107  * Return value: a new #GtkMenu for changing presence in a menu.
1108  */
1109 GtkWidget *
1110 empathy_presence_chooser_create_menu (void)
1111 {
1112         const gchar *status;
1113         GtkWidget   *menu;
1114         GtkWidget   *item;
1115         GtkWidget   *image;
1116         guint        i;
1117
1118         menu = gtk_menu_new ();
1119
1120         for (i = 0; states[i].state != TP_CONNECTION_PRESENCE_TYPE_UNSET; i++) {
1121                 GList       *list, *l;
1122
1123                 status = empathy_presence_get_default_message (states[i].state);
1124                 presence_chooser_menu_add_item (menu,
1125                                                 status,
1126                                                 states[i].state);
1127
1128                 if (states[i].customisable) {
1129                         /* Set custom messages if wanted */
1130                         list = empathy_status_presets_get (states[i].state, 5);
1131                         for (l = list; l; l = l->next) {
1132                                 presence_chooser_menu_add_item (menu,
1133                                                                 l->data,
1134                                                                 states[i].state);
1135                         }
1136                         g_list_free (list);
1137                 }
1138
1139         }
1140
1141         /* Separator */
1142         item = gtk_menu_item_new ();
1143         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1144         gtk_widget_show (item);
1145
1146         /* Custom messages */
1147         item = gtk_image_menu_item_new_with_label (_("Custom messages…"));
1148         image = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
1149         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1150         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1151         gtk_widget_show (image);
1152         gtk_widget_show (item);
1153
1154         g_signal_connect (item,
1155                           "activate",
1156                           G_CALLBACK (presence_chooser_custom_activate_cb),
1157                           NULL);
1158
1159         return menu;
1160 }
1161
1162 static void
1163 presence_chooser_menu_add_item (GtkWidget   *menu,
1164                                 const gchar *str,
1165                                 TpConnectionPresenceType state)
1166 {
1167         GtkWidget   *item;
1168         GtkWidget   *image;
1169         const gchar *icon_name;
1170
1171         item = gtk_image_menu_item_new_with_label (str);
1172         icon_name = empathy_icon_name_for_presence (state);
1173
1174         g_signal_connect (item, "activate",
1175                           G_CALLBACK (presence_chooser_noncustom_activate_cb),
1176                           NULL);
1177
1178         image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
1179         gtk_widget_show (image);
1180
1181         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1182         gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (item), TRUE);
1183         gtk_widget_show (item);
1184
1185         g_object_set_data_full (G_OBJECT (item),
1186                                 "status", g_strdup (str),
1187                                 (GDestroyNotify) g_free);
1188
1189         g_object_set_data (G_OBJECT (item), "state", GINT_TO_POINTER (state));
1190
1191         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1192 }
1193
1194 static void
1195 presence_chooser_noncustom_activate_cb (GtkWidget *item,
1196                                         gpointer   user_data)
1197 {
1198         TpConnectionPresenceType state;
1199         const gchar *status;
1200
1201         status = g_object_get_data (G_OBJECT (item), "status");
1202         state = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "state"));
1203
1204         presence_chooser_set_state (state, status);
1205 }
1206
1207 static void
1208 presence_chooser_set_state (TpConnectionPresenceType state,
1209                             const gchar *status)
1210 {
1211         EmpathyPresenceManager *presence_mgr;
1212
1213         presence_mgr = empathy_presence_manager_dup_singleton ();
1214         empathy_presence_manager_set_presence (presence_mgr, state, status);
1215         g_object_unref (presence_mgr);
1216 }
1217
1218 static void
1219 presence_chooser_custom_activate_cb (GtkWidget *item,
1220                                      gpointer   user_data)
1221 {
1222         GtkWidget *dialog;
1223
1224         dialog = empathy_status_preset_dialog_new (NULL);
1225         gtk_dialog_run (GTK_DIALOG (dialog));
1226         gtk_widget_destroy (dialog);
1227 }