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