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
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>
43 #include "buddyicon.h"
47 #include "savedstatuses.h"
52 #include "gtksavedstatuses.h"
53 #include "pidginstock.h"
54 #include "gtkstatusbox.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
);
88 /* A PidginStatusBoxItemType */
91 /* This is the stock-id for the icon. */
95 * This is a GdkPixbuf (the other columns are strings).
96 * This column is visible.
100 /* The text displayed on the status box. This column is visible. */
103 /* The plain-English title of this item */
106 /* A plain-English description of this item */
110 * This value depends on TYPE_COLUMN. For POPULAR types,
111 * this is the creation time. For PRIMITIVE types,
112 * this is the PurpleStatusPrimitive.
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.
125 * This column stores whether to show the emblem.
127 EMBLEM_VISIBLE_COLUMN
,
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
,
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
,
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
);
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
,
214 return status_box_type
;
218 pidgin_status_box_get_property(GObject
*object
, guint param_id
,
219 GValue
*value
, GParamSpec
*psec
)
221 PidginStatusBox
*statusbox
= PIDGIN_STATUS_BOX(object
);
225 g_value_set_pointer(value
, statusbox
->account
);
228 g_value_set_boolean(value
, statusbox
->icon_box
!= NULL
);
231 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, param_id
, psec
);
237 update_to_reflect_account_status(PidginStatusBox
*status_box
, PurpleAccount
*account
, PurpleStatus
*newstatus
)
241 const PurpleStatusType
*statustype
= NULL
;
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
))
254 if (statustype
== status_type
)
258 if (status_no
!= -1) {
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
;
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
);
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
);
295 icon_box_press_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginStatusBox
*box
)
297 if (gdk_event_triggers_context_menu((GdkEvent
*)event
)) {
298 GtkWidget
*menu_item
;
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"))
314 gtk_widget_set_sensitive(menu_item
, FALSE
);
316 gtk_menu_popup_at_pointer(GTK_MENU(box
->icon_box_menu
), (GdkEvent
*)event
);
319 choose_buddy_icon_cb(widget
, box
);
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
;
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"));
344 if ((rtmp
= strchr(tmp
, '\r')) || (rtmp
= strchr(tmp
, '\n')))
346 icon_choose_cb(tmp
, box
);
349 gtk_drag_finish(dc
, TRUE
, FALSE
, t
);
351 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
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
);
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
) ;
371 static const GtkTargetEntry dnd_targets
[] = {
372 {"text/plain", 0, 0},
373 {"text/uri-list", 0, 1},
378 setup_icon_box(PidginStatusBox
*status_box
)
382 if (status_box
->icon_box
!= NULL
)
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
);
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
);
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
);
419 gtk_drag_dest_set(status_box
->icon_box
,
420 GTK_DEST_DEFAULT_MOTION
|
421 GTK_DEST_DEFAULT_DROP
,
423 sizeof(dnd_targets
) / sizeof(GtkTargetEntry
),
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
);
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_object(&statusbox
->buddy_icon_sel
);
450 g_clear_pointer(&statusbox
->icon_box_menu
, gtk_widget_destroy
);
452 statusbox
->icon
= NULL
;
456 pidgin_status_box_set_property(GObject
*object
, guint param_id
,
457 const GValue
*value
, GParamSpec
*pspec
)
459 PidginStatusBox
*statusbox
= PIDGIN_STATUS_BOX(object
);
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
));
469 icon_spec
= purple_protocol_get_icon_spec(protocol
);
470 if (icon_spec
&& icon_spec
->format
!= NULL
)
471 setup_icon_box(statusbox
);
473 setup_icon_box(statusbox
);
476 destroy_icon_box(statusbox
);
480 statusbox
->account
= g_value_get_pointer(value
);
481 if (statusbox
->account
)
482 statusbox
->token_status_account
= NULL
;
484 statusbox
->token_status_account
= check_active_accounts_for_identical_statuses();
486 pidgin_status_box_regenerate(statusbox
, TRUE
);
490 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, param_id
, pspec
);
496 pidgin_status_box_dispose(GObject
*obj
)
498 PidginStatusBox
*statusbox
= PIDGIN_STATUS_BOX(obj
);
500 destroy_icon_box(statusbox
);
504 pidgin_status_box_finalize(GObject
*obj
)
506 PidginStatusBox
*statusbox
= PIDGIN_STATUS_BOX(obj
);
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
);
531 pidgin_status_box_child_type (GtkContainer
*container
)
533 return GTK_TYPE_WIDGET
;
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
,
564 g_param_spec_pointer("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
,
572 g_param_spec_boolean("iconsel",
574 "Whether the icon selector should be displayed or not.",
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
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
;
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
;
605 if (status_box
->typing
!= 0)
608 PidginStatusBoxItemType type
;
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
))
615 gtk_tree_path_free(path
);
617 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
621 if (type
== PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
)
622 primary
= g_strdup(purple_primitive_get_name_from_type(GPOINTER_TO_INT(data
)));
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
)));
632 primary
= g_markup_escape_text(purple_savedstatus_get_title(saved_status
), -1);
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
))
647 message
= purple_savedstatus_get_message(saved_status
);
650 tmp
= purple_markup_strip_html(message
);
651 purple_util_chrreplace(tmp
, '\n', ' ');
652 secondary
= g_markup_escape_text(tmp
, -1);
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
];
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
);
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
);
688 text
= g_strdup(primary
);
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
,
700 EMBLEM_COLUMN
, emblem
,
701 EMBLEM_VISIBLE_COLUMN
, (emblem
!= NULL
),
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
);
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
))
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?
746 status_menu_refresh_iter(PidginStatusBox
*status_box
, gboolean status_changed
)
748 PurpleSavedStatus
*saved_status
;
749 PurpleStatusPrimitive primitive
;
752 GtkTreePath
*path
= NULL
;
754 /* this function is inappropriate for ones with accounts */
755 if (status_box
->account
)
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);
783 PidginStatusBoxItemType type
;
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
,
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
))
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
))
812 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
);
818 } else if ((type
== PIDGIN_STATUS_BOX_TYPE_POPULAR
) &&
819 (GPOINTER_TO_INT(data
) == purple_savedstatus_get_creation_time(saved_status
)))
822 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
);
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
);
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
);
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
);
875 add_popular_statuses(PidginStatusBox
*statusbox
)
879 list
= purple_savedstatuses_get_popular(6);
881 /* Odd... oh well, nothing we can do about it. */
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
;
904 type
= PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR
;
906 message
= purple_savedstatus_get_message(saved
);
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
)));
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
;
932 acct1
= active_accts
->data
;
933 proto1
= purple_account_get_protocol_id(acct1
);
935 /* there's no enabled account */
939 /* start at the second account */
940 for (iter
= active_accts
->next
; iter
; iter
= iter
->next
) {
941 PurpleAccount
*acct2
= iter
->data
;
944 if (!purple_strequal(proto1
, purple_account_get_protocol_id(acct2
))) {
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
))) {
962 if (s1
!= s2
) {/* Will both be NULL if matched */
968 g_list_free(active_accts
);
974 add_account_statuses(PidginStatusBox
*status_box
, PurpleAccount
*account
)
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
))
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
),
994 GINT_TO_POINTER(prim
));
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
);
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
);
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
);
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
);
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
);
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
);
1066 if (status_box
->typing
== 0)
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
));
1079 status_menu_refresh_iter(status_box
, TRUE
);
1080 pidgin_status_box_refresh(status_box
);
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
);
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
)
1107 cache_pixbufs(PidginStatusBox
*status_box
)
1109 GtkIconSize icon_size
;
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");
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");
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
)
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
);
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
);
1163 saved_status_updated_cb(PurpleSavedStatus
*status
, PidginStatusBox
*status_box
)
1165 pidgin_status_box_regenerate(status_box
,
1166 purple_savedstatus_get_current() == status
);
1170 pidgin_status_box_list_position (PidginStatusBox
*status_box
, int *x
, int *y
, int *width
, int *height
)
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
);
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
,
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
,
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
);
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
)
1219 else if (monitor
.y
+ monitor
.height
- (*y
+ allocation
.height
) > *y
- monitor
.y
)
1221 *y
+= allocation
.height
;
1222 *height
= monitor
.y
+ monitor
.height
- *y
;
1226 *height
= *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
,
1242 popup_grab_on_window(GdkWindow
*window
, GdkEvent
*event
)
1244 GdkSeat
*seat
= gdk_event_get_seat(event
);
1245 GdkGrabStatus status
;
1247 status
= gdk_seat_grab(seat
, window
, GDK_SEAT_CAPABILITY_ALL
, TRUE
, NULL
,
1249 if (status
== GDK_GRAB_SUCCESS
) {
1258 pidgin_status_box_popup(PidginStatusBox
*box
, GdkEvent
*event
)
1260 int width
, height
, x
, y
;
1261 pidgin_status_box_list_position (box
, &x
, &y
, &width
, &height
);
1263 gtk_widget_set_size_request (box
->popup_window
, width
, height
);
1264 gtk_window_move (GTK_WINDOW (box
->popup_window
), x
, y
);
1265 gtk_widget_show(box
->popup_window
);
1266 gtk_widget_grab_focus (box
->tree_view
);
1267 if (!popup_grab_on_window(gtk_widget_get_window(box
->popup_window
), event
)) {
1268 gtk_widget_hide (box
->popup_window
);
1271 gtk_grab_add (box
->popup_window
);
1272 /*box->popup_in_progress = TRUE;*/
1273 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (box
->toggle_button
),
1276 if (box
->active_row
) {
1277 GtkTreePath
*path
= gtk_tree_row_reference_get_path(box
->active_row
);
1278 gtk_tree_view_set_cursor(GTK_TREE_VIEW(box
->tree_view
), path
, NULL
, FALSE
);
1279 gtk_tree_path_free(path
);
1284 pidgin_status_box_popdown(PidginStatusBox
*box
, GdkEvent
*event
)
1287 gtk_widget_hide(box
->popup_window
);
1288 box
->popup_in_progress
= FALSE
;
1289 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box
->toggle_button
), FALSE
);
1290 gtk_grab_remove(box
->popup_window
);
1291 seat
= gdk_event_get_seat(event
);
1292 gdk_seat_ungrab(seat
);
1296 toggle_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
, PidginStatusBox
*box
)
1298 switch (event
->keyval
) {
1299 case GDK_KEY_Return
:
1300 case GDK_KEY_KP_Enter
:
1301 case GDK_KEY_KP_Space
:
1303 if (!box
->popup_in_progress
) {
1304 pidgin_status_box_popup(box
, (GdkEvent
*)event
);
1305 box
->popup_in_progress
= TRUE
;
1307 pidgin_status_box_popdown(box
, (GdkEvent
*)event
);
1316 toggled_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginStatusBox
*box
)
1318 if (!box
->popup_in_progress
)
1319 pidgin_status_box_popup(box
, (GdkEvent
*)event
);
1321 pidgin_status_box_popdown(box
, (GdkEvent
*)event
);
1326 buddy_icon_set_cb(const char *filename
, PidginStatusBox
*box
)
1328 PurpleImage
*img
= NULL
;
1329 PurpleBuddyIconSpec
*icon_spec
= NULL
;
1332 PurpleProtocol
*protocol
=
1333 purple_protocols_find(purple_account_get_protocol_id(box
->account
));
1335 icon_spec
= purple_protocol_get_icon_spec(protocol
);
1336 if (icon_spec
&& icon_spec
->format
) {
1337 gpointer data
= NULL
;
1340 data
= pidgin_convert_buddy_icon(protocol
, filename
, &len
);
1341 img
= purple_buddy_icons_set_account_icon(box
->account
, data
, len
);
1344 * set_account_icon doesn't give us a reference, but we
1345 * unref one below (for the other code path)
1350 purple_account_set_buddy_icon_path(box
->account
, filename
);
1352 purple_account_set_bool(box
->account
, "use-global-buddyicon", (filename
!= NULL
));
1356 for (accounts
= purple_accounts_get_all(); accounts
!= NULL
; accounts
= accounts
->next
) {
1357 PurpleAccount
*account
= accounts
->data
;
1358 PurpleProtocol
*protocol
=
1359 purple_protocols_find(purple_account_get_protocol_id(account
));
1361 icon_spec
= purple_protocol_get_icon_spec(protocol
);
1362 if (icon_spec
&& icon_spec
->format
&&
1363 purple_account_get_bool(account
, "use-global-buddyicon", TRUE
)) {
1364 gpointer data
= NULL
;
1367 data
= pidgin_convert_buddy_icon(protocol
, filename
, &len
);
1368 purple_buddy_icons_set_account_icon(account
, data
, len
);
1369 purple_account_set_buddy_icon_path(account
, filename
);
1373 /* Even if no accounts were processed, load the icon that was set. */
1374 if (filename
!= NULL
)
1375 img
= purple_image_new_from_file(filename
, NULL
);
1378 pidgin_status_box_set_buddy_icon(box
, img
);
1380 g_object_unref(img
);
1384 remove_buddy_icon_cb(GtkWidget
*w
, PidginStatusBox
*box
)
1386 if (box
->account
== NULL
)
1387 /* The pref-connect callback does the actual work */
1388 purple_prefs_set_path(PIDGIN_PREFS_ROOT
"/accounts/buddyicon", NULL
);
1390 buddy_icon_set_cb(NULL
, box
);
1392 gtk_widget_destroy(box
->icon_box_menu
);
1393 box
->icon_box_menu
= NULL
;
1397 choose_buddy_icon_cb(GtkWidget
*w
, PidginStatusBox
*box
)
1399 if (box
->buddy_icon_sel
== NULL
) {
1400 box
->buddy_icon_sel
= pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtk_widget_get_toplevel(w
)), icon_choose_cb
, box
);
1402 gtk_native_dialog_show(GTK_NATIVE_DIALOG(box
->buddy_icon_sel
));
1406 icon_choose_cb(const char *filename
, gpointer data
)
1408 PidginStatusBox
*box
= data
;
1410 if (box
->account
== NULL
)
1411 /* The pref-connect callback does the actual work */
1412 purple_prefs_set_path(PIDGIN_PREFS_ROOT
"/accounts/buddyicon", filename
);
1414 buddy_icon_set_cb(filename
, box
);
1417 g_clear_object(&box
->buddy_icon_sel
);
1421 update_buddyicon_cb(const char *name
, PurplePrefType type
,
1422 gconstpointer value
, gpointer data
)
1424 buddy_icon_set_cb(value
, (PidginStatusBox
*) data
);
1428 treeview_activate_current_selection(PidginStatusBox
*status_box
, GtkTreePath
*path
, GdkEvent
*event
)
1430 if (status_box
->active_row
)
1431 gtk_tree_row_reference_free(status_box
->active_row
);
1433 status_box
->active_row
= gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box
->dropdown_store
), path
);
1434 pidgin_status_box_popdown(status_box
, event
);
1435 pidgin_status_box_changed(status_box
);
1438 static void tree_view_delete_current_selection_cb(gpointer data
)
1440 PurpleSavedStatus
*saved
;
1442 saved
= purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data
));
1443 g_return_if_fail(saved
!= NULL
);
1445 if (purple_savedstatus_get_current() != saved
)
1446 purple_savedstatus_delete_by_status(saved
);
1450 tree_view_delete_current_selection(PidginStatusBox
*status_box
, GtkTreePath
*path
, GdkEvent
*event
)
1454 PurpleSavedStatus
*saved
;
1457 if (status_box
->active_row
) {
1458 /* don't delete active status */
1459 if (gtk_tree_path_compare(path
, gtk_tree_row_reference_get_path(status_box
->active_row
)) == 0)
1463 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
, path
))
1466 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
1470 saved
= purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data
));
1471 g_return_if_fail(saved
!= NULL
);
1472 if (saved
== purple_savedstatus_get_current())
1475 msg
= g_strdup_printf(_("Are you sure you want to delete %s?"), purple_savedstatus_get_title(saved
));
1477 purple_request_action(saved
, NULL
, msg
, NULL
, 0,
1480 _("Delete"), tree_view_delete_current_selection_cb
,
1485 pidgin_status_box_popdown(status_box
, event
);
1489 treeview_button_release_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginStatusBox
*status_box
)
1491 GtkTreePath
*path
= NULL
;
1493 GtkWidget
*ewidget
= gtk_get_event_widget ((GdkEvent
*)event
);
1495 if (ewidget
!= status_box
->tree_view
) {
1496 if (ewidget
== status_box
->toggle_button
&&
1497 status_box
->popup_in_progress
&&
1498 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (status_box
->toggle_button
))) {
1499 pidgin_status_box_popdown(status_box
, (GdkEvent
*)event
);
1501 } else if (ewidget
== status_box
->toggle_button
) {
1502 status_box
->popup_in_progress
= TRUE
;
1505 /* released outside treeview */
1506 if (ewidget
!= status_box
->toggle_button
) {
1507 pidgin_status_box_popdown(status_box
, (GdkEvent
*)event
);
1514 ret
= gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (status_box
->tree_view
),
1520 return TRUE
; /* clicked outside window? */
1522 treeview_activate_current_selection(status_box
, path
, (GdkEvent
*)event
);
1523 gtk_tree_path_free (path
);
1529 treeview_key_press_event(GtkWidget
*widget
,
1530 GdkEventKey
*event
, PidginStatusBox
*box
)
1532 if (box
->popup_in_progress
) {
1533 if (event
->keyval
== GDK_KEY_Escape
) {
1534 pidgin_status_box_popdown(box
, (GdkEvent
*)event
);
1537 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(box
->tree_view
));
1541 if (gtk_tree_selection_get_selected(sel
, NULL
, &iter
)) {
1542 gboolean ret
= TRUE
;
1543 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(box
->dropdown_store
), &iter
);
1544 if (event
->keyval
== GDK_KEY_Return
) {
1545 treeview_activate_current_selection(box
, path
, (GdkEvent
*)event
);
1546 } else if (event
->keyval
== GDK_KEY_Delete
) {
1547 tree_view_delete_current_selection(box
, path
, (GdkEvent
*)event
);
1551 gtk_tree_path_free (path
);
1560 treeview_cursor_changed_cb(GtkTreeView
*treeview
, gpointer data
)
1562 GtkTreeSelection
*sel
= gtk_tree_view_get_selection (treeview
);
1563 GtkTreeModel
*model
= GTK_TREE_MODEL (data
);
1565 GtkTreePath
*cursor
;
1566 GtkTreePath
*selection
;
1569 if (gtk_tree_selection_get_selected (sel
, NULL
, &iter
)) {
1570 if ((selection
= gtk_tree_model_get_path (model
, &iter
)) == NULL
) {
1571 /* Shouldn't happen, but ignore anyway */
1575 /* I don't think this can happen, but we'll just ignore it */
1579 gtk_tree_view_get_cursor (treeview
, &cursor
, NULL
);
1580 if (cursor
== NULL
) {
1581 /* Probably won't happen in a 'cursor-changed' event? */
1582 gtk_tree_path_free (selection
);
1586 cmp
= gtk_tree_path_compare (cursor
, selection
);
1588 /* The cursor moved up without moving the selection, so move it up again */
1589 gtk_tree_path_prev (cursor
);
1590 gtk_tree_view_set_cursor (treeview
, cursor
, NULL
, FALSE
);
1591 } else if (cmp
> 0) {
1592 /* The cursor moved down without moving the selection, so move it down again */
1593 gtk_tree_path_next (cursor
);
1594 gtk_tree_view_set_cursor (treeview
, cursor
, NULL
, FALSE
);
1597 gtk_tree_path_free (selection
);
1598 gtk_tree_path_free (cursor
);
1602 pidgin_status_box_buffer_changed_cb(GtkTextBuffer
*buffer
, gpointer data
) {
1603 PidginStatusBox
*status_box
= (PidginStatusBox
*)data
;
1604 if (gtk_widget_get_sensitive(GTK_WIDGET(status_box
)))
1606 if (status_box
->typing
!= 0) {
1607 pidgin_status_box_pulse_typing(status_box
);
1608 g_source_remove(status_box
->typing
);
1610 status_box
->typing
= g_timeout_add_seconds(TYPING_TIMEOUT
, (GSourceFunc
)remove_typing_cb
, status_box
);
1612 pidgin_status_box_refresh(status_box
);
1616 pidgin_status_box_init (PidginStatusBox
*status_box
)
1618 GtkCellRenderer
*text_rend
;
1619 GtkCellRenderer
*icon_rend
;
1620 GtkCellRenderer
*emblem_rend
;
1621 GtkWidget
*toplevel
;
1622 GtkTreeSelection
*sel
;
1624 gtk_widget_set_has_window(GTK_WIDGET(status_box
), FALSE
);
1625 status_box
->editor_visible
= FALSE
;
1626 status_box
->network_available
= purple_network_is_available();
1627 status_box
->connecting
= FALSE
;
1628 status_box
->typing
= 0;
1629 status_box
->toggle_button
= gtk_toggle_button_new();
1630 status_box
->hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
1631 status_box
->cell_view
= gtk_cell_view_new();
1632 status_box
->vsep
= gtk_separator_new(GTK_ORIENTATION_VERTICAL
);
1633 status_box
->arrow
= gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON
);
1635 status_box
->store
= gtk_list_store_new(NUM_COLUMNS
, G_TYPE_INT
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
, G_TYPE_STRING
,
1636 G_TYPE_STRING
, G_TYPE_POINTER
, GDK_TYPE_PIXBUF
, G_TYPE_BOOLEAN
);
1637 status_box
->dropdown_store
= gtk_list_store_new(NUM_COLUMNS
, G_TYPE_INT
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
,
1638 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_POINTER
, G_TYPE_STRING
, G_TYPE_BOOLEAN
);
1640 gtk_cell_view_set_model(GTK_CELL_VIEW(status_box
->cell_view
), GTK_TREE_MODEL(status_box
->store
));
1641 gtk_list_store_append(status_box
->store
, &(status_box
->iter
));
1643 atk_object_set_name(gtk_widget_get_accessible(status_box
->toggle_button
), _("Status Selector"));
1645 gtk_container_add(GTK_CONTAINER(status_box
->toggle_button
), status_box
->hbox
);
1646 gtk_box_pack_start(GTK_BOX(status_box
->hbox
), status_box
->cell_view
, TRUE
, TRUE
, 0);
1647 gtk_box_pack_start(GTK_BOX(status_box
->hbox
), status_box
->vsep
, FALSE
, FALSE
, 0);
1648 gtk_box_pack_start(GTK_BOX(status_box
->hbox
), status_box
->arrow
, FALSE
, FALSE
, 0);
1649 gtk_widget_show_all(status_box
->toggle_button
);
1650 gtk_widget_set_focus_on_click(status_box
->toggle_button
, FALSE
);
1652 text_rend
= gtk_cell_renderer_text_new();
1653 icon_rend
= gtk_cell_renderer_pixbuf_new();
1654 emblem_rend
= gtk_cell_renderer_pixbuf_new();
1655 status_box
->popup_window
= gtk_window_new (GTK_WINDOW_POPUP
);
1657 toplevel
= gtk_widget_get_toplevel (GTK_WIDGET (status_box
));
1658 if (GTK_IS_WINDOW (toplevel
)) {
1659 gtk_window_set_transient_for (GTK_WINDOW (status_box
->popup_window
),
1660 GTK_WINDOW (toplevel
));
1663 gtk_window_set_resizable (GTK_WINDOW (status_box
->popup_window
), FALSE
);
1664 gtk_window_set_type_hint (GTK_WINDOW (status_box
->popup_window
),
1665 GDK_WINDOW_TYPE_HINT_POPUP_MENU
);
1666 gtk_window_set_screen (GTK_WINDOW (status_box
->popup_window
),
1667 gtk_widget_get_screen (GTK_WIDGET (status_box
)));
1668 status_box
->popup_frame
= gtk_frame_new (NULL
);
1669 gtk_frame_set_shadow_type (GTK_FRAME (status_box
->popup_frame
),
1670 GTK_SHADOW_ETCHED_IN
);
1671 gtk_container_add (GTK_CONTAINER (status_box
->popup_window
),
1672 status_box
->popup_frame
);
1674 gtk_widget_show (status_box
->popup_frame
);
1676 status_box
->tree_view
= gtk_tree_view_new ();
1677 sel
= gtk_tree_view_get_selection (GTK_TREE_VIEW (status_box
->tree_view
));
1678 gtk_tree_selection_set_mode (sel
, GTK_SELECTION_BROWSE
);
1679 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (status_box
->tree_view
),
1681 gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (status_box
->tree_view
),
1683 gtk_tree_view_set_model (GTK_TREE_VIEW (status_box
->tree_view
),
1684 GTK_TREE_MODEL(status_box
->dropdown_store
));
1685 status_box
->column
= gtk_tree_view_column_new ();
1686 gtk_tree_view_append_column (GTK_TREE_VIEW (status_box
->tree_view
),
1687 status_box
->column
);
1688 gtk_tree_view_column_pack_start(status_box
->column
, icon_rend
, FALSE
);
1689 gtk_tree_view_column_pack_start(status_box
->column
, text_rend
, TRUE
);
1690 gtk_tree_view_column_pack_start(status_box
->column
, emblem_rend
, FALSE
);
1691 gtk_tree_view_column_set_attributes(status_box
->column
, icon_rend
, "stock-id", ICON_STOCK_COLUMN
, NULL
);
1692 gtk_tree_view_column_set_attributes(status_box
->column
, text_rend
, "markup", TEXT_COLUMN
, NULL
);
1693 gtk_tree_view_column_set_attributes(status_box
->column
, emblem_rend
, "stock-id", EMBLEM_COLUMN
, "visible", EMBLEM_VISIBLE_COLUMN
, NULL
);
1695 status_box
->scrolled_window
= pidgin_make_scrollable(status_box
->tree_view
, GTK_POLICY_NEVER
, GTK_POLICY_NEVER
, GTK_SHADOW_NONE
, -1, -1);
1696 gtk_container_add (GTK_CONTAINER (status_box
->popup_frame
),
1697 status_box
->scrolled_window
);
1699 gtk_widget_show(status_box
->tree_view
);
1700 gtk_tree_view_set_search_column(GTK_TREE_VIEW(status_box
->tree_view
), TEXT_COLUMN
);
1701 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(status_box
->tree_view
),
1702 pidgin_tree_view_search_equal_func
, NULL
, NULL
);
1704 g_object_set(text_rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1706 status_box
->icon_rend
= gtk_cell_renderer_pixbuf_new();
1707 status_box
->text_rend
= gtk_cell_renderer_text_new();
1708 emblem_rend
= gtk_cell_renderer_pixbuf_new();
1709 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box
->cell_view
), status_box
->icon_rend
, FALSE
);
1710 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box
->cell_view
), status_box
->text_rend
, TRUE
);
1711 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box
->cell_view
), emblem_rend
, FALSE
);
1712 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box
->cell_view
), status_box
->icon_rend
, "stock-id", ICON_STOCK_COLUMN
, NULL
);
1713 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box
->cell_view
), status_box
->text_rend
, "markup", TEXT_COLUMN
, NULL
);
1714 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box
->cell_view
), emblem_rend
, "pixbuf", EMBLEM_COLUMN
, "visible", EMBLEM_VISIBLE_COLUMN
, NULL
);
1715 g_object_set(status_box
->text_rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1717 status_box
->vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, FALSE
);
1718 status_box
->editor
= talkatu_editor_new();
1719 status_box
->view
= talkatu_editor_get_view(TALKATU_EDITOR(status_box
->editor
));
1720 status_box
->buffer
= talkatu_html_buffer_new();
1721 gtk_text_view_set_buffer(GTK_TEXT_VIEW(status_box
->view
), status_box
->buffer
);
1723 g_signal_connect(G_OBJECT(status_box
->buffer
), "changed",
1724 G_CALLBACK(pidgin_status_box_buffer_changed_cb
),
1727 g_signal_connect(G_OBJECT(status_box
->toggle_button
), "key-press-event",
1728 G_CALLBACK(toggle_key_press_cb
), status_box
);
1729 g_signal_connect(G_OBJECT(status_box
->toggle_button
), "button-press-event",
1730 G_CALLBACK(toggled_cb
), status_box
);
1731 g_signal_connect(G_OBJECT(status_box
->view
), "key-press-event",
1732 G_CALLBACK(editor_remove_focus
), status_box
);
1734 gtk_widget_set_parent(status_box
->vbox
, GTK_WIDGET(status_box
));
1735 gtk_widget_show_all(status_box
->vbox
);
1737 gtk_widget_set_parent(status_box
->toggle_button
, GTK_WIDGET(status_box
));
1739 gtk_box_pack_start(GTK_BOX(status_box
->vbox
), status_box
->editor
, TRUE
, TRUE
, 0);
1741 g_signal_connect(G_OBJECT(status_box
), "scroll-event", G_CALLBACK(combo_box_scroll_event_cb
), NULL
);
1742 g_signal_connect(G_OBJECT(status_box
->popup_window
), "button_release_event", G_CALLBACK(treeview_button_release_cb
), status_box
);
1743 g_signal_connect(G_OBJECT(status_box
->popup_window
), "key_press_event", G_CALLBACK(treeview_key_press_event
), status_box
);
1744 g_signal_connect(G_OBJECT(status_box
->tree_view
), "cursor-changed",
1745 G_CALLBACK(treeview_cursor_changed_cb
), status_box
->dropdown_store
);
1747 gtk_tree_view_set_row_separator_func(GTK_TREE_VIEW(status_box
->tree_view
), dropdown_store_row_separator_func
, NULL
, NULL
);
1749 status_box
->token_status_account
= check_active_accounts_for_identical_statuses();
1751 cache_pixbufs(status_box
);
1752 pidgin_status_box_regenerate(status_box
, TRUE
);
1754 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed",
1756 PURPLE_CALLBACK(current_savedstatus_changed_cb
),
1758 purple_signal_connect(purple_savedstatuses_get_handle(),
1759 "savedstatus-added", status_box
,
1760 PURPLE_CALLBACK(saved_status_updated_cb
), status_box
);
1761 purple_signal_connect(purple_savedstatuses_get_handle(),
1762 "savedstatus-deleted", status_box
,
1763 PURPLE_CALLBACK(saved_status_updated_cb
), status_box
);
1764 purple_signal_connect(purple_savedstatuses_get_handle(),
1765 "savedstatus-modified", status_box
,
1766 PURPLE_CALLBACK(saved_status_updated_cb
), status_box
);
1767 purple_signal_connect(purple_accounts_get_handle(), "account-enabled", status_box
,
1768 PURPLE_CALLBACK(account_enabled_cb
),
1770 purple_signal_connect(purple_accounts_get_handle(), "account-disabled", status_box
,
1771 PURPLE_CALLBACK(account_enabled_cb
),
1773 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed", status_box
,
1774 PURPLE_CALLBACK(account_status_changed_cb
),
1777 purple_prefs_connect_callback(status_box
, PIDGIN_PREFS_ROOT
"/accounts/buddyicon",
1778 update_buddyicon_cb
, status_box
);
1783 pidgin_status_box_get_preferred_height(GtkWidget
*widget
, gint
*minimum_height
,
1784 gint
*natural_height
)
1786 gint box_min_height
, box_nat_height
;
1787 gint border_width
= gtk_container_get_border_width(GTK_CONTAINER (widget
));
1789 gtk_widget_get_preferred_height(PIDGIN_STATUS_BOX(widget
)->toggle_button
,
1790 minimum_height
, natural_height
);
1792 *minimum_height
= MAX(*minimum_height
, 34) + border_width
* 2;
1793 *natural_height
= MAX(*natural_height
, 34) + border_width
* 2;
1795 /* If the editor is visible, then add some additional padding */
1796 if (PIDGIN_STATUS_BOX(widget
)->editor_visible
) {
1797 gtk_widget_get_preferred_height(PIDGIN_STATUS_BOX(widget
)->vbox
,
1798 &box_min_height
, &box_nat_height
);
1800 if (box_min_height
> 1)
1801 *minimum_height
+= box_min_height
+ border_width
* 2;
1803 if (box_nat_height
> 1)
1804 *natural_height
+= box_nat_height
+ border_width
* 2;
1808 /* From gnome-panel */
1810 do_colorshift (GdkPixbuf
*dest
, GdkPixbuf
*src
, int shift
)
1813 gint width
, height
, has_alpha
, srcrowstride
, destrowstride
;
1814 guchar
*target_pixels
;
1815 guchar
*original_pixels
;
1821 has_alpha
= gdk_pixbuf_get_has_alpha (src
);
1822 width
= gdk_pixbuf_get_width (src
);
1823 height
= gdk_pixbuf_get_height (src
);
1824 srcrowstride
= gdk_pixbuf_get_rowstride (src
);
1825 destrowstride
= gdk_pixbuf_get_rowstride (dest
);
1826 target_pixels
= gdk_pixbuf_get_pixels (dest
);
1827 original_pixels
= gdk_pixbuf_get_pixels (src
);
1829 for (i
= 0; i
< height
; i
++) {
1830 pixdest
= target_pixels
+ i
*destrowstride
;
1831 pixsrc
= original_pixels
+ i
*srcrowstride
;
1832 for (j
= 0; j
< width
; j
++) {
1837 *(pixdest
++) = CLAMP(val
, 0, 255);
1839 *(pixdest
++) = CLAMP(val
, 0, 255);
1841 *(pixdest
++) = CLAMP(val
, 0, 255);
1843 *(pixdest
++) = *(pixsrc
++);
1849 pidgin_status_box_size_allocate(GtkWidget
*widget
,
1850 GtkAllocation
*allocation
)
1852 PidginStatusBox
*status_box
= PIDGIN_STATUS_BOX(widget
);
1853 GtkRequisition req
= {0,0};
1854 GtkAllocation parent_alc
, box_alc
, icon_alc
;
1855 gint border_width
= gtk_container_get_border_width(GTK_CONTAINER (widget
));
1857 gtk_widget_get_preferred_size(status_box
->toggle_button
, NULL
, &req
);
1858 /* Make this icon the same size as other buddy icons in the list; unless it already wants to be bigger */
1860 req
.height
= MAX(req
.height
, 34);
1861 req
.height
+= border_width
* 2;
1863 box_alc
= *allocation
;
1865 box_alc
.width
-= (border_width
* 2);
1866 box_alc
.height
= MAX(1, ((allocation
->height
- req
.height
) - (border_width
*2)));
1867 box_alc
.x
+= border_width
;
1868 box_alc
.y
+= req
.height
+ border_width
;
1869 gtk_widget_size_allocate(status_box
->vbox
, &box_alc
);
1871 parent_alc
= *allocation
;
1872 parent_alc
.height
= MAX(1,req
.height
- (border_width
*2));
1873 parent_alc
.width
-= (border_width
* 2);
1874 parent_alc
.x
+= border_width
;
1875 parent_alc
.y
+= border_width
;
1877 if (status_box
->icon_box
)
1879 parent_alc
.width
-= (parent_alc
.height
+ border_width
);
1880 icon_alc
= parent_alc
;
1881 icon_alc
.height
= MAX(1, icon_alc
.height
) - 2;
1882 icon_alc
.width
= icon_alc
.height
;
1883 icon_alc
.x
+= allocation
->width
- (icon_alc
.width
+ border_width
+ 1);
1886 if (status_box
->icon_size
!= icon_alc
.height
)
1888 status_box
->icon_size
= icon_alc
.height
;
1889 pidgin_status_box_redisplay_buddy_icon(status_box
);
1891 gtk_widget_size_allocate(status_box
->icon_box
, &icon_alc
);
1893 gtk_widget_size_allocate(status_box
->toggle_button
, &parent_alc
);
1894 gtk_widget_set_allocation(GTK_WIDGET(status_box
), allocation
);
1898 pidgin_status_box_draw(GtkWidget
*widget
, cairo_t
*cr
)
1900 PidginStatusBox
*status_box
= PIDGIN_STATUS_BOX(widget
);
1901 gtk_widget_draw(status_box
->toggle_button
, cr
);
1903 gtk_container_propagate_draw(GTK_CONTAINER(widget
), status_box
->vbox
, cr
);
1905 if (status_box
->icon_box
) {
1906 gtk_container_propagate_draw(GTK_CONTAINER(widget
),
1907 status_box
->icon_box
, cr
);
1909 if (status_box
->icon_opaque
) {
1910 GtkAllocation allocation
;
1911 GtkStyleContext
*context
;
1913 gtk_widget_get_allocation(status_box
->icon_box
, &allocation
);
1914 context
= gtk_widget_get_style_context(widget
);
1915 gtk_style_context_add_class(context
, GTK_STYLE_CLASS_BUTTON
);
1916 gtk_render_frame(context
, cr
, allocation
.x
-1, allocation
.y
-1, 34, 34);
1923 pidgin_status_box_forall(GtkContainer
*container
,
1924 gboolean include_internals
,
1925 GtkCallback callback
,
1926 gpointer callback_data
)
1928 PidginStatusBox
*status_box
= PIDGIN_STATUS_BOX (container
);
1930 if (include_internals
)
1932 (* callback
) (status_box
->vbox
, callback_data
);
1933 (* callback
) (status_box
->toggle_button
, callback_data
);
1934 (* callback
) (status_box
->arrow
, callback_data
);
1935 if (status_box
->icon_box
)
1936 (* callback
) (status_box
->icon_box
, callback_data
);
1941 pidgin_status_box_new()
1943 return g_object_new(PIDGIN_TYPE_STATUS_BOX
, "account", NULL
,
1944 "iconsel", TRUE
, NULL
);
1948 pidgin_status_box_new_with_account(PurpleAccount
*account
)
1950 return g_object_new(PIDGIN_TYPE_STATUS_BOX
, "account", account
,
1951 "iconsel", TRUE
, NULL
);
1955 * pidgin_status_box_add:
1956 * @status_box: The status box itself.
1957 * @type: A PidginStatusBoxItemType.
1958 * @pixbuf: The icon to associate with this row in the menu. The
1959 * function will try to decide a pixbuf if none is given.
1960 * @title: The title of this item. For the primitive entries,
1961 * this is something like "Available" or "Away." For
1962 * the saved statuses, this is something like
1963 * "My favorite away message!" This should be
1964 * plaintext (non-markedup) (this function escapes it).
1965 * @desc: The secondary text for this item. This will be
1966 * placed on the row below the title, in a dimmer
1967 * font (generally gray). This text should be plaintext
1968 * (non-markedup) (this function escapes it).
1969 * @data: Data to be associated with this row in the dropdown
1970 * menu. For primitives this is the value of the
1971 * PurpleStatusPrimitive. For saved statuses this is the
1972 * creation timestamp.
1974 * Add a row to the dropdown menu.
1977 pidgin_status_box_add(PidginStatusBox
*status_box
, PidginStatusBoxItemType type
, GdkPixbuf
*pixbuf
,
1978 const char *title
, const char *desc
, gpointer data
)
1982 const char *stock
= NULL
;
1986 text
= g_markup_escape_text(title
, -1);
1990 const char *aa_color
;
1991 gchar
*escaped_title
, *escaped_desc
;
1993 aa_color
= pidgin_get_dim_grey_string(GTK_WIDGET(status_box
));
1995 escaped_title
= g_markup_escape_text(title
, -1);
1996 escaped_desc
= g_markup_escape_text(desc
, -1);
1997 text
= g_strdup_printf("%s - <span color=\"%s\" size=\"smaller\">%s</span>",
1999 aa_color
, escaped_desc
);
2000 g_free(escaped_title
);
2001 g_free(escaped_desc
);
2005 PurpleStatusPrimitive prim
= PURPLE_STATUS_UNSET
;
2006 if (type
== PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
) {
2007 prim
= GPOINTER_TO_INT(data
);
2008 } else if (type
== PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR
||
2009 type
== PIDGIN_STATUS_BOX_TYPE_POPULAR
) {
2010 PurpleSavedStatus
*saved
= purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data
));
2012 prim
= purple_savedstatus_get_primitive_type(saved
);
2016 stock
= pidgin_stock_id_from_status_primitive(prim
);
2019 gtk_list_store_append(status_box
->dropdown_store
, &iter
);
2020 gtk_list_store_set(status_box
->dropdown_store
, &iter
,
2022 ICON_STOCK_COLUMN
, stock
,
2024 TITLE_COLUMN
, title
,
2027 EMBLEM_VISIBLE_COLUMN
, type
== PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR
,
2028 EMBLEM_COLUMN
, GTK_STOCK_SAVE
,
2034 pidgin_status_box_add_separator(PidginStatusBox
*status_box
)
2036 /* Don't do anything unless GTK actually supports
2037 * gtk_combo_box_set_row_separator_func */
2040 gtk_list_store_append(status_box
->dropdown_store
, &iter
);
2041 gtk_list_store_set(status_box
->dropdown_store
, &iter
,
2042 TYPE_COLUMN
, PIDGIN_STATUS_BOX_TYPE_SEPARATOR
,
2047 pidgin_status_box_set_network_available(PidginStatusBox
*status_box
, gboolean available
)
2051 status_box
->network_available
= available
;
2052 pidgin_status_box_refresh(status_box
);
2056 pidgin_status_box_set_connecting(PidginStatusBox
*status_box
, gboolean connecting
)
2060 status_box
->connecting
= connecting
;
2061 pidgin_status_box_refresh(status_box
);
2065 pixbuf_size_prepared_cb(GdkPixbufLoader
*loader
, int width
, int height
, gpointer data
)
2068 GtkIconSize icon_size
= gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM
);
2069 gtk_icon_size_lookup(icon_size
, &w
, &h
);
2071 w
= width
* h
/ height
;
2072 else if (width
> height
)
2073 h
= height
* w
/ width
;
2074 gdk_pixbuf_loader_set_size(loader
, w
, h
);
2078 pidgin_status_box_redisplay_buddy_icon(PidginStatusBox
*status_box
)
2081 /* This is sometimes called before the box is shown, and we will not have a size */
2082 if (status_box
->icon_size
<= 0)
2085 if (status_box
->buddy_icon
)
2086 g_object_unref(status_box
->buddy_icon
);
2087 if (status_box
->buddy_icon_hover
)
2088 g_object_unref(status_box
->buddy_icon_hover
);
2089 status_box
->buddy_icon
= NULL
;
2090 status_box
->buddy_icon_hover
= NULL
;
2092 if (status_box
->buddy_icon_img
!= NULL
)
2094 GdkPixbufLoader
*loader
;
2095 GError
*error
= NULL
;
2097 loader
= gdk_pixbuf_loader_new();
2099 g_signal_connect(G_OBJECT(loader
), "size-prepared", G_CALLBACK(pixbuf_size_prepared_cb
), NULL
);
2100 if (!gdk_pixbuf_loader_write(loader
,
2101 purple_image_get_data(status_box
->buddy_icon_img
),
2102 purple_image_get_data_size(status_box
->buddy_icon_img
),
2105 purple_debug_warning("gtkstatusbox",
2106 "gdk_pixbuf_loader_write() failed with size=%"
2107 G_GSIZE_FORMAT
": %s", purple_image_get_data_size(
2108 status_box
->buddy_icon_img
),
2109 error
? error
->message
: "(no error message)");
2111 g_error_free(error
);
2112 } else if (!gdk_pixbuf_loader_close(loader
, &error
) || error
) {
2113 purple_debug_warning("gtkstatusbox",
2114 "gdk_pixbuf_loader_close() failed for image of "
2115 "size %" G_GSIZE_FORMAT
": %s",
2116 purple_image_get_data_size(status_box
->buddy_icon_img
),
2117 error
? error
->message
: "(no error message)");
2119 g_error_free(error
);
2121 GdkPixbuf
*buf
, *scale
;
2122 int scale_width
, scale_height
;
2124 buf
= gdk_pixbuf_loader_get_pixbuf(loader
);
2125 scale_width
= gdk_pixbuf_get_width(buf
);
2126 scale_height
= gdk_pixbuf_get_height(buf
);
2127 scale
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, TRUE
, 8, scale_width
, scale_height
);
2128 gdk_pixbuf_fill(scale
, 0x00000000);
2129 gdk_pixbuf_copy_area(buf
, 0, 0, scale_width
, scale_height
, scale
, 0, 0);
2130 if (pidgin_gdk_pixbuf_is_opaque(scale
))
2131 pidgin_gdk_pixbuf_make_round(scale
);
2132 status_box
->buddy_icon
= scale
;
2135 g_object_unref(loader
);
2138 if (status_box
->buddy_icon
== NULL
)
2140 /* Show a placeholder icon */
2141 GtkIconSize icon_size
= gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL
);
2142 status_box
->buddy_icon
= gtk_widget_render_icon(GTK_WIDGET(status_box
),
2143 PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR
,
2144 icon_size
, "PidginStatusBox");
2147 if (status_box
->buddy_icon
!= NULL
) {
2148 status_box
->icon_opaque
= pidgin_gdk_pixbuf_is_opaque(status_box
->buddy_icon
);
2149 gtk_image_set_from_pixbuf(GTK_IMAGE(status_box
->icon
), status_box
->buddy_icon
);
2150 status_box
->buddy_icon_hover
= gdk_pixbuf_copy(status_box
->buddy_icon
);
2151 do_colorshift(status_box
->buddy_icon_hover
, status_box
->buddy_icon_hover
, 32);
2152 gtk_widget_queue_resize(GTK_WIDGET(status_box
));
2157 pidgin_status_box_set_buddy_icon(PidginStatusBox
*status_box
, PurpleImage
*img
)
2159 if (status_box
->buddy_icon_img
)
2160 g_object_unref(status_box
->buddy_icon_img
);
2161 status_box
->buddy_icon_img
= img
;
2162 if (status_box
->buddy_icon_img
!= NULL
)
2163 g_object_ref(status_box
->buddy_icon_img
);
2165 pidgin_status_box_redisplay_buddy_icon(status_box
);
2169 pidgin_status_box_pulse_connecting(PidginStatusBox
*status_box
)
2173 if (!connecting_stock_ids
[++status_box
->connecting_index
])
2174 status_box
->connecting_index
= 0;
2175 pidgin_status_box_refresh(status_box
);
2179 pidgin_status_box_pulse_typing(PidginStatusBox
*status_box
)
2181 if (!typing_stock_ids
[++status_box
->typing_index
])
2182 status_box
->typing_index
= 0;
2183 pidgin_status_box_refresh(status_box
);
2187 activate_currently_selected_status(PidginStatusBox
*status_box
)
2189 PidginStatusBoxItemType type
;
2195 PurpleSavedStatus
*saved_status
= NULL
;
2196 gboolean changed
= TRUE
;
2198 path
= gtk_tree_row_reference_get_path(status_box
->active_row
);
2199 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
, path
))
2201 gtk_tree_path_free(path
);
2203 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
2209 * If the currently selected status is "New..." or
2210 * "Saved..." or a popular status then do nothing.
2211 * Popular statuses are
2212 * activated elsewhere, and we update the status_box
2213 * accordingly by connecting to the savedstatus-changed
2214 * signal and then calling status_menu_refresh_iter()
2216 if (type
!= PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
)
2219 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
2220 TITLE_COLUMN
, &title
, -1);
2222 message
= pidgin_status_box_get_message(status_box
);
2223 if (!message
|| !*message
)
2225 gtk_widget_hide(status_box
->vbox
);
2226 status_box
->editor_visible
= FALSE
;
2231 if (status_box
->account
== NULL
) {
2232 PurpleStatusType
*acct_status_type
= NULL
;
2233 const char *id
= NULL
; /* id of acct_status_type */
2234 PurpleStatusPrimitive primitive
= GPOINTER_TO_INT(data
);
2236 /* Save the newly selected status to prefs.xml and status.xml */
2238 /* Has the status really been changed? */
2239 if (status_box
->token_status_account
) {
2241 PurpleStatus
*status
;
2242 GtkTreePath
*path
= gtk_tree_row_reference_get_path(status_box
->active_row
);
2243 active
= gtk_tree_path_get_indices(path
)[0];
2245 gtk_tree_path_free(path
);
2247 status
= purple_account_get_active_status(status_box
->token_status_account
);
2249 acct_status_type
= find_status_type_by_index(status_box
->token_status_account
, active
);
2250 id
= purple_status_type_get_id(acct_status_type
);
2252 if (purple_strequal(id
, purple_status_get_id(status
)) &&
2253 purple_strequal(message
, purple_status_get_attr_string(status
, "message")))
2255 /* Selected status and previous status is the same */
2256 PurpleSavedStatus
*ss
= purple_savedstatus_get_current();
2257 /* Make sure that statusbox displays the correct thing.
2258 * It can get messed up if the previous selection was a
2259 * saved status that wasn't supported by this account */
2260 if ((purple_savedstatus_get_primitive_type(ss
) == primitive
)
2261 && purple_savedstatus_is_transient(ss
)
2262 && purple_savedstatus_has_substatuses(ss
))
2266 saved_status
= purple_savedstatus_get_current();
2267 if (purple_savedstatus_get_primitive_type(saved_status
) == primitive
&&
2268 !purple_savedstatus_has_substatuses(saved_status
) &&
2269 purple_strequal(purple_savedstatus_get_message(saved_status
), message
))
2277 /* Manually find the appropriate transient status */
2278 if (status_box
->token_status_account
) {
2279 GList
*iter
= purple_savedstatuses_get_all();
2280 GList
*tmp
, *active_accts
= purple_accounts_get_all_active();
2282 for (; iter
!= NULL
; iter
= iter
->next
) {
2283 PurpleSavedStatus
*ss
= iter
->data
;
2284 const char *ss_msg
= purple_savedstatus_get_message(ss
);
2285 /* find a known transient status that is the same as the
2286 * new selected one */
2287 if ((purple_savedstatus_get_primitive_type(ss
) == primitive
) && purple_savedstatus_is_transient(ss
) &&
2288 purple_savedstatus_has_substatuses(ss
) && /* Must have substatuses */
2289 purple_strequal(ss_msg
, message
))
2291 gboolean found
= FALSE
;
2292 /* this status must have substatuses for all the active accts */
2293 for(tmp
= active_accts
; tmp
!= NULL
; tmp
= tmp
->next
) {
2294 PurpleAccount
*acct
= tmp
->data
;
2295 PurpleSavedStatusSub
*sub
= purple_savedstatus_get_substatus(ss
, acct
);
2297 const PurpleStatusType
*sub_type
=
2298 purple_savedstatus_substatus_get_status_type(sub
);
2299 const char *subtype_status_id
= purple_status_type_get_id(sub_type
);
2300 if (purple_strequal(subtype_status_id
, id
)) {
2314 g_list_free(active_accts
);
2317 /* If we've used this type+message before, lookup the transient status */
2318 saved_status
= purple_savedstatus_find_transient_by_type_and_message(primitive
, message
);
2321 /* If this type+message is unique then create a new transient saved status */
2322 if (saved_status
== NULL
)
2324 saved_status
= purple_savedstatus_new(NULL
, primitive
);
2325 purple_savedstatus_set_message(saved_status
, message
);
2326 if (status_box
->token_status_account
) {
2327 GList
*tmp
, *active_accts
= purple_accounts_get_all_active();
2328 for (tmp
= active_accts
; tmp
!= NULL
; tmp
= tmp
->next
) {
2329 purple_savedstatus_set_substatus(saved_status
,
2330 (PurpleAccount
*) tmp
->data
, acct_status_type
, message
);
2332 g_list_free(active_accts
);
2336 /* Set the status for each account */
2337 purple_savedstatus_activate(saved_status
);
2342 PurpleStatusType
*status_type
;
2343 PurpleStatus
*status
;
2344 const char *id
= NULL
;
2346 status
= purple_account_get_active_status(status_box
->account
);
2348 active
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(status_box
), "active"));
2350 status_type
= find_status_type_by_index(status_box
->account
, active
);
2351 id
= purple_status_type_get_id(status_type
);
2353 if (purple_strequal(id
, purple_status_get_id(status
)) &&
2354 purple_strequal(message
, purple_status_get_attr_string(status
, "message")))
2356 /* Selected status and previous status is the same */
2363 purple_account_set_status(status_box
->account
, id
,
2364 TRUE
, "message", message
, NULL
);
2366 purple_account_set_status(status_box
->account
, id
,
2369 saved_status
= purple_savedstatus_get_current();
2370 if (purple_savedstatus_is_transient(saved_status
))
2371 purple_savedstatus_set_substatus(saved_status
, status_box
->account
,
2372 status_type
, message
);
2380 static void update_size(PidginStatusBox
*status_box
)
2382 if (!status_box
->editor_visible
)
2384 if (status_box
->vbox
!= NULL
)
2385 gtk_widget_set_size_request(status_box
->vbox
, -1, -1);
2389 gtk_widget_set_size_request(status_box
->vbox
, -1, -1);
2392 static void remove_typing_cb(PidginStatusBox
*status_box
)
2394 if (status_box
->typing
== 0)
2396 /* Nothing has changed, so we don't need to do anything */
2397 status_menu_refresh_iter(status_box
, FALSE
);
2401 g_source_remove(status_box
->typing
);
2402 status_box
->typing
= 0;
2404 activate_currently_selected_status(status_box
);
2405 pidgin_status_box_refresh(status_box
);
2408 static void pidgin_status_box_changed(PidginStatusBox
*status_box
)
2410 GtkTreePath
*path
= gtk_tree_row_reference_get_path(status_box
->active_row
);
2412 PidginStatusBoxItemType type
;
2414 GList
*accounts
= NULL
, *node
;
2416 gboolean wastyping
= FALSE
;
2419 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
, path
))
2421 active
= gtk_tree_path_get_indices(path
)[0];
2422 gtk_tree_path_free(path
);
2423 g_object_set_data(G_OBJECT(status_box
), "active", GINT_TO_POINTER(active
));
2425 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
2429 if ((wastyping
= (status_box
->typing
!= 0)))
2430 g_source_remove(status_box
->typing
);
2431 status_box
->typing
= 0;
2433 if (gtk_widget_get_sensitive(GTK_WIDGET(status_box
)))
2435 if (type
== PIDGIN_STATUS_BOX_TYPE_POPULAR
|| type
== PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR
)
2437 PurpleSavedStatus
*saved
;
2438 saved
= purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data
));
2439 g_return_if_fail(saved
!= NULL
);
2440 purple_savedstatus_activate(saved
);
2444 if (type
== PIDGIN_STATUS_BOX_TYPE_CUSTOM
)
2446 PurpleSavedStatus
*saved_status
;
2447 saved_status
= purple_savedstatus_get_current();
2448 if (purple_savedstatus_get_primitive_type(saved_status
) == PURPLE_STATUS_AVAILABLE
)
2449 saved_status
= purple_savedstatus_new(NULL
, PURPLE_STATUS_AWAY
);
2450 pidgin_status_editor_show(FALSE
,
2451 purple_savedstatus_is_transient(saved_status
)
2452 ? saved_status
: NULL
);
2453 status_menu_refresh_iter(status_box
, wastyping
);
2455 pidgin_status_box_refresh(status_box
);
2459 if (type
== PIDGIN_STATUS_BOX_TYPE_SAVED
)
2461 pidgin_status_window_show();
2462 status_menu_refresh_iter(status_box
, wastyping
);
2464 pidgin_status_box_refresh(status_box
);
2470 * Show the message box whenever the primitive allows for a
2471 * message attribute on any protocol that is enabled,
2472 * or our protocol, if we have account set
2474 if (status_box
->account
)
2475 accounts
= g_list_prepend(accounts
, status_box
->account
);
2477 accounts
= purple_accounts_get_all_active();
2478 status_box
->editor_visible
= FALSE
;
2479 for (node
= accounts
; node
!= NULL
; node
= node
->next
)
2481 PurpleAccount
*account
;
2482 PurpleStatusType
*status_type
;
2484 account
= node
->data
;
2485 status_type
= purple_account_get_status_type_with_primitive(account
, GPOINTER_TO_INT(data
));
2486 if ((status_type
!= NULL
) &&
2487 (purple_status_type_get_attr(status_type
, "message") != NULL
))
2489 status_box
->editor_visible
= TRUE
;
2493 g_list_free(accounts
);
2495 if (gtk_widget_get_sensitive(GTK_WIDGET(status_box
)))
2497 if (status_box
->editor_visible
)
2499 GtkTextIter start
, end
;
2501 gtk_widget_show_all(status_box
->vbox
);
2502 status_box
->typing
= g_timeout_add_seconds(TYPING_TIMEOUT
, (GSourceFunc
)remove_typing_cb
, status_box
);
2503 gtk_widget_grab_focus(status_box
->view
);
2505 gtk_text_buffer_get_start_iter(status_box
->buffer
, &start
);
2506 gtk_text_buffer_get_end_iter(status_box
->buffer
, &end
);
2507 gtk_text_buffer_select_range(status_box
->buffer
, &start
, &end
);
2511 gtk_widget_hide(status_box
->vbox
);
2512 activate_currently_selected_status(status_box
); /* This is where we actually set the status */
2515 pidgin_status_box_refresh(status_box
);
2519 get_statusbox_index(PidginStatusBox
*box
, PurpleSavedStatus
*saved_status
)
2523 switch (purple_savedstatus_get_primitive_type(saved_status
))
2525 /* In reverse order */
2526 case PURPLE_STATUS_OFFLINE
:
2529 case PURPLE_STATUS_INVISIBLE
:
2532 case PURPLE_STATUS_UNAVAILABLE
:
2535 case PURPLE_STATUS_AWAY
:
2538 case PURPLE_STATUS_AVAILABLE
:
2548 char *pidgin_status_box_get_message(PidginStatusBox
*status_box
)
2550 if (status_box
->editor_visible
)
2551 return g_strstrip(talkatu_markup_get_html(status_box
->buffer
, NULL
));