mark PurpleImageClass as private
[pidgin-git.git] / pidgin / gtkstatusbox.c
blobce1834a899498f0d10345e45191378239614288f
1 /* pidgin
3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 * The status box is made up of two main pieces:
24 * - The box that displays the current status, which is made
25 * of a GtkListStore ("status_box->store") and GtkCellView
26 * ("status_box->cell_view"). There is always exactly 1 row
27 * in this list store. Only the TYPE_ICON and TYPE_TEXT
28 * columns are used in this list store.
29 * - The dropdown menu that lets users select a status, which
30 * is made of a GtkComboBox ("status_box") and GtkListStore
31 * ("status_box->dropdown_store"). This dropdown is shown
32 * when the user clicks on the box that displays the current
33 * status. This list store contains one row for Available,
34 * one row for Away, etc., a few rows for popular statuses,
35 * and the "New..." and "Saved..." options.
38 #include <gdk/gdkkeysyms.h>
40 #include "internal.h"
42 #include "account.h"
43 #include "buddyicon.h"
44 #include "core.h"
45 #include "network.h"
46 #include "request.h"
47 #include "savedstatuses.h"
48 #include "status.h"
49 #include "debug.h"
51 #include "pidgin.h"
52 #include "gtksavedstatuses.h"
53 #include "pidginstock.h"
54 #include "gtkstatusbox.h"
55 #include "gtkutils.h"
56 #include "pidgingdkpixbuf.h"
58 #include "gtk3compat.h"
60 /* Timeout for typing notifications in seconds */
61 #define TYPING_TIMEOUT 4
63 static void remove_typing_cb(PidginStatusBox *box);
64 static void update_size (PidginStatusBox *box);
65 static gint get_statusbox_index(PidginStatusBox *box, PurpleSavedStatus *saved_status);
66 static PurpleAccount* check_active_accounts_for_identical_statuses(void);
68 static void pidgin_status_box_pulse_typing(PidginStatusBox *status_box);
69 static void pidgin_status_box_refresh(PidginStatusBox *status_box);
70 static void status_menu_refresh_iter(PidginStatusBox *status_box, gboolean status_changed);
71 static void pidgin_status_box_regenerate(PidginStatusBox *status_box, gboolean status_changed);
72 static void pidgin_status_box_changed(PidginStatusBox *box);
73 static void pidgin_status_box_get_preferred_height (GtkWidget *widget,
74 gint *minimum_height, gint *natural_height);
75 static gboolean pidgin_status_box_draw (GtkWidget *widget, cairo_t *cr);
76 static void pidgin_status_box_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
77 static void pidgin_status_box_redisplay_buddy_icon(PidginStatusBox *status_box);
78 static void pidgin_status_box_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data);
79 static void pidgin_status_box_popup(PidginStatusBox *box, GdkEvent *event);
80 static void pidgin_status_box_popdown(PidginStatusBox *box, GdkEvent *event);
82 static void do_colorshift (GdkPixbuf *dest, GdkPixbuf *src, int shift);
83 static void icon_choose_cb(const char *filename, gpointer data);
84 static void remove_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box);
85 static void choose_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box);
87 enum {
88 /* A PidginStatusBoxItemType */
89 TYPE_COLUMN,
91 /* This is the stock-id for the icon. */
92 ICON_STOCK_COLUMN,
95 * This is a GdkPixbuf (the other columns are strings).
96 * This column is visible.
98 ICON_COLUMN,
100 /* The text displayed on the status box. This column is visible. */
101 TEXT_COLUMN,
103 /* The plain-English title of this item */
104 TITLE_COLUMN,
106 /* A plain-English description of this item */
107 DESC_COLUMN,
110 * This value depends on TYPE_COLUMN. For POPULAR types,
111 * this is the creation time. For PRIMITIVE types,
112 * this is the PurpleStatusPrimitive.
114 DATA_COLUMN,
117 * This column stores the GdkPixbuf for the status emblem. Currently only 'saved' is stored.
118 * In the GtkTreeModel for the dropdown, this is the stock-id (gchararray), and for the
119 * GtkTreeModel for the cell_view (for the account-specific statusbox), this is the protocol icon
120 * (GdkPixbuf) of the account.
122 EMBLEM_COLUMN,
125 * This column stores whether to show the emblem.
127 EMBLEM_VISIBLE_COLUMN,
129 NUM_COLUMNS
132 enum {
133 PROP_0,
134 PROP_ACCOUNT,
135 PROP_ICON_SEL,
138 static char *typing_stock_ids[7] = {
139 PIDGIN_STOCK_ANIMATION_TYPING0,
140 PIDGIN_STOCK_ANIMATION_TYPING1,
141 PIDGIN_STOCK_ANIMATION_TYPING2,
142 PIDGIN_STOCK_ANIMATION_TYPING3,
143 PIDGIN_STOCK_ANIMATION_TYPING4,
144 NULL
147 static char *connecting_stock_ids[] = {
148 PIDGIN_STOCK_ANIMATION_CONNECT0,
149 PIDGIN_STOCK_ANIMATION_CONNECT1,
150 PIDGIN_STOCK_ANIMATION_CONNECT2,
151 PIDGIN_STOCK_ANIMATION_CONNECT3,
152 PIDGIN_STOCK_ANIMATION_CONNECT4,
153 PIDGIN_STOCK_ANIMATION_CONNECT5,
154 PIDGIN_STOCK_ANIMATION_CONNECT6,
155 PIDGIN_STOCK_ANIMATION_CONNECT7,
156 PIDGIN_STOCK_ANIMATION_CONNECT8,
157 PIDGIN_STOCK_ANIMATION_CONNECT9,
158 PIDGIN_STOCK_ANIMATION_CONNECT10,
159 PIDGIN_STOCK_ANIMATION_CONNECT11,
160 PIDGIN_STOCK_ANIMATION_CONNECT12,
161 PIDGIN_STOCK_ANIMATION_CONNECT13,
162 PIDGIN_STOCK_ANIMATION_CONNECT14,
163 PIDGIN_STOCK_ANIMATION_CONNECT15,
164 PIDGIN_STOCK_ANIMATION_CONNECT16,
165 PIDGIN_STOCK_ANIMATION_CONNECT17,
166 PIDGIN_STOCK_ANIMATION_CONNECT18,
167 PIDGIN_STOCK_ANIMATION_CONNECT19,
168 PIDGIN_STOCK_ANIMATION_CONNECT20,
169 PIDGIN_STOCK_ANIMATION_CONNECT21,
170 PIDGIN_STOCK_ANIMATION_CONNECT22,
171 PIDGIN_STOCK_ANIMATION_CONNECT23,
172 PIDGIN_STOCK_ANIMATION_CONNECT24,
173 PIDGIN_STOCK_ANIMATION_CONNECT25,
174 PIDGIN_STOCK_ANIMATION_CONNECT26,
175 PIDGIN_STOCK_ANIMATION_CONNECT27,
176 PIDGIN_STOCK_ANIMATION_CONNECT28,
177 PIDGIN_STOCK_ANIMATION_CONNECT29,
178 PIDGIN_STOCK_ANIMATION_CONNECT30,
179 NULL
182 static GtkContainerClass *parent_class = NULL;
184 static void pidgin_status_box_class_init (PidginStatusBoxClass *klass);
185 static void pidgin_status_box_init (PidginStatusBox *status_box);
187 GType
188 pidgin_status_box_get_type (void)
190 static GType status_box_type = 0;
192 if (!status_box_type)
194 static const GTypeInfo status_box_info =
196 sizeof (PidginStatusBoxClass),
197 NULL, /* base_init */
198 NULL, /* base_finalize */
199 (GClassInitFunc) pidgin_status_box_class_init,
200 NULL, /* class_finalize */
201 NULL, /* class_data */
202 sizeof (PidginStatusBox),
204 (GInstanceInitFunc) pidgin_status_box_init,
205 NULL /* value_table */
208 status_box_type = g_type_register_static(GTK_TYPE_CONTAINER,
209 "PidginStatusBox",
210 &status_box_info,
214 return status_box_type;
217 static void
218 pidgin_status_box_get_property(GObject *object, guint param_id,
219 GValue *value, GParamSpec *psec)
221 PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(object);
223 switch (param_id) {
224 case PROP_ACCOUNT:
225 g_value_set_pointer(value, statusbox->account);
226 break;
227 case PROP_ICON_SEL:
228 g_value_set_boolean(value, statusbox->icon_box != NULL);
229 break;
230 default:
231 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, psec);
232 break;
236 static void
237 update_to_reflect_account_status(PidginStatusBox *status_box, PurpleAccount *account, PurpleStatus *newstatus)
239 GList *l;
240 int status_no = -1;
241 const PurpleStatusType *statustype = NULL;
242 const char *message;
244 statustype = purple_status_type_find_with_id((GList *)purple_account_get_status_types(account),
245 (char *)purple_status_type_get_id(purple_status_get_status_type(newstatus)));
247 for (l = purple_account_get_status_types(account); l != NULL; l = l->next) {
248 PurpleStatusType *status_type = (PurpleStatusType *)l->data;
250 if (!purple_status_type_is_user_settable(status_type) ||
251 purple_status_type_is_independent(status_type))
252 continue;
253 status_no++;
254 if (statustype == status_type)
255 break;
258 if (status_no != -1) {
259 GtkTreePath *path;
260 gtk_widget_set_sensitive(GTK_WIDGET(status_box), FALSE);
261 path = gtk_tree_path_new_from_indices(status_no, -1);
262 if (status_box->active_row)
263 gtk_tree_row_reference_free(status_box->active_row);
264 status_box->active_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box->dropdown_store), path);
265 gtk_tree_path_free(path);
267 message = purple_status_get_attr_string(newstatus, "message");
269 if (!message || !*message)
271 gtk_widget_hide(status_box->vbox);
272 status_box->editor_visible = FALSE;
274 else
276 gtk_widget_show_all(status_box->vbox);
277 status_box->editor_visible = TRUE;
278 talkatu_markup_set_html(TALKATU_BUFFER(status_box->buffer), message, -1);
280 gtk_widget_set_sensitive(GTK_WIDGET(status_box), TRUE);
281 pidgin_status_box_refresh(status_box);
285 static void
286 account_status_changed_cb(PurpleAccount *account, PurpleStatus *oldstatus, PurpleStatus *newstatus, PidginStatusBox *status_box)
288 if (status_box->account == account)
289 update_to_reflect_account_status(status_box, account, newstatus);
290 else if (status_box->token_status_account == account)
291 status_menu_refresh_iter(status_box, TRUE);
294 static gboolean
295 icon_box_press_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
297 if (gdk_event_triggers_context_menu((GdkEvent *)event)) {
298 GtkWidget *menu_item;
299 const char *path;
301 if (box->icon_box_menu)
302 gtk_widget_destroy(box->icon_box_menu);
304 box->icon_box_menu = gtk_menu_new();
306 pidgin_new_menu_item(box->icon_box_menu,
307 _("Select Buddy Icon"), GTK_STOCK_ADD,
308 G_CALLBACK(choose_buddy_icon_cb), box);
310 menu_item = pidgin_new_menu_item(box->icon_box_menu, _("Remove"), GTK_STOCK_REMOVE,
311 G_CALLBACK(remove_buddy_icon_cb), box);
312 if (!(path = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon"))
313 || !*path)
314 gtk_widget_set_sensitive(menu_item, FALSE);
316 gtk_menu_popup_at_pointer(GTK_MENU(box->icon_box_menu), (GdkEvent *)event);
318 } else {
319 choose_buddy_icon_cb(widget, box);
321 return FALSE;
324 static void
325 icon_box_dnd_cb(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
326 GtkSelectionData *sd, guint info, guint t, PidginStatusBox *box)
328 gchar *name = (gchar *) gtk_selection_data_get_data(sd);
330 if ((gtk_selection_data_get_length(sd) >= 0)
331 && (gtk_selection_data_get_format(sd) == 8)) {
332 /* Well, it looks like the drag event was cool.
333 * Let's do something with it */
334 if (!g_ascii_strncasecmp(name, "file://", 7)) {
335 GError *converr = NULL;
336 gchar *tmp, *rtmp;
338 if(!(tmp = g_filename_from_uri(name, NULL, &converr))) {
339 purple_debug(PURPLE_DEBUG_ERROR, "buddyicon", "%s\n",
340 (converr ? converr->message :
341 "g_filename_from_uri error"));
342 return;
344 if ((rtmp = strchr(tmp, '\r')) || (rtmp = strchr(tmp, '\n')))
345 *rtmp = '\0';
346 icon_choose_cb(tmp, box);
347 g_free(tmp);
349 gtk_drag_finish(dc, TRUE, FALSE, t);
351 gtk_drag_finish(dc, FALSE, FALSE, t);
354 static gboolean
355 icon_box_enter_cb(GtkWidget *widget, GdkEventCrossing *event, PidginStatusBox *box)
357 gdk_window_set_cursor(gtk_widget_get_window(widget), box->hand_cursor);
358 gtk_image_set_from_pixbuf(GTK_IMAGE(box->icon), box->buddy_icon_hover);
359 return FALSE;
362 static gboolean
363 icon_box_leave_cb(GtkWidget *widget, GdkEventCrossing *event, PidginStatusBox *box)
365 gdk_window_set_cursor(gtk_widget_get_window(widget), box->arrow_cursor);
366 gtk_image_set_from_pixbuf(GTK_IMAGE(box->icon), box->buddy_icon) ;
367 return FALSE;
371 static const GtkTargetEntry dnd_targets[] = {
372 {"text/plain", 0, 0},
373 {"text/uri-list", 0, 1},
374 {"STRING", 0, 2}
377 static void
378 setup_icon_box(PidginStatusBox *status_box)
380 GdkDisplay *display;
382 if (status_box->icon_box != NULL)
383 return;
385 status_box->icon = gtk_image_new();
386 status_box->icon_box = gtk_event_box_new();
387 gtk_widget_set_parent(status_box->icon_box, GTK_WIDGET(status_box));
388 gtk_widget_show(status_box->icon_box);
390 gtk_widget_set_tooltip_text(status_box->icon_box,
391 status_box->account ? _("Click to change your buddyicon for this account.") :
392 _("Click to change your buddyicon for all accounts."));
394 if (status_box->account &&
395 !purple_account_get_bool(status_box->account, "use-global-buddyicon", TRUE))
397 PurpleImage *img = purple_buddy_icons_find_account_icon(status_box->account);
398 pidgin_status_box_set_buddy_icon(status_box, img);
399 g_object_unref(img);
401 else
403 const char *filename = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon");
404 PurpleImage *img = NULL;
406 if (filename && *filename)
407 img = purple_image_new_from_file(filename, NULL);
409 pidgin_status_box_set_buddy_icon(status_box, img);
410 if (img)
411 g_object_unref(img);
414 display = gtk_widget_get_display(status_box->icon_box);
415 status_box->hand_cursor = gdk_cursor_new_for_display(display, GDK_HAND2);
416 status_box->arrow_cursor = gdk_cursor_new_for_display(display, GDK_LEFT_PTR);
418 /* Set up DND */
419 gtk_drag_dest_set(status_box->icon_box,
420 GTK_DEST_DEFAULT_MOTION |
421 GTK_DEST_DEFAULT_DROP,
422 dnd_targets,
423 sizeof(dnd_targets) / sizeof(GtkTargetEntry),
424 GDK_ACTION_COPY);
426 g_signal_connect(G_OBJECT(status_box->icon_box), "drag_data_received", G_CALLBACK(icon_box_dnd_cb), status_box);
427 g_signal_connect(G_OBJECT(status_box->icon_box), "enter-notify-event", G_CALLBACK(icon_box_enter_cb), status_box);
428 g_signal_connect(G_OBJECT(status_box->icon_box), "leave-notify-event", G_CALLBACK(icon_box_leave_cb), status_box);
429 g_signal_connect(G_OBJECT(status_box->icon_box), "button-press-event", G_CALLBACK(icon_box_press_cb), status_box);
431 gtk_container_add(GTK_CONTAINER(status_box->icon_box), status_box->icon);
432 gtk_widget_show(status_box->icon);
435 static void
436 destroy_icon_box(PidginStatusBox *statusbox)
438 g_clear_pointer(&statusbox->icon_box, gtk_widget_destroy);
440 g_clear_object(&statusbox->hand_cursor);
441 g_clear_object(&statusbox->arrow_cursor);
443 g_clear_object(&statusbox->buddy_icon_img);
445 g_clear_object(&statusbox->buddy_icon);
446 g_clear_object(&statusbox->buddy_icon_hover);
448 g_clear_pointer(&statusbox->buddy_icon_sel, gtk_widget_destroy);
450 g_clear_pointer(&statusbox->icon_box_menu, gtk_widget_destroy);
452 statusbox->icon = NULL;
455 static void
456 pidgin_status_box_set_property(GObject *object, guint param_id,
457 const GValue *value, GParamSpec *pspec)
459 PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(object);
461 switch (param_id) {
462 case PROP_ICON_SEL:
463 if (g_value_get_boolean(value)) {
464 if (statusbox->account) {
465 PurpleBuddyIconSpec *icon_spec = NULL;
466 PurpleProtocol *protocol =
467 purple_protocols_find(purple_account_get_protocol_id(statusbox->account));
468 if (protocol)
469 icon_spec = purple_protocol_get_icon_spec(protocol);
470 if (icon_spec && icon_spec->format != NULL)
471 setup_icon_box(statusbox);
472 } else {
473 setup_icon_box(statusbox);
475 } else {
476 destroy_icon_box(statusbox);
478 break;
479 case PROP_ACCOUNT:
480 statusbox->account = g_value_get_pointer(value);
481 if (statusbox->account)
482 statusbox->token_status_account = NULL;
483 else
484 statusbox->token_status_account = check_active_accounts_for_identical_statuses();
486 pidgin_status_box_regenerate(statusbox, TRUE);
488 break;
489 default:
490 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
491 break;
495 static void
496 pidgin_status_box_dispose(GObject *obj)
498 PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(obj);
500 destroy_icon_box(statusbox);
503 static void
504 pidgin_status_box_finalize(GObject *obj)
506 PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(obj);
507 gsize i;
509 purple_signals_disconnect_by_handle(statusbox);
510 purple_prefs_disconnect_by_handle(statusbox);
512 if (statusbox->active_row)
513 gtk_tree_row_reference_free(statusbox->active_row);
515 for (i = 0; i < G_N_ELEMENTS(statusbox->connecting_pixbufs); i++) {
516 if (statusbox->connecting_pixbufs[i] != NULL)
517 g_object_unref(G_OBJECT(statusbox->connecting_pixbufs[i]));
520 for (i = 0; i < G_N_ELEMENTS(statusbox->typing_pixbufs); i++) {
521 if (statusbox->typing_pixbufs[i] != NULL)
522 g_object_unref(G_OBJECT(statusbox->typing_pixbufs[i]));
525 g_object_unref(G_OBJECT(statusbox->store));
526 g_object_unref(G_OBJECT(statusbox->dropdown_store));
527 G_OBJECT_CLASS(parent_class)->finalize(obj);
530 static GType
531 pidgin_status_box_child_type (GtkContainer *container)
533 return GTK_TYPE_WIDGET;
536 static void
537 pidgin_status_box_class_init (PidginStatusBoxClass *klass)
539 GObjectClass *object_class;
540 GtkWidgetClass *widget_class;
541 GtkContainerClass *container_class = (GtkContainerClass*)klass;
543 parent_class = g_type_class_peek_parent(klass);
545 widget_class = (GtkWidgetClass*)klass;
546 widget_class->get_preferred_height = pidgin_status_box_get_preferred_height;
547 widget_class->draw = pidgin_status_box_draw;
548 widget_class->size_allocate = pidgin_status_box_size_allocate;
550 container_class->child_type = pidgin_status_box_child_type;
551 container_class->forall = pidgin_status_box_forall;
552 container_class->remove = NULL;
554 object_class = (GObjectClass *)klass;
556 object_class->dispose = pidgin_status_box_dispose;
557 object_class->finalize = pidgin_status_box_finalize;
559 object_class->get_property = pidgin_status_box_get_property;
560 object_class->set_property = pidgin_status_box_set_property;
562 g_object_class_install_property(object_class,
563 PROP_ACCOUNT,
564 g_param_spec_pointer("account",
565 "Account",
566 "The account, or NULL for all accounts",
567 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
570 g_object_class_install_property(object_class,
571 PROP_ICON_SEL,
572 g_param_spec_boolean("iconsel",
573 "Icon Selector",
574 "Whether the icon selector should be displayed or not.",
575 FALSE,
576 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
582 * This updates the text displayed on the status box so that it shows
583 * the current status. This is the only function in this file that
584 * should modify status_box->store
586 static void
587 pidgin_status_box_refresh(PidginStatusBox *status_box)
589 const char *aa_color;
590 PurpleSavedStatus *saved_status;
591 char *primary, *secondary, *text;
592 const char *stock = NULL;
593 GdkPixbuf *emblem = NULL;
594 GtkTreePath *path;
595 gboolean account_status = FALSE;
596 PurpleAccount *acct = (status_box->account) ? status_box->account : status_box->token_status_account;
598 saved_status = purple_savedstatus_get_current();
600 if (status_box->account || (status_box->token_status_account
601 && purple_savedstatus_is_transient(saved_status)))
602 account_status = TRUE;
604 /* Primary */
605 if (status_box->typing != 0)
607 GtkTreeIter iter;
608 PidginStatusBoxItemType type;
609 gpointer data;
611 /* Primary (get the status selected in the dropdown) */
612 path = gtk_tree_row_reference_get_path(status_box->active_row);
613 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
614 return;
615 gtk_tree_path_free(path);
617 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
618 TYPE_COLUMN, &type,
619 DATA_COLUMN, &data,
620 -1);
621 if (type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE)
622 primary = g_strdup(purple_primitive_get_name_from_type(GPOINTER_TO_INT(data)));
623 else
624 /* This should never happen, but just in case... */
625 primary = g_strdup("New status");
627 else if (account_status)
628 primary = g_strdup(purple_status_get_name(purple_account_get_active_status(acct)));
629 else if (purple_savedstatus_is_transient(saved_status))
630 primary = g_strdup(purple_primitive_get_name_from_type(purple_savedstatus_get_primitive_type(saved_status)));
631 else
632 primary = g_markup_escape_text(purple_savedstatus_get_title(saved_status), -1);
634 /* Secondary */
635 if (status_box->typing != 0)
636 secondary = g_strdup(_("Typing"));
637 else if (status_box->connecting)
638 secondary = g_strdup(_("Connecting"));
639 else if (!status_box->network_available)
640 secondary = g_strdup(_("Waiting for network connection"));
641 else if (purple_savedstatus_is_transient(saved_status))
642 secondary = NULL;
643 else
645 const char *message;
646 char *tmp;
647 message = purple_savedstatus_get_message(saved_status);
648 if (message != NULL)
650 tmp = purple_markup_strip_html(message);
651 purple_util_chrreplace(tmp, '\n', ' ');
652 secondary = g_markup_escape_text(tmp, -1);
653 g_free(tmp);
655 else
656 secondary = NULL;
659 /* Pixbuf */
660 if (status_box->typing != 0)
661 stock = typing_stock_ids[status_box->typing_index];
662 else if (status_box->connecting)
663 stock = connecting_stock_ids[status_box->connecting_index];
664 else
666 PurpleStatusType *status_type;
667 PurpleStatusPrimitive prim;
668 if (account_status) {
669 status_type = purple_status_get_status_type(purple_account_get_active_status(acct));
670 prim = purple_status_type_get_primitive(status_type);
671 } else {
672 prim = purple_savedstatus_get_primitive_type(saved_status);
675 stock = pidgin_stock_id_from_status_primitive(prim);
678 aa_color = pidgin_get_dim_grey_string(GTK_WIDGET(status_box));
679 if (status_box->account != NULL) {
680 text = g_strdup_printf("%s - <span size=\"smaller\" color=\"%s\">%s</span>",
681 purple_account_get_username(status_box->account),
682 aa_color, secondary ? secondary : primary);
683 emblem = pidgin_create_protocol_icon(status_box->account, PIDGIN_PROTOCOL_ICON_SMALL);
684 } else if (secondary != NULL) {
685 text = g_strdup_printf("%s<span size=\"smaller\" color=\"%s\"> - %s</span>",
686 primary, aa_color, secondary);
687 } else {
688 text = g_strdup(primary);
690 g_free(primary);
691 g_free(secondary);
694 * Only two columns are used in this list store (does it
695 * really need to be a list store?)
697 gtk_list_store_set(status_box->store, &(status_box->iter),
698 ICON_STOCK_COLUMN, (gpointer)stock,
699 TEXT_COLUMN, text,
700 EMBLEM_COLUMN, emblem,
701 EMBLEM_VISIBLE_COLUMN, (emblem != NULL),
702 -1);
703 g_free(text);
704 if (emblem)
705 g_object_unref(emblem);
707 /* Make sure to activate the only row in the tree view */
708 path = gtk_tree_path_new_from_string("0");
709 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(status_box->cell_view), path);
710 gtk_tree_path_free(path);
712 update_size(status_box);
715 static PurpleStatusType *
716 find_status_type_by_index(PurpleAccount *account, gint active)
718 GList *l = purple_account_get_status_types(account);
719 gint i;
721 for (i = 0; l; l = l->next) {
722 PurpleStatusType *status_type = l->data;
723 if (!purple_status_type_is_user_settable(status_type) ||
724 purple_status_type_is_independent(status_type))
725 continue;
727 if (active == i)
728 return status_type;
729 i++;
732 return NULL;
736 * This updates the GtkTreeView so that it correctly shows the state
737 * we are currently using. It is used when the current state is
738 * updated from somewhere other than the GtkStatusBox (from a plugin,
739 * or when signing on with the "-n" option, for example). It is
740 * also used when the user selects the "New..." option.
742 * Maybe we could accomplish this by triggering off the mouse and
743 * keyboard signals instead of the changed signal?
745 static void
746 status_menu_refresh_iter(PidginStatusBox *status_box, gboolean status_changed)
748 PurpleSavedStatus *saved_status;
749 PurpleStatusPrimitive primitive;
750 gint index;
751 const char *message;
752 GtkTreePath *path = NULL;
754 /* this function is inappropriate for ones with accounts */
755 if (status_box->account)
756 return;
758 saved_status = purple_savedstatus_get_current();
761 * Suppress the "changed" signal because the status
762 * was changed programmatically.
764 gtk_widget_set_sensitive(GTK_WIDGET(status_box), FALSE);
767 * If there is a token-account, then select the primitive from the
768 * dropdown using a loop. Otherwise select from the default list.
770 primitive = purple_savedstatus_get_primitive_type(saved_status);
771 if (!status_box->token_status_account && purple_savedstatus_is_transient(saved_status) &&
772 ((primitive == PURPLE_STATUS_AVAILABLE) || (primitive == PURPLE_STATUS_AWAY) ||
773 (primitive == PURPLE_STATUS_INVISIBLE) || (primitive == PURPLE_STATUS_OFFLINE) ||
774 (primitive == PURPLE_STATUS_UNAVAILABLE)) &&
775 (!purple_savedstatus_has_substatuses(saved_status)))
777 index = get_statusbox_index(status_box, saved_status);
778 path = gtk_tree_path_new_from_indices(index, -1);
780 else
782 GtkTreeIter iter;
783 PidginStatusBoxItemType type;
784 gpointer data;
786 /* If this saved status is in the list store, then set it as the active item */
787 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(status_box->dropdown_store), &iter))
791 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
792 TYPE_COLUMN, &type,
793 DATA_COLUMN, &data,
794 -1);
796 /* This is a special case because Primitives for the token_status_account are actually
797 * saved statuses with substatuses for the enabled accounts */
798 if (status_box->token_status_account && purple_savedstatus_is_transient(saved_status)
799 && type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE && primitive == (PurpleStatusPrimitive)GPOINTER_TO_INT(data))
801 char *name;
802 const char *acct_status_name = purple_status_get_name(
803 purple_account_get_active_status(status_box->token_status_account));
805 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
806 TEXT_COLUMN, &name, -1);
808 if (!purple_savedstatus_has_substatuses(saved_status)
809 || purple_strequal(name, acct_status_name))
811 /* Found! */
812 path = gtk_tree_model_get_path(GTK_TREE_MODEL(status_box->dropdown_store), &iter);
813 g_free(name);
814 break;
816 g_free(name);
818 } else if ((type == PIDGIN_STATUS_BOX_TYPE_POPULAR) &&
819 (GPOINTER_TO_INT(data) == purple_savedstatus_get_creation_time(saved_status)))
821 /* Found! */
822 path = gtk_tree_model_get_path(GTK_TREE_MODEL(status_box->dropdown_store), &iter);
823 break;
825 } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(status_box->dropdown_store), &iter));
829 if (status_box->active_row)
830 gtk_tree_row_reference_free(status_box->active_row);
831 if (path) { /* path should never be NULL */
832 status_box->active_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box->dropdown_store), path);
833 gtk_tree_path_free(path);
834 } else
835 status_box->active_row = NULL;
837 if (status_changed) {
838 message = purple_savedstatus_get_message(saved_status);
841 * If we are going to hide the editor, don't retain the
842 * message because showing the old message later is
843 * confusing. If we are going to set the message to a pre-set,
844 * then we need to do this anyway
846 * Suppress the "changed" signal because the status
847 * was changed programmatically.
849 gtk_widget_set_sensitive(GTK_WIDGET(status_box->view), FALSE);
851 talkatu_buffer_clear(TALKATU_BUFFER(status_box->buffer));
853 if (!purple_savedstatus_is_transient(saved_status) || !message || !*message)
855 status_box->editor_visible = FALSE;
856 gtk_widget_hide(status_box->vbox);
858 else
860 status_box->editor_visible = TRUE;
861 gtk_widget_show_all(status_box->vbox);
863 talkatu_markup_set_html(TALKATU_BUFFER(status_box->buffer), message, -1);
866 gtk_widget_set_sensitive(GTK_WIDGET(status_box->view), TRUE);
867 update_size(status_box);
870 /* Stop suppressing the "changed" signal. */
871 gtk_widget_set_sensitive(GTK_WIDGET(status_box), TRUE);
874 static void
875 add_popular_statuses(PidginStatusBox *statusbox)
877 GList *list, *cur;
879 list = purple_savedstatuses_get_popular(6);
880 if (list == NULL)
881 /* Odd... oh well, nothing we can do about it. */
882 return;
884 pidgin_status_box_add_separator(statusbox);
886 for (cur = list; cur != NULL; cur = cur->next)
888 PurpleSavedStatus *saved = cur->data;
889 const gchar *message;
890 gchar *stripped = NULL;
891 PidginStatusBoxItemType type;
893 if (purple_savedstatus_is_transient(saved))
896 * Transient statuses do not have a title, so the savedstatus
897 * API returns the message when purple_savedstatus_get_title() is
898 * called, so we don't need to get the message a second time.
900 type = PIDGIN_STATUS_BOX_TYPE_POPULAR;
902 else
904 type = PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR;
906 message = purple_savedstatus_get_message(saved);
907 if (message != NULL)
909 stripped = purple_markup_strip_html(message);
910 purple_util_chrreplace(stripped, '\n', ' ');
914 pidgin_status_box_add(statusbox, type,
915 NULL, purple_savedstatus_get_title(saved), stripped,
916 GINT_TO_POINTER(purple_savedstatus_get_creation_time(saved)));
917 g_free(stripped);
920 g_list_free(list);
923 /* This returns NULL if the active accounts don't have identical
924 * statuses and a token account if they do */
925 static PurpleAccount* check_active_accounts_for_identical_statuses(void)
927 GList *iter, *active_accts = purple_accounts_get_all_active();
928 PurpleAccount *acct1 = NULL;
929 const char *proto1 = NULL;
931 if (active_accts) {
932 acct1 = active_accts->data;
933 proto1 = purple_account_get_protocol_id(acct1);
934 } else {
935 /* there's no enabled account */
936 return NULL;
939 /* start at the second account */
940 for (iter = active_accts->next; iter; iter = iter->next) {
941 PurpleAccount *acct2 = iter->data;
942 GList *s1, *s2;
944 if (!purple_strequal(proto1, purple_account_get_protocol_id(acct2))) {
945 acct1 = NULL;
946 break;
949 for (s1 = purple_account_get_status_types(acct1),
950 s2 = purple_account_get_status_types(acct2); s1 && s2;
951 s1 = s1->next, s2 = s2->next) {
952 PurpleStatusType *st1 = s1->data, *st2 = s2->data;
953 /* TODO: Are these enough to consider the statuses identical? */
954 if (purple_status_type_get_primitive(st1) != purple_status_type_get_primitive(st2)
955 || !purple_strequal(purple_status_type_get_id(st1), purple_status_type_get_id(st2))
956 || !purple_strequal(purple_status_type_get_name(st1), purple_status_type_get_name(st2))) {
957 acct1 = NULL;
958 break;
962 if (s1 != s2) {/* Will both be NULL if matched */
963 acct1 = NULL;
964 break;
968 g_list_free(active_accts);
970 return acct1;
973 static void
974 add_account_statuses(PidginStatusBox *status_box, PurpleAccount *account)
976 /* Per-account */
977 GList *l;
979 for (l = purple_account_get_status_types(account); l != NULL; l = l->next)
981 PurpleStatusType *status_type = (PurpleStatusType *)l->data;
982 PurpleStatusPrimitive prim;
984 if (!purple_status_type_is_user_settable(status_type) ||
985 purple_status_type_is_independent(status_type))
986 continue;
988 prim = purple_status_type_get_primitive(status_type);
990 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box),
991 PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL,
992 purple_status_type_get_name(status_type),
993 NULL,
994 GINT_TO_POINTER(prim));
998 static void
999 pidgin_status_box_regenerate(PidginStatusBox *status_box, gboolean status_changed)
1001 /* Unset the model while clearing it */
1002 gtk_tree_view_set_model(GTK_TREE_VIEW(status_box->tree_view), NULL);
1003 gtk_list_store_clear(status_box->dropdown_store);
1004 /* Don't set the model until the new statuses have been added to the box.
1005 * What is presumably a bug in Gtk < 2.4 causes things to get all confused
1006 * if we do this here. */
1007 /* gtk_combo_box_set_model(GTK_COMBO_BOX(status_box), GTK_TREE_MODEL(status_box->dropdown_store)); */
1009 if (status_box->account == NULL)
1011 /* Do all the currently enabled accounts have the same statuses?
1012 * If so, display them instead of our global list.
1014 if (status_box->token_status_account) {
1015 add_account_statuses(status_box, status_box->token_status_account);
1016 } else {
1017 /* Global */
1018 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL, _("Available"), NULL, GINT_TO_POINTER(PURPLE_STATUS_AVAILABLE));
1019 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL, _("Away"), NULL, GINT_TO_POINTER(PURPLE_STATUS_AWAY));
1020 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL, _("Do not disturb"), NULL, GINT_TO_POINTER(PURPLE_STATUS_UNAVAILABLE));
1021 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL, _("Invisible"), NULL, GINT_TO_POINTER(PURPLE_STATUS_INVISIBLE));
1022 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL, _("Offline"), NULL, GINT_TO_POINTER(PURPLE_STATUS_OFFLINE));
1025 add_popular_statuses(status_box);
1027 pidgin_status_box_add_separator(PIDGIN_STATUS_BOX(status_box));
1028 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_CUSTOM, NULL, _("New status..."), NULL, NULL);
1029 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_SAVED, NULL, _("Saved statuses..."), NULL, NULL);
1031 status_menu_refresh_iter(status_box, status_changed);
1032 pidgin_status_box_refresh(status_box);
1034 } else {
1035 add_account_statuses(status_box, status_box->account);
1036 update_to_reflect_account_status(status_box, status_box->account,
1037 purple_account_get_active_status(status_box->account));
1039 gtk_tree_view_set_model(GTK_TREE_VIEW(status_box->tree_view), GTK_TREE_MODEL(status_box->dropdown_store));
1040 gtk_tree_view_set_search_column(GTK_TREE_VIEW(status_box->tree_view), TEXT_COLUMN);
1043 static gboolean
1044 combo_box_scroll_event_cb(GtkWidget *w, GdkEventScroll *event, G_GNUC_UNUSED gpointer data)
1046 pidgin_status_box_popup(PIDGIN_STATUS_BOX(w), (GdkEvent *)event);
1047 return TRUE;
1050 static gboolean
1051 editor_remove_focus(GtkWidget *w, GdkEventKey *event, PidginStatusBox *status_box)
1053 if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) {
1054 remove_typing_cb(status_box);
1055 return TRUE;
1057 else if (event->keyval == GDK_KEY_Tab || event->keyval == GDK_KEY_KP_Tab || event->keyval == GDK_KEY_ISO_Left_Tab)
1059 /* If last inserted character is a tab, then remove the focus from here */
1060 GtkWidget *top = gtk_widget_get_toplevel(w);
1061 g_signal_emit_by_name(G_OBJECT(top), "move-focus",
1062 (event->state & GDK_SHIFT_MASK) ?
1063 GTK_DIR_TAB_BACKWARD: GTK_DIR_TAB_FORWARD);
1064 return TRUE;
1066 if (status_box->typing == 0)
1067 return FALSE;
1069 /* Reset the status if Escape was pressed */
1070 if (event->keyval == GDK_KEY_Escape)
1072 g_source_remove(status_box->typing);
1073 status_box->typing = 0;
1075 if (status_box->account != NULL)
1076 update_to_reflect_account_status(status_box, status_box->account,
1077 purple_account_get_active_status(status_box->account));
1078 else {
1079 status_menu_refresh_iter(status_box, TRUE);
1080 pidgin_status_box_refresh(status_box);
1082 return TRUE;
1085 pidgin_status_box_pulse_typing(status_box);
1086 g_source_remove(status_box->typing);
1087 status_box->typing = g_timeout_add_seconds(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
1089 return FALSE;
1092 static gboolean
1093 dropdown_store_row_separator_func(GtkTreeModel *model,
1094 GtkTreeIter *iter, gpointer data)
1096 PidginStatusBoxItemType type;
1098 gtk_tree_model_get(model, iter, TYPE_COLUMN, &type, -1);
1100 if (type == PIDGIN_STATUS_BOX_TYPE_SEPARATOR)
1101 return TRUE;
1103 return FALSE;
1106 static void
1107 cache_pixbufs(PidginStatusBox *status_box)
1109 GtkIconSize icon_size;
1110 gsize i;
1112 g_object_set(G_OBJECT(status_box->icon_rend), "xpad", 3, NULL);
1113 icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL);
1115 for (i = 0; i < G_N_ELEMENTS(status_box->connecting_pixbufs); i++) {
1116 if (status_box->connecting_pixbufs[i] != NULL)
1117 g_object_unref(G_OBJECT(status_box->connecting_pixbufs[i]));
1118 if (connecting_stock_ids[i])
1119 status_box->connecting_pixbufs[i] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox),
1120 connecting_stock_ids[i], icon_size, "PidginStatusBox");
1121 else
1122 status_box->connecting_pixbufs[i] = NULL;
1124 status_box->connecting_index = 0;
1127 for (i = 0; i < G_N_ELEMENTS(status_box->typing_pixbufs); i++) {
1128 if (status_box->typing_pixbufs[i] != NULL)
1129 g_object_unref(G_OBJECT(status_box->typing_pixbufs[i]));
1130 if (typing_stock_ids[i])
1131 status_box->typing_pixbufs[i] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox),
1132 typing_stock_ids[i], icon_size, "PidginStatusBox");
1133 else
1134 status_box->typing_pixbufs[i] = NULL;
1136 status_box->typing_index = 0;
1139 static void account_enabled_cb(PurpleAccount *acct, PidginStatusBox *status_box)
1141 PurpleAccount *initial_token_acct = status_box->token_status_account;
1143 if (status_box->account)
1144 return;
1146 status_box->token_status_account = check_active_accounts_for_identical_statuses();
1148 /* Regenerate the list if it has changed */
1149 if (initial_token_acct != status_box->token_status_account) {
1150 pidgin_status_box_regenerate(status_box, TRUE);
1155 static void
1156 current_savedstatus_changed_cb(PurpleSavedStatus *now, PurpleSavedStatus *old, PidginStatusBox *status_box)
1158 /* Make sure our current status is added to the list of popular statuses */
1159 pidgin_status_box_regenerate(status_box, TRUE);
1162 static void
1163 saved_status_updated_cb(PurpleSavedStatus *status, PidginStatusBox *status_box)
1165 pidgin_status_box_regenerate(status_box,
1166 purple_savedstatus_get_current() == status);
1169 static void
1170 pidgin_status_box_list_position (PidginStatusBox *status_box, int *x, int *y, int *width, int *height)
1172 GdkScreen *screen;
1173 gint monitor_num;
1174 GdkRectangle monitor;
1175 GtkRequisition popup_req;
1176 GtkPolicyType hpolicy, vpolicy;
1177 GtkAllocation allocation;
1179 gtk_widget_get_allocation(GTK_WIDGET(status_box), &allocation);
1180 gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(status_box)), x, y);
1182 *x += allocation.x;
1183 *y += allocation.y;
1185 *width = allocation.width;
1187 hpolicy = vpolicy = GTK_POLICY_NEVER;
1188 g_object_set(G_OBJECT(status_box->scrolled_window),
1189 "hscrollbar-policy", hpolicy,
1190 "vscrollbar-policy", vpolicy,
1191 NULL);
1192 gtk_widget_get_preferred_size(status_box->popup_frame, NULL, &popup_req);
1194 if (popup_req.width > *width) {
1195 hpolicy = GTK_POLICY_ALWAYS;
1196 g_object_set(G_OBJECT(status_box->scrolled_window),
1197 "hscrollbar-policy", hpolicy,
1198 "vscrollbar-policy", vpolicy,
1199 NULL);
1200 gtk_widget_get_preferred_size(status_box->popup_frame, NULL, &popup_req);
1203 *height = popup_req.height;
1205 screen = gtk_widget_get_screen(GTK_WIDGET(status_box));
1206 monitor_num = gdk_screen_get_monitor_at_window(screen,
1207 gtk_widget_get_window(GTK_WIDGET(status_box)));
1208 gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor);
1210 if (*x < monitor.x)
1211 *x = monitor.x;
1212 else if (*x + *width > monitor.x + monitor.width)
1213 *x = monitor.x + monitor.width - *width;
1215 if (*y + allocation.height + *height <= monitor.y + monitor.height)
1216 *y += allocation.height;
1217 else if (*y - *height >= monitor.y)
1218 *y -= *height;
1219 else if (monitor.y + monitor.height - (*y + allocation.height) > *y - monitor.y)
1221 *y += allocation.height;
1222 *height = monitor.y + monitor.height - *y;
1224 else
1226 *height = *y - monitor.y;
1227 *y = monitor.y;
1230 if (popup_req.height > *height)
1232 vpolicy = GTK_POLICY_ALWAYS;
1234 g_object_set(G_OBJECT(status_box->scrolled_window),
1235 "hscrollbar-policy", hpolicy,
1236 "vscrollbar-policy", vpolicy,
1237 NULL);
1241 static gboolean
1242 popup_grab_on_window(GdkWindow *window, GdkEvent *event)
1244 guint32 activate_time = gdk_event_get_time(event);
1245 GdkDevice *device = gdk_event_get_device(event);
1246 GdkGrabStatus status;
1248 status = gdk_device_grab(device, window, GDK_OWNERSHIP_WINDOW, TRUE,
1249 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1250 GDK_POINTER_MOTION_MASK | GDK_KEY_PRESS_MASK |
1251 GDK_KEY_RELEASE_MASK, NULL, activate_time);
1252 if (status == GDK_GRAB_SUCCESS) {
1253 status = gdk_device_grab(gdk_device_get_associated_device(device),
1254 window, GDK_OWNERSHIP_WINDOW, TRUE,
1255 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1256 GDK_POINTER_MOTION_MASK | GDK_KEY_PRESS_MASK |
1257 GDK_KEY_RELEASE_MASK, NULL, activate_time);
1258 if (status == GDK_GRAB_SUCCESS)
1259 return TRUE;
1260 else
1261 gdk_device_ungrab(device, activate_time);
1264 return FALSE;
1268 static void
1269 pidgin_status_box_popup(PidginStatusBox *box, GdkEvent *event)
1271 int width, height, x, y;
1272 pidgin_status_box_list_position (box, &x, &y, &width, &height);
1274 gtk_widget_set_size_request (box->popup_window, width, height);
1275 gtk_window_move (GTK_WINDOW (box->popup_window), x, y);
1276 gtk_widget_show(box->popup_window);
1277 gtk_widget_grab_focus (box->tree_view);
1278 if (!popup_grab_on_window(gtk_widget_get_window(box->popup_window), event)) {
1279 gtk_widget_hide (box->popup_window);
1280 return;
1282 gtk_grab_add (box->popup_window);
1283 /*box->popup_in_progress = TRUE;*/
1284 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (box->toggle_button),
1285 TRUE);
1287 if (box->active_row) {
1288 GtkTreePath *path = gtk_tree_row_reference_get_path(box->active_row);
1289 gtk_tree_view_set_cursor(GTK_TREE_VIEW(box->tree_view), path, NULL, FALSE);
1290 gtk_tree_path_free(path);
1294 static void
1295 pidgin_status_box_popdown(PidginStatusBox *box, GdkEvent *event)
1297 guint32 time;
1298 GdkDevice *device;
1299 gtk_widget_hide(box->popup_window);
1300 box->popup_in_progress = FALSE;
1301 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box->toggle_button), FALSE);
1302 gtk_grab_remove(box->popup_window);
1303 time = gdk_event_get_time(event);
1304 device = gdk_event_get_device(event);
1305 gdk_device_ungrab(device, time);
1306 gdk_device_ungrab(gdk_device_get_associated_device(device), time);
1309 static gboolean
1310 toggle_key_press_cb(GtkWidget *widget, GdkEventKey *event, PidginStatusBox *box)
1312 switch (event->keyval) {
1313 case GDK_KEY_Return:
1314 case GDK_KEY_KP_Enter:
1315 case GDK_KEY_KP_Space:
1316 case GDK_KEY_space:
1317 if (!box->popup_in_progress) {
1318 pidgin_status_box_popup(box, (GdkEvent *)event);
1319 box->popup_in_progress = TRUE;
1320 } else {
1321 pidgin_status_box_popdown(box, (GdkEvent *)event);
1323 return TRUE;
1324 default:
1325 return FALSE;
1329 static gboolean
1330 toggled_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
1332 if (!box->popup_in_progress)
1333 pidgin_status_box_popup(box, (GdkEvent *)event);
1334 else
1335 pidgin_status_box_popdown(box, (GdkEvent *)event);
1336 return TRUE;
1339 static void
1340 buddy_icon_set_cb(const char *filename, PidginStatusBox *box)
1342 PurpleImage *img = NULL;
1343 PurpleBuddyIconSpec *icon_spec = NULL;
1345 if (box->account) {
1346 PurpleProtocol *protocol =
1347 purple_protocols_find(purple_account_get_protocol_id(box->account));
1348 if (protocol)
1349 icon_spec = purple_protocol_get_icon_spec(protocol);
1350 if (icon_spec && icon_spec->format) {
1351 gpointer data = NULL;
1352 size_t len = 0;
1353 if (filename)
1354 data = pidgin_convert_buddy_icon(protocol, filename, &len);
1355 img = purple_buddy_icons_set_account_icon(box->account, data, len);
1356 if (img) {
1358 * set_account_icon doesn't give us a reference, but we
1359 * unref one below (for the other code path)
1361 g_object_ref(img);
1364 purple_account_set_buddy_icon_path(box->account, filename);
1366 purple_account_set_bool(box->account, "use-global-buddyicon", (filename != NULL));
1368 } else {
1369 GList *accounts;
1370 for (accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) {
1371 PurpleAccount *account = accounts->data;
1372 PurpleProtocol *protocol =
1373 purple_protocols_find(purple_account_get_protocol_id(account));
1374 if (protocol)
1375 icon_spec = purple_protocol_get_icon_spec(protocol);
1376 if (icon_spec && icon_spec->format &&
1377 purple_account_get_bool(account, "use-global-buddyicon", TRUE)) {
1378 gpointer data = NULL;
1379 size_t len = 0;
1380 if (filename)
1381 data = pidgin_convert_buddy_icon(protocol, filename, &len);
1382 purple_buddy_icons_set_account_icon(account, data, len);
1383 purple_account_set_buddy_icon_path(account, filename);
1387 /* Even if no accounts were processed, load the icon that was set. */
1388 if (filename != NULL)
1389 img = purple_image_new_from_file(filename, NULL);
1392 pidgin_status_box_set_buddy_icon(box, img);
1393 if (img)
1394 g_object_unref(img);
1397 static void
1398 remove_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box)
1400 if (box->account == NULL)
1401 /* The pref-connect callback does the actual work */
1402 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon", NULL);
1403 else
1404 buddy_icon_set_cb(NULL, box);
1406 gtk_widget_destroy(box->icon_box_menu);
1407 box->icon_box_menu = NULL;
1410 static void
1411 choose_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box)
1413 if (box->buddy_icon_sel) {
1414 gtk_window_present(GTK_WINDOW(box->buddy_icon_sel));
1415 } else {
1416 box->buddy_icon_sel = pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtk_widget_get_toplevel(w)), icon_choose_cb, box);
1417 gtk_widget_show_all(box->buddy_icon_sel);
1421 static void
1422 icon_choose_cb(const char *filename, gpointer data)
1424 PidginStatusBox *box = data;
1425 if (filename) {
1426 if (box->account == NULL)
1427 /* The pref-connect callback does the actual work */
1428 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon", filename);
1429 else
1430 buddy_icon_set_cb(filename, box);
1433 box->buddy_icon_sel = NULL;
1436 static void
1437 update_buddyicon_cb(const char *name, PurplePrefType type,
1438 gconstpointer value, gpointer data)
1440 buddy_icon_set_cb(value, (PidginStatusBox*) data);
1443 static void
1444 treeview_activate_current_selection(PidginStatusBox *status_box, GtkTreePath *path, GdkEvent *event)
1446 if (status_box->active_row)
1447 gtk_tree_row_reference_free(status_box->active_row);
1449 status_box->active_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box->dropdown_store), path);
1450 pidgin_status_box_popdown(status_box, event);
1451 pidgin_status_box_changed(status_box);
1454 static void tree_view_delete_current_selection_cb(gpointer data)
1456 PurpleSavedStatus *saved;
1458 saved = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
1459 g_return_if_fail(saved != NULL);
1461 if (purple_savedstatus_get_current() != saved)
1462 purple_savedstatus_delete_by_status(saved);
1465 static void
1466 tree_view_delete_current_selection(PidginStatusBox *status_box, GtkTreePath *path, GdkEvent *event)
1468 GtkTreeIter iter;
1469 gpointer data;
1470 PurpleSavedStatus *saved;
1471 gchar *msg;
1473 if (status_box->active_row) {
1474 /* don't delete active status */
1475 if (gtk_tree_path_compare(path, gtk_tree_row_reference_get_path(status_box->active_row)) == 0)
1476 return;
1479 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
1480 return;
1482 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
1483 DATA_COLUMN, &data,
1484 -1);
1486 saved = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
1487 g_return_if_fail(saved != NULL);
1488 if (saved == purple_savedstatus_get_current())
1489 return;
1491 msg = g_strdup_printf(_("Are you sure you want to delete %s?"), purple_savedstatus_get_title(saved));
1493 purple_request_action(saved, NULL, msg, NULL, 0,
1494 NULL,
1495 data, 2,
1496 _("Delete"), tree_view_delete_current_selection_cb,
1497 _("Cancel"), NULL);
1499 g_free(msg);
1501 pidgin_status_box_popdown(status_box, event);
1504 static gboolean
1505 treeview_button_release_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *status_box)
1507 GtkTreePath *path = NULL;
1508 int ret;
1509 GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);
1511 if (ewidget != status_box->tree_view) {
1512 if (ewidget == status_box->toggle_button &&
1513 status_box->popup_in_progress &&
1514 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (status_box->toggle_button))) {
1515 pidgin_status_box_popdown(status_box, (GdkEvent *)event);
1516 return TRUE;
1517 } else if (ewidget == status_box->toggle_button) {
1518 status_box->popup_in_progress = TRUE;
1521 /* released outside treeview */
1522 if (ewidget != status_box->toggle_button) {
1523 pidgin_status_box_popdown(status_box, (GdkEvent *)event);
1524 return TRUE;
1527 return FALSE;
1530 ret = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (status_box->tree_view),
1531 event->x, event->y,
1532 &path,
1533 NULL, NULL, NULL);
1535 if (!ret)
1536 return TRUE; /* clicked outside window? */
1538 treeview_activate_current_selection(status_box, path, (GdkEvent *)event);
1539 gtk_tree_path_free (path);
1541 return TRUE;
1544 static gboolean
1545 treeview_key_press_event(GtkWidget *widget,
1546 GdkEventKey *event, PidginStatusBox *box)
1548 if (box->popup_in_progress) {
1549 if (event->keyval == GDK_KEY_Escape) {
1550 pidgin_status_box_popdown(box, (GdkEvent *)event);
1551 return TRUE;
1552 } else {
1553 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(box->tree_view));
1554 GtkTreeIter iter;
1555 GtkTreePath *path;
1557 if (gtk_tree_selection_get_selected(sel, NULL, &iter)) {
1558 gboolean ret = TRUE;
1559 path = gtk_tree_model_get_path(GTK_TREE_MODEL(box->dropdown_store), &iter);
1560 if (event->keyval == GDK_KEY_Return) {
1561 treeview_activate_current_selection(box, path, (GdkEvent *)event);
1562 } else if (event->keyval == GDK_KEY_Delete) {
1563 tree_view_delete_current_selection(box, path, (GdkEvent *)event);
1564 } else
1565 ret = FALSE;
1567 gtk_tree_path_free (path);
1568 return ret;
1572 return FALSE;
1575 static void
1576 treeview_cursor_changed_cb(GtkTreeView *treeview, gpointer data)
1578 GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview);
1579 GtkTreeModel *model = GTK_TREE_MODEL (data);
1580 GtkTreeIter iter;
1581 GtkTreePath *cursor;
1582 GtkTreePath *selection;
1583 gint cmp;
1585 if (gtk_tree_selection_get_selected (sel, NULL, &iter)) {
1586 if ((selection = gtk_tree_model_get_path (model, &iter)) == NULL) {
1587 /* Shouldn't happen, but ignore anyway */
1588 return;
1590 } else {
1591 /* I don't think this can happen, but we'll just ignore it */
1592 return;
1595 gtk_tree_view_get_cursor (treeview, &cursor, NULL);
1596 if (cursor == NULL) {
1597 /* Probably won't happen in a 'cursor-changed' event? */
1598 gtk_tree_path_free (selection);
1599 return;
1602 cmp = gtk_tree_path_compare (cursor, selection);
1603 if (cmp < 0) {
1604 /* The cursor moved up without moving the selection, so move it up again */
1605 gtk_tree_path_prev (cursor);
1606 gtk_tree_view_set_cursor (treeview, cursor, NULL, FALSE);
1607 } else if (cmp > 0) {
1608 /* The cursor moved down without moving the selection, so move it down again */
1609 gtk_tree_path_next (cursor);
1610 gtk_tree_view_set_cursor (treeview, cursor, NULL, FALSE);
1613 gtk_tree_path_free (selection);
1614 gtk_tree_path_free (cursor);
1617 static void
1618 pidgin_status_box_buffer_changed_cb(GtkTextBuffer *buffer, gpointer data) {
1619 PidginStatusBox *status_box = (PidginStatusBox*)data;
1620 if (gtk_widget_get_sensitive(GTK_WIDGET(status_box)))
1622 if (status_box->typing != 0) {
1623 pidgin_status_box_pulse_typing(status_box);
1624 g_source_remove(status_box->typing);
1626 status_box->typing = g_timeout_add_seconds(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
1628 pidgin_status_box_refresh(status_box);
1631 static void
1632 pidgin_status_box_init (PidginStatusBox *status_box)
1634 GtkCellRenderer *text_rend;
1635 GtkCellRenderer *icon_rend;
1636 GtkCellRenderer *emblem_rend;
1637 GtkWidget *toplevel;
1638 GtkTreeSelection *sel;
1640 gtk_widget_set_has_window(GTK_WIDGET(status_box), FALSE);
1641 status_box->editor_visible = FALSE;
1642 status_box->network_available = purple_network_is_available();
1643 status_box->connecting = FALSE;
1644 status_box->typing = 0;
1645 status_box->toggle_button = gtk_toggle_button_new();
1646 status_box->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
1647 status_box->cell_view = gtk_cell_view_new();
1648 status_box->vsep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
1649 status_box->arrow = gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON);
1651 status_box->store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_INT, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING,
1652 G_TYPE_STRING, G_TYPE_POINTER, GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN);
1653 status_box->dropdown_store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_INT, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_STRING,
1654 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
1656 gtk_cell_view_set_model(GTK_CELL_VIEW(status_box->cell_view), GTK_TREE_MODEL(status_box->store));
1657 gtk_list_store_append(status_box->store, &(status_box->iter));
1659 atk_object_set_name(gtk_widget_get_accessible(status_box->toggle_button), _("Status Selector"));
1661 gtk_container_add(GTK_CONTAINER(status_box->toggle_button), status_box->hbox);
1662 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->cell_view, TRUE, TRUE, 0);
1663 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->vsep, FALSE, FALSE, 0);
1664 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->arrow, FALSE, FALSE, 0);
1665 gtk_widget_show_all(status_box->toggle_button);
1666 gtk_button_set_focus_on_click(GTK_BUTTON(status_box->toggle_button), FALSE);
1668 text_rend = gtk_cell_renderer_text_new();
1669 icon_rend = gtk_cell_renderer_pixbuf_new();
1670 emblem_rend = gtk_cell_renderer_pixbuf_new();
1671 status_box->popup_window = gtk_window_new (GTK_WINDOW_POPUP);
1673 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (status_box));
1674 if (GTK_IS_WINDOW (toplevel)) {
1675 gtk_window_set_transient_for (GTK_WINDOW (status_box->popup_window),
1676 GTK_WINDOW (toplevel));
1679 gtk_window_set_resizable (GTK_WINDOW (status_box->popup_window), FALSE);
1680 gtk_window_set_type_hint (GTK_WINDOW (status_box->popup_window),
1681 GDK_WINDOW_TYPE_HINT_POPUP_MENU);
1682 gtk_window_set_screen (GTK_WINDOW (status_box->popup_window),
1683 gtk_widget_get_screen (GTK_WIDGET (status_box)));
1684 status_box->popup_frame = gtk_frame_new (NULL);
1685 gtk_frame_set_shadow_type (GTK_FRAME (status_box->popup_frame),
1686 GTK_SHADOW_ETCHED_IN);
1687 gtk_container_add (GTK_CONTAINER (status_box->popup_window),
1688 status_box->popup_frame);
1690 gtk_widget_show (status_box->popup_frame);
1692 status_box->tree_view = gtk_tree_view_new ();
1693 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (status_box->tree_view));
1694 gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE);
1695 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (status_box->tree_view),
1696 FALSE);
1697 gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (status_box->tree_view),
1698 TRUE);
1699 gtk_tree_view_set_model (GTK_TREE_VIEW (status_box->tree_view),
1700 GTK_TREE_MODEL(status_box->dropdown_store));
1701 status_box->column = gtk_tree_view_column_new ();
1702 gtk_tree_view_append_column (GTK_TREE_VIEW (status_box->tree_view),
1703 status_box->column);
1704 gtk_tree_view_column_pack_start(status_box->column, icon_rend, FALSE);
1705 gtk_tree_view_column_pack_start(status_box->column, text_rend, TRUE);
1706 gtk_tree_view_column_pack_start(status_box->column, emblem_rend, FALSE);
1707 gtk_tree_view_column_set_attributes(status_box->column, icon_rend, "stock-id", ICON_STOCK_COLUMN, NULL);
1708 gtk_tree_view_column_set_attributes(status_box->column, text_rend, "markup", TEXT_COLUMN, NULL);
1709 gtk_tree_view_column_set_attributes(status_box->column, emblem_rend, "stock-id", EMBLEM_COLUMN, "visible", EMBLEM_VISIBLE_COLUMN, NULL);
1711 status_box->scrolled_window = pidgin_make_scrollable(status_box->tree_view, GTK_POLICY_NEVER, GTK_POLICY_NEVER, GTK_SHADOW_NONE, -1, -1);
1712 gtk_container_add (GTK_CONTAINER (status_box->popup_frame),
1713 status_box->scrolled_window);
1715 gtk_widget_show(status_box->tree_view);
1716 gtk_tree_view_set_search_column(GTK_TREE_VIEW(status_box->tree_view), TEXT_COLUMN);
1717 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(status_box->tree_view),
1718 pidgin_tree_view_search_equal_func, NULL, NULL);
1720 g_object_set(text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1722 status_box->icon_rend = gtk_cell_renderer_pixbuf_new();
1723 status_box->text_rend = gtk_cell_renderer_text_new();
1724 emblem_rend = gtk_cell_renderer_pixbuf_new();
1725 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), status_box->icon_rend, FALSE);
1726 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), status_box->text_rend, TRUE);
1727 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), emblem_rend, FALSE);
1728 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), status_box->icon_rend, "stock-id", ICON_STOCK_COLUMN, NULL);
1729 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), status_box->text_rend, "markup", TEXT_COLUMN, NULL);
1730 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), emblem_rend, "pixbuf", EMBLEM_COLUMN, "visible", EMBLEM_VISIBLE_COLUMN, NULL);
1731 g_object_set(status_box->text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1733 status_box->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, FALSE);
1734 status_box->editor = talkatu_editor_new();
1735 status_box->view = talkatu_editor_get_view(TALKATU_EDITOR(status_box->editor));
1736 status_box->buffer = talkatu_html_buffer_new();
1737 gtk_text_view_set_buffer(GTK_TEXT_VIEW(status_box->view), status_box->buffer);
1739 g_signal_connect(G_OBJECT(status_box->buffer), "changed",
1740 G_CALLBACK(pidgin_status_box_buffer_changed_cb),
1741 status_box);
1743 g_signal_connect(G_OBJECT(status_box->toggle_button), "key-press-event",
1744 G_CALLBACK(toggle_key_press_cb), status_box);
1745 g_signal_connect(G_OBJECT(status_box->toggle_button), "button-press-event",
1746 G_CALLBACK(toggled_cb), status_box);
1747 g_signal_connect(G_OBJECT(status_box->view), "key-press-event",
1748 G_CALLBACK(editor_remove_focus), status_box);
1750 gtk_widget_set_parent(status_box->vbox, GTK_WIDGET(status_box));
1751 gtk_widget_show_all(status_box->vbox);
1753 gtk_widget_set_parent(status_box->toggle_button, GTK_WIDGET(status_box));
1755 gtk_box_pack_start(GTK_BOX(status_box->vbox), status_box->editor, TRUE, TRUE, 0);
1757 g_signal_connect(G_OBJECT(status_box), "scroll-event", G_CALLBACK(combo_box_scroll_event_cb), NULL);
1758 g_signal_connect(G_OBJECT(status_box->popup_window), "button_release_event", G_CALLBACK(treeview_button_release_cb), status_box);
1759 g_signal_connect(G_OBJECT(status_box->popup_window), "key_press_event", G_CALLBACK(treeview_key_press_event), status_box);
1760 g_signal_connect(G_OBJECT(status_box->tree_view), "cursor-changed",
1761 G_CALLBACK(treeview_cursor_changed_cb), status_box->dropdown_store);
1763 gtk_tree_view_set_row_separator_func(GTK_TREE_VIEW(status_box->tree_view), dropdown_store_row_separator_func, NULL, NULL);
1765 status_box->token_status_account = check_active_accounts_for_identical_statuses();
1767 cache_pixbufs(status_box);
1768 pidgin_status_box_regenerate(status_box, TRUE);
1770 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed",
1771 status_box,
1772 PURPLE_CALLBACK(current_savedstatus_changed_cb),
1773 status_box);
1774 purple_signal_connect(purple_savedstatuses_get_handle(),
1775 "savedstatus-added", status_box,
1776 PURPLE_CALLBACK(saved_status_updated_cb), status_box);
1777 purple_signal_connect(purple_savedstatuses_get_handle(),
1778 "savedstatus-deleted", status_box,
1779 PURPLE_CALLBACK(saved_status_updated_cb), status_box);
1780 purple_signal_connect(purple_savedstatuses_get_handle(),
1781 "savedstatus-modified", status_box,
1782 PURPLE_CALLBACK(saved_status_updated_cb), status_box);
1783 purple_signal_connect(purple_accounts_get_handle(), "account-enabled", status_box,
1784 PURPLE_CALLBACK(account_enabled_cb),
1785 status_box);
1786 purple_signal_connect(purple_accounts_get_handle(), "account-disabled", status_box,
1787 PURPLE_CALLBACK(account_enabled_cb),
1788 status_box);
1789 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed", status_box,
1790 PURPLE_CALLBACK(account_status_changed_cb),
1791 status_box);
1793 purple_prefs_connect_callback(status_box, PIDGIN_PREFS_ROOT "/accounts/buddyicon",
1794 update_buddyicon_cb, status_box);
1798 static void
1799 pidgin_status_box_get_preferred_height(GtkWidget *widget, gint *minimum_height,
1800 gint *natural_height)
1802 gint box_min_height, box_nat_height;
1803 gint border_width = gtk_container_get_border_width(GTK_CONTAINER (widget));
1805 gtk_widget_get_preferred_height(PIDGIN_STATUS_BOX(widget)->toggle_button,
1806 minimum_height, natural_height);
1808 *minimum_height = MAX(*minimum_height, 34) + border_width * 2;
1809 *natural_height = MAX(*natural_height, 34) + border_width * 2;
1811 /* If the editor is visible, then add some additional padding */
1812 if (PIDGIN_STATUS_BOX(widget)->editor_visible) {
1813 gtk_widget_get_preferred_height(PIDGIN_STATUS_BOX(widget)->vbox,
1814 &box_min_height, &box_nat_height);
1816 if (box_min_height > 1)
1817 *minimum_height += box_min_height + border_width * 2;
1819 if (box_nat_height > 1)
1820 *natural_height += box_nat_height + border_width * 2;
1824 /* From gnome-panel */
1825 static void
1826 do_colorshift (GdkPixbuf *dest, GdkPixbuf *src, int shift)
1828 gint i, j;
1829 gint width, height, has_alpha, srcrowstride, destrowstride;
1830 guchar *target_pixels;
1831 guchar *original_pixels;
1832 guchar *pixsrc;
1833 guchar *pixdest;
1834 int val;
1835 guchar r,g,b;
1837 has_alpha = gdk_pixbuf_get_has_alpha (src);
1838 width = gdk_pixbuf_get_width (src);
1839 height = gdk_pixbuf_get_height (src);
1840 srcrowstride = gdk_pixbuf_get_rowstride (src);
1841 destrowstride = gdk_pixbuf_get_rowstride (dest);
1842 target_pixels = gdk_pixbuf_get_pixels (dest);
1843 original_pixels = gdk_pixbuf_get_pixels (src);
1845 for (i = 0; i < height; i++) {
1846 pixdest = target_pixels + i*destrowstride;
1847 pixsrc = original_pixels + i*srcrowstride;
1848 for (j = 0; j < width; j++) {
1849 r = *(pixsrc++);
1850 g = *(pixsrc++);
1851 b = *(pixsrc++);
1852 val = r + shift;
1853 *(pixdest++) = CLAMP(val, 0, 255);
1854 val = g + shift;
1855 *(pixdest++) = CLAMP(val, 0, 255);
1856 val = b + shift;
1857 *(pixdest++) = CLAMP(val, 0, 255);
1858 if (has_alpha)
1859 *(pixdest++) = *(pixsrc++);
1864 static void
1865 pidgin_status_box_size_allocate(GtkWidget *widget,
1866 GtkAllocation *allocation)
1868 PidginStatusBox *status_box = PIDGIN_STATUS_BOX(widget);
1869 GtkRequisition req = {0,0};
1870 GtkAllocation parent_alc, box_alc, icon_alc;
1871 gint border_width = gtk_container_get_border_width(GTK_CONTAINER (widget));
1873 gtk_widget_get_preferred_size(status_box->toggle_button, NULL, &req);
1874 /* Make this icon the same size as other buddy icons in the list; unless it already wants to be bigger */
1876 req.height = MAX(req.height, 34);
1877 req.height += border_width * 2;
1879 box_alc = *allocation;
1881 box_alc.width -= (border_width * 2);
1882 box_alc.height = MAX(1, ((allocation->height - req.height) - (border_width*2)));
1883 box_alc.x += border_width;
1884 box_alc.y += req.height + border_width;
1885 gtk_widget_size_allocate(status_box->vbox, &box_alc);
1887 parent_alc = *allocation;
1888 parent_alc.height = MAX(1,req.height - (border_width *2));
1889 parent_alc.width -= (border_width * 2);
1890 parent_alc.x += border_width;
1891 parent_alc.y += border_width;
1893 if (status_box->icon_box)
1895 parent_alc.width -= (parent_alc.height + border_width);
1896 icon_alc = parent_alc;
1897 icon_alc.height = MAX(1, icon_alc.height) - 2;
1898 icon_alc.width = icon_alc.height;
1899 icon_alc.x += allocation->width - (icon_alc.width + border_width + 1);
1900 icon_alc.y += 1;
1902 if (status_box->icon_size != icon_alc.height)
1904 status_box->icon_size = icon_alc.height;
1905 pidgin_status_box_redisplay_buddy_icon(status_box);
1907 gtk_widget_size_allocate(status_box->icon_box, &icon_alc);
1909 gtk_widget_size_allocate(status_box->toggle_button, &parent_alc);
1910 gtk_widget_set_allocation(GTK_WIDGET(status_box), allocation);
1913 static gboolean
1914 pidgin_status_box_draw(GtkWidget *widget, cairo_t *cr)
1916 PidginStatusBox *status_box = PIDGIN_STATUS_BOX(widget);
1917 gtk_widget_draw(status_box->toggle_button, cr);
1919 gtk_container_propagate_draw(GTK_CONTAINER(widget), status_box->vbox, cr);
1921 if (status_box->icon_box) {
1922 gtk_container_propagate_draw(GTK_CONTAINER(widget),
1923 status_box->icon_box, cr);
1925 if (status_box->icon_opaque) {
1926 GtkAllocation allocation;
1927 GtkStyleContext *context;
1929 gtk_widget_get_allocation(status_box->icon_box, &allocation);
1930 context = gtk_widget_get_style_context(widget);
1931 gtk_style_context_add_class(context, GTK_STYLE_CLASS_BUTTON);
1932 gtk_render_frame(context, cr, allocation.x-1, allocation.y-1, 34, 34);
1935 return FALSE;
1938 static void
1939 pidgin_status_box_forall(GtkContainer *container,
1940 gboolean include_internals,
1941 GtkCallback callback,
1942 gpointer callback_data)
1944 PidginStatusBox *status_box = PIDGIN_STATUS_BOX (container);
1946 if (include_internals)
1948 (* callback) (status_box->vbox, callback_data);
1949 (* callback) (status_box->toggle_button, callback_data);
1950 (* callback) (status_box->arrow, callback_data);
1951 if (status_box->icon_box)
1952 (* callback) (status_box->icon_box, callback_data);
1956 GtkWidget *
1957 pidgin_status_box_new()
1959 return g_object_new(PIDGIN_TYPE_STATUS_BOX, "account", NULL,
1960 "iconsel", TRUE, NULL);
1963 GtkWidget *
1964 pidgin_status_box_new_with_account(PurpleAccount *account)
1966 return g_object_new(PIDGIN_TYPE_STATUS_BOX, "account", account,
1967 "iconsel", TRUE, NULL);
1971 * pidgin_status_box_add:
1972 * @status_box: The status box itself.
1973 * @type: A PidginStatusBoxItemType.
1974 * @pixbuf: The icon to associate with this row in the menu. The
1975 * function will try to decide a pixbuf if none is given.
1976 * @title: The title of this item. For the primitive entries,
1977 * this is something like "Available" or "Away." For
1978 * the saved statuses, this is something like
1979 * "My favorite away message!" This should be
1980 * plaintext (non-markedup) (this function escapes it).
1981 * @desc: The secondary text for this item. This will be
1982 * placed on the row below the title, in a dimmer
1983 * font (generally gray). This text should be plaintext
1984 * (non-markedup) (this function escapes it).
1985 * @data: Data to be associated with this row in the dropdown
1986 * menu. For primitives this is the value of the
1987 * PurpleStatusPrimitive. For saved statuses this is the
1988 * creation timestamp.
1990 * Add a row to the dropdown menu.
1992 void
1993 pidgin_status_box_add(PidginStatusBox *status_box, PidginStatusBoxItemType type, GdkPixbuf *pixbuf,
1994 const char *title, const char *desc, gpointer data)
1996 GtkTreeIter iter;
1997 char *text;
1998 const char *stock = NULL;
2000 if (desc == NULL)
2002 text = g_markup_escape_text(title, -1);
2004 else
2006 const char *aa_color;
2007 gchar *escaped_title, *escaped_desc;
2009 aa_color = pidgin_get_dim_grey_string(GTK_WIDGET(status_box));
2011 escaped_title = g_markup_escape_text(title, -1);
2012 escaped_desc = g_markup_escape_text(desc, -1);
2013 text = g_strdup_printf("%s - <span color=\"%s\" size=\"smaller\">%s</span>",
2014 escaped_title,
2015 aa_color, escaped_desc);
2016 g_free(escaped_title);
2017 g_free(escaped_desc);
2020 if (!pixbuf) {
2021 PurpleStatusPrimitive prim = PURPLE_STATUS_UNSET;
2022 if (type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE) {
2023 prim = GPOINTER_TO_INT(data);
2024 } else if (type == PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR ||
2025 type == PIDGIN_STATUS_BOX_TYPE_POPULAR) {
2026 PurpleSavedStatus *saved = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
2027 if (saved) {
2028 prim = purple_savedstatus_get_primitive_type(saved);
2032 stock = pidgin_stock_id_from_status_primitive(prim);
2035 gtk_list_store_append(status_box->dropdown_store, &iter);
2036 gtk_list_store_set(status_box->dropdown_store, &iter,
2037 TYPE_COLUMN, type,
2038 ICON_STOCK_COLUMN, stock,
2039 TEXT_COLUMN, text,
2040 TITLE_COLUMN, title,
2041 DESC_COLUMN, desc,
2042 DATA_COLUMN, data,
2043 EMBLEM_VISIBLE_COLUMN, type == PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR,
2044 EMBLEM_COLUMN, GTK_STOCK_SAVE,
2045 -1);
2046 g_free(text);
2049 void
2050 pidgin_status_box_add_separator(PidginStatusBox *status_box)
2052 /* Don't do anything unless GTK actually supports
2053 * gtk_combo_box_set_row_separator_func */
2054 GtkTreeIter iter;
2056 gtk_list_store_append(status_box->dropdown_store, &iter);
2057 gtk_list_store_set(status_box->dropdown_store, &iter,
2058 TYPE_COLUMN, PIDGIN_STATUS_BOX_TYPE_SEPARATOR,
2059 -1);
2062 void
2063 pidgin_status_box_set_network_available(PidginStatusBox *status_box, gboolean available)
2065 if (!status_box)
2066 return;
2067 status_box->network_available = available;
2068 pidgin_status_box_refresh(status_box);
2071 void
2072 pidgin_status_box_set_connecting(PidginStatusBox *status_box, gboolean connecting)
2074 if (!status_box)
2075 return;
2076 status_box->connecting = connecting;
2077 pidgin_status_box_refresh(status_box);
2080 static void
2081 pixbuf_size_prepared_cb(GdkPixbufLoader *loader, int width, int height, gpointer data)
2083 int w, h;
2084 GtkIconSize icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM);
2085 gtk_icon_size_lookup(icon_size, &w, &h);
2086 if (height > width)
2087 w = width * h / height;
2088 else if (width > height)
2089 h = height * w / width;
2090 gdk_pixbuf_loader_set_size(loader, w, h);
2093 static void
2094 pidgin_status_box_redisplay_buddy_icon(PidginStatusBox *status_box)
2097 /* This is sometimes called before the box is shown, and we will not have a size */
2098 if (status_box->icon_size <= 0)
2099 return;
2101 if (status_box->buddy_icon)
2102 g_object_unref(status_box->buddy_icon);
2103 if (status_box->buddy_icon_hover)
2104 g_object_unref(status_box->buddy_icon_hover);
2105 status_box->buddy_icon = NULL;
2106 status_box->buddy_icon_hover = NULL;
2108 if (status_box->buddy_icon_img != NULL)
2110 GdkPixbufLoader *loader;
2111 GError *error = NULL;
2113 loader = gdk_pixbuf_loader_new();
2115 g_signal_connect(G_OBJECT(loader), "size-prepared", G_CALLBACK(pixbuf_size_prepared_cb), NULL);
2116 if (!gdk_pixbuf_loader_write(loader,
2117 purple_image_get_data(status_box->buddy_icon_img),
2118 purple_image_get_data_size(status_box->buddy_icon_img),
2119 &error) || error)
2121 purple_debug_warning("gtkstatusbox",
2122 "gdk_pixbuf_loader_write() failed with size=%"
2123 G_GSIZE_FORMAT ": %s", purple_image_get_data_size(
2124 status_box->buddy_icon_img),
2125 error ? error->message : "(no error message)");
2126 if (error)
2127 g_error_free(error);
2128 } else if (!gdk_pixbuf_loader_close(loader, &error) || error) {
2129 purple_debug_warning("gtkstatusbox",
2130 "gdk_pixbuf_loader_close() failed for image of "
2131 "size %" G_GSIZE_FORMAT ": %s",
2132 purple_image_get_data_size(status_box->buddy_icon_img),
2133 error ? error->message : "(no error message)");
2134 if (error)
2135 g_error_free(error);
2136 } else {
2137 GdkPixbuf *buf, *scale;
2138 int scale_width, scale_height;
2140 buf = gdk_pixbuf_loader_get_pixbuf(loader);
2141 scale_width = gdk_pixbuf_get_width(buf);
2142 scale_height = gdk_pixbuf_get_height(buf);
2143 scale = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
2144 gdk_pixbuf_fill(scale, 0x00000000);
2145 gdk_pixbuf_copy_area(buf, 0, 0, scale_width, scale_height, scale, 0, 0);
2146 if (pidgin_gdk_pixbuf_is_opaque(scale))
2147 pidgin_gdk_pixbuf_make_round(scale);
2148 status_box->buddy_icon = scale;
2151 g_object_unref(loader);
2154 if (status_box->buddy_icon == NULL)
2156 /* Show a placeholder icon */
2157 GtkIconSize icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL);
2158 status_box->buddy_icon = gtk_widget_render_icon(GTK_WIDGET(status_box),
2159 PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR,
2160 icon_size, "PidginStatusBox");
2163 if (status_box->buddy_icon != NULL) {
2164 status_box->icon_opaque = pidgin_gdk_pixbuf_is_opaque(status_box->buddy_icon);
2165 gtk_image_set_from_pixbuf(GTK_IMAGE(status_box->icon), status_box->buddy_icon);
2166 status_box->buddy_icon_hover = gdk_pixbuf_copy(status_box->buddy_icon);
2167 do_colorshift(status_box->buddy_icon_hover, status_box->buddy_icon_hover, 32);
2168 gtk_widget_queue_resize(GTK_WIDGET(status_box));
2172 void
2173 pidgin_status_box_set_buddy_icon(PidginStatusBox *status_box, PurpleImage *img)
2175 if (status_box->buddy_icon_img)
2176 g_object_unref(status_box->buddy_icon_img);
2177 status_box->buddy_icon_img = img;
2178 if (status_box->buddy_icon_img != NULL)
2179 g_object_ref(status_box->buddy_icon_img);
2181 pidgin_status_box_redisplay_buddy_icon(status_box);
2184 void
2185 pidgin_status_box_pulse_connecting(PidginStatusBox *status_box)
2187 if (!status_box)
2188 return;
2189 if (!connecting_stock_ids[++status_box->connecting_index])
2190 status_box->connecting_index = 0;
2191 pidgin_status_box_refresh(status_box);
2194 static void
2195 pidgin_status_box_pulse_typing(PidginStatusBox *status_box)
2197 if (!typing_stock_ids[++status_box->typing_index])
2198 status_box->typing_index = 0;
2199 pidgin_status_box_refresh(status_box);
2202 static void
2203 activate_currently_selected_status(PidginStatusBox *status_box)
2205 PidginStatusBoxItemType type;
2206 gpointer data;
2207 gchar *title;
2208 GtkTreeIter iter;
2209 GtkTreePath *path;
2210 char *message;
2211 PurpleSavedStatus *saved_status = NULL;
2212 gboolean changed = TRUE;
2214 path = gtk_tree_row_reference_get_path(status_box->active_row);
2215 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
2216 return;
2217 gtk_tree_path_free(path);
2219 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
2220 TYPE_COLUMN, &type,
2221 DATA_COLUMN, &data,
2222 -1);
2225 * If the currently selected status is "New..." or
2226 * "Saved..." or a popular status then do nothing.
2227 * Popular statuses are
2228 * activated elsewhere, and we update the status_box
2229 * accordingly by connecting to the savedstatus-changed
2230 * signal and then calling status_menu_refresh_iter()
2232 if (type != PIDGIN_STATUS_BOX_TYPE_PRIMITIVE)
2233 return;
2235 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
2236 TITLE_COLUMN, &title, -1);
2238 message = pidgin_status_box_get_message(status_box);
2239 if (!message || !*message)
2241 gtk_widget_hide(status_box->vbox);
2242 status_box->editor_visible = FALSE;
2243 g_free(message);
2244 message = NULL;
2247 if (status_box->account == NULL) {
2248 PurpleStatusType *acct_status_type = NULL;
2249 const char *id = NULL; /* id of acct_status_type */
2250 PurpleStatusPrimitive primitive = GPOINTER_TO_INT(data);
2251 /* Global */
2252 /* Save the newly selected status to prefs.xml and status.xml */
2254 /* Has the status really been changed? */
2255 if (status_box->token_status_account) {
2256 gint active;
2257 PurpleStatus *status;
2258 GtkTreePath *path = gtk_tree_row_reference_get_path(status_box->active_row);
2259 active = gtk_tree_path_get_indices(path)[0];
2261 gtk_tree_path_free(path);
2263 status = purple_account_get_active_status(status_box->token_status_account);
2265 acct_status_type = find_status_type_by_index(status_box->token_status_account, active);
2266 id = purple_status_type_get_id(acct_status_type);
2268 if (purple_strequal(id, purple_status_get_id(status)) &&
2269 purple_strequal(message, purple_status_get_attr_string(status, "message")))
2271 /* Selected status and previous status is the same */
2272 PurpleSavedStatus *ss = purple_savedstatus_get_current();
2273 /* Make sure that statusbox displays the correct thing.
2274 * It can get messed up if the previous selection was a
2275 * saved status that wasn't supported by this account */
2276 if ((purple_savedstatus_get_primitive_type(ss) == primitive)
2277 && purple_savedstatus_is_transient(ss)
2278 && purple_savedstatus_has_substatuses(ss))
2279 changed = FALSE;
2281 } else {
2282 saved_status = purple_savedstatus_get_current();
2283 if (purple_savedstatus_get_primitive_type(saved_status) == primitive &&
2284 !purple_savedstatus_has_substatuses(saved_status) &&
2285 purple_strequal(purple_savedstatus_get_message(saved_status), message))
2287 changed = FALSE;
2291 if (changed)
2293 /* Manually find the appropriate transient status */
2294 if (status_box->token_status_account) {
2295 GList *iter = purple_savedstatuses_get_all();
2296 GList *tmp, *active_accts = purple_accounts_get_all_active();
2298 for (; iter != NULL; iter = iter->next) {
2299 PurpleSavedStatus *ss = iter->data;
2300 const char *ss_msg = purple_savedstatus_get_message(ss);
2301 /* find a known transient status that is the same as the
2302 * new selected one */
2303 if ((purple_savedstatus_get_primitive_type(ss) == primitive) && purple_savedstatus_is_transient(ss) &&
2304 purple_savedstatus_has_substatuses(ss) && /* Must have substatuses */
2305 purple_strequal(ss_msg, message))
2307 gboolean found = FALSE;
2308 /* this status must have substatuses for all the active accts */
2309 for(tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2310 PurpleAccount *acct = tmp->data;
2311 PurpleSavedStatusSub *sub = purple_savedstatus_get_substatus(ss, acct);
2312 if (sub) {
2313 const PurpleStatusType *sub_type =
2314 purple_savedstatus_substatus_get_status_type(sub);
2315 const char *subtype_status_id = purple_status_type_get_id(sub_type);
2316 if (purple_strequal(subtype_status_id, id)) {
2317 found = TRUE;
2318 break;
2323 if (found) {
2324 saved_status = ss;
2325 break;
2330 g_list_free(active_accts);
2332 } else {
2333 /* If we've used this type+message before, lookup the transient status */
2334 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2337 /* If this type+message is unique then create a new transient saved status */
2338 if (saved_status == NULL)
2340 saved_status = purple_savedstatus_new(NULL, primitive);
2341 purple_savedstatus_set_message(saved_status, message);
2342 if (status_box->token_status_account) {
2343 GList *tmp, *active_accts = purple_accounts_get_all_active();
2344 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2345 purple_savedstatus_set_substatus(saved_status,
2346 (PurpleAccount*) tmp->data, acct_status_type, message);
2348 g_list_free(active_accts);
2352 /* Set the status for each account */
2353 purple_savedstatus_activate(saved_status);
2355 } else {
2356 /* Per-account */
2357 gint active;
2358 PurpleStatusType *status_type;
2359 PurpleStatus *status;
2360 const char *id = NULL;
2362 status = purple_account_get_active_status(status_box->account);
2364 active = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(status_box), "active"));
2366 status_type = find_status_type_by_index(status_box->account, active);
2367 id = purple_status_type_get_id(status_type);
2369 if (purple_strequal(id, purple_status_get_id(status)) &&
2370 purple_strequal(message, purple_status_get_attr_string(status, "message")))
2372 /* Selected status and previous status is the same */
2373 changed = FALSE;
2376 if (changed)
2378 if (message)
2379 purple_account_set_status(status_box->account, id,
2380 TRUE, "message", message, NULL);
2381 else
2382 purple_account_set_status(status_box->account, id,
2383 TRUE, NULL);
2385 saved_status = purple_savedstatus_get_current();
2386 if (purple_savedstatus_is_transient(saved_status))
2387 purple_savedstatus_set_substatus(saved_status, status_box->account,
2388 status_type, message);
2392 g_free(title);
2393 g_free(message);
2396 static void update_size(PidginStatusBox *status_box)
2398 if (!status_box->editor_visible)
2400 if (status_box->vbox != NULL)
2401 gtk_widget_set_size_request(status_box->vbox, -1, -1);
2402 return;
2405 gtk_widget_set_size_request(status_box->vbox, -1, -1);
2408 static void remove_typing_cb(PidginStatusBox *status_box)
2410 if (status_box->typing == 0)
2412 /* Nothing has changed, so we don't need to do anything */
2413 status_menu_refresh_iter(status_box, FALSE);
2414 return;
2417 g_source_remove(status_box->typing);
2418 status_box->typing = 0;
2420 activate_currently_selected_status(status_box);
2421 pidgin_status_box_refresh(status_box);
2424 static void pidgin_status_box_changed(PidginStatusBox *status_box)
2426 GtkTreePath *path = gtk_tree_row_reference_get_path(status_box->active_row);
2427 GtkTreeIter iter;
2428 PidginStatusBoxItemType type;
2429 gpointer data;
2430 GList *accounts = NULL, *node;
2431 int active;
2432 gboolean wastyping = FALSE;
2435 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
2436 return;
2437 active = gtk_tree_path_get_indices(path)[0];
2438 gtk_tree_path_free(path);
2439 g_object_set_data(G_OBJECT(status_box), "active", GINT_TO_POINTER(active));
2441 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
2442 TYPE_COLUMN, &type,
2443 DATA_COLUMN, &data,
2444 -1);
2445 if ((wastyping = (status_box->typing != 0)))
2446 g_source_remove(status_box->typing);
2447 status_box->typing = 0;
2449 if (gtk_widget_get_sensitive(GTK_WIDGET(status_box)))
2451 if (type == PIDGIN_STATUS_BOX_TYPE_POPULAR || type == PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR)
2453 PurpleSavedStatus *saved;
2454 saved = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
2455 g_return_if_fail(saved != NULL);
2456 purple_savedstatus_activate(saved);
2457 return;
2460 if (type == PIDGIN_STATUS_BOX_TYPE_CUSTOM)
2462 PurpleSavedStatus *saved_status;
2463 saved_status = purple_savedstatus_get_current();
2464 if (purple_savedstatus_get_primitive_type(saved_status) == PURPLE_STATUS_AVAILABLE)
2465 saved_status = purple_savedstatus_new(NULL, PURPLE_STATUS_AWAY);
2466 pidgin_status_editor_show(FALSE,
2467 purple_savedstatus_is_transient(saved_status)
2468 ? saved_status : NULL);
2469 status_menu_refresh_iter(status_box, wastyping);
2470 if (wastyping)
2471 pidgin_status_box_refresh(status_box);
2472 return;
2475 if (type == PIDGIN_STATUS_BOX_TYPE_SAVED)
2477 pidgin_status_window_show();
2478 status_menu_refresh_iter(status_box, wastyping);
2479 if (wastyping)
2480 pidgin_status_box_refresh(status_box);
2481 return;
2486 * Show the message box whenever the primitive allows for a
2487 * message attribute on any protocol that is enabled,
2488 * or our protocol, if we have account set
2490 if (status_box->account)
2491 accounts = g_list_prepend(accounts, status_box->account);
2492 else
2493 accounts = purple_accounts_get_all_active();
2494 status_box->editor_visible = FALSE;
2495 for (node = accounts; node != NULL; node = node->next)
2497 PurpleAccount *account;
2498 PurpleStatusType *status_type;
2500 account = node->data;
2501 status_type = purple_account_get_status_type_with_primitive(account, GPOINTER_TO_INT(data));
2502 if ((status_type != NULL) &&
2503 (purple_status_type_get_attr(status_type, "message") != NULL))
2505 status_box->editor_visible = TRUE;
2506 break;
2509 g_list_free(accounts);
2511 if (gtk_widget_get_sensitive(GTK_WIDGET(status_box)))
2513 if (status_box->editor_visible)
2515 GtkTextIter start, end;
2517 gtk_widget_show_all(status_box->vbox);
2518 status_box->typing = g_timeout_add_seconds(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
2519 gtk_widget_grab_focus(status_box->view);
2521 gtk_text_buffer_get_start_iter(status_box->buffer, &start);
2522 gtk_text_buffer_get_end_iter(status_box->buffer, &end);
2523 gtk_text_buffer_select_range(status_box->buffer, &start, &end);
2525 else
2527 gtk_widget_hide(status_box->vbox);
2528 activate_currently_selected_status(status_box); /* This is where we actually set the status */
2531 pidgin_status_box_refresh(status_box);
2534 static gint
2535 get_statusbox_index(PidginStatusBox *box, PurpleSavedStatus *saved_status)
2537 gint index = -1;
2539 switch (purple_savedstatus_get_primitive_type(saved_status))
2541 /* In reverse order */
2542 case PURPLE_STATUS_OFFLINE:
2543 index++;
2544 /* fall through */
2545 case PURPLE_STATUS_INVISIBLE:
2546 index++;
2547 /* fall through */
2548 case PURPLE_STATUS_UNAVAILABLE:
2549 index++;
2550 /* fall through */
2551 case PURPLE_STATUS_AWAY:
2552 index++;
2553 /* fall through */
2554 case PURPLE_STATUS_AVAILABLE:
2555 index++;
2556 break;
2557 default:
2558 break;
2561 return index;
2564 char *pidgin_status_box_get_message(PidginStatusBox *status_box)
2566 if (status_box->editor_visible)
2567 return g_strstrip(talkatu_markup_get_html(status_box->buffer, NULL));
2568 else
2569 return NULL;