Merged pidgin/main into default
[pidgin-git.git] / pidgin / gtkstatusbox.c
blobc879728e91e3491cdf883c72d94c73f0d7ab85d0
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"
57 #include "gtk3compat.h"
59 /* Timeout for typing notifications in seconds */
60 #define TYPING_TIMEOUT 4
62 static void webview_changed_cb(PidginWebView *webview, void *data);
63 static void webview_format_changed_cb(PidginWebView *webview, PidginWebViewButtons buttons, void *data);
64 static void remove_typing_cb(PidginStatusBox *box);
65 static void update_size (PidginStatusBox *box);
66 static gint get_statusbox_index(PidginStatusBox *box, PurpleSavedStatus *saved_status);
67 static PurpleAccount* check_active_accounts_for_identical_statuses(void);
69 static void pidgin_status_box_pulse_typing(PidginStatusBox *status_box);
70 static void pidgin_status_box_refresh(PidginStatusBox *status_box);
71 static void status_menu_refresh_iter(PidginStatusBox *status_box, gboolean status_changed);
72 static void pidgin_status_box_regenerate(PidginStatusBox *status_box, gboolean status_changed);
73 static void pidgin_status_box_changed(PidginStatusBox *box);
74 static void pidgin_status_box_get_preferred_height (GtkWidget *widget,
75 gint *minimum_height, gint *natural_height);
76 static gboolean pidgin_status_box_draw (GtkWidget *widget, cairo_t *cr);
77 static void pidgin_status_box_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
78 static void pidgin_status_box_redisplay_buddy_icon(PidginStatusBox *status_box);
79 static void pidgin_status_box_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data);
80 static void pidgin_status_box_popup(PidginStatusBox *box, GdkEvent *event);
81 static void pidgin_status_box_popdown(PidginStatusBox *box, GdkEvent *event);
83 static void do_colorshift (GdkPixbuf *dest, GdkPixbuf *src, int shift);
84 static void icon_choose_cb(const char *filename, gpointer data);
85 static void remove_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box);
86 static void choose_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box);
88 enum {
89 /* A PidginStatusBoxItemType */
90 TYPE_COLUMN,
92 /* This is the stock-id for the icon. */
93 ICON_STOCK_COLUMN,
96 * This is a GdkPixbuf (the other columns are strings).
97 * This column is visible.
99 ICON_COLUMN,
101 /* The text displayed on the status box. This column is visible. */
102 TEXT_COLUMN,
104 /* The plain-English title of this item */
105 TITLE_COLUMN,
107 /* A plain-English description of this item */
108 DESC_COLUMN,
111 * This value depends on TYPE_COLUMN. For POPULAR types,
112 * this is the creation time. For PRIMITIVE types,
113 * this is the PurpleStatusPrimitive.
115 DATA_COLUMN,
118 * This column stores the GdkPixbuf for the status emblem. Currently only 'saved' is stored.
119 * In the GtkTreeModel for the dropdown, this is the stock-id (gchararray), and for the
120 * GtkTreeModel for the cell_view (for the account-specific statusbox), this is the protocol icon
121 * (GdkPixbuf) of the account.
123 EMBLEM_COLUMN,
126 * This column stores whether to show the emblem.
128 EMBLEM_VISIBLE_COLUMN,
130 NUM_COLUMNS
133 enum {
134 PROP_0,
135 PROP_ACCOUNT,
136 PROP_ICON_SEL,
139 static char *typing_stock_ids[7] = {
140 PIDGIN_STOCK_ANIMATION_TYPING0,
141 PIDGIN_STOCK_ANIMATION_TYPING1,
142 PIDGIN_STOCK_ANIMATION_TYPING2,
143 PIDGIN_STOCK_ANIMATION_TYPING3,
144 PIDGIN_STOCK_ANIMATION_TYPING4,
145 NULL
148 static char *connecting_stock_ids[] = {
149 PIDGIN_STOCK_ANIMATION_CONNECT0,
150 PIDGIN_STOCK_ANIMATION_CONNECT1,
151 PIDGIN_STOCK_ANIMATION_CONNECT2,
152 PIDGIN_STOCK_ANIMATION_CONNECT3,
153 PIDGIN_STOCK_ANIMATION_CONNECT4,
154 PIDGIN_STOCK_ANIMATION_CONNECT5,
155 PIDGIN_STOCK_ANIMATION_CONNECT6,
156 PIDGIN_STOCK_ANIMATION_CONNECT7,
157 PIDGIN_STOCK_ANIMATION_CONNECT8,
158 PIDGIN_STOCK_ANIMATION_CONNECT9,
159 PIDGIN_STOCK_ANIMATION_CONNECT10,
160 PIDGIN_STOCK_ANIMATION_CONNECT11,
161 PIDGIN_STOCK_ANIMATION_CONNECT12,
162 PIDGIN_STOCK_ANIMATION_CONNECT13,
163 PIDGIN_STOCK_ANIMATION_CONNECT14,
164 PIDGIN_STOCK_ANIMATION_CONNECT15,
165 PIDGIN_STOCK_ANIMATION_CONNECT16,
166 PIDGIN_STOCK_ANIMATION_CONNECT17,
167 PIDGIN_STOCK_ANIMATION_CONNECT18,
168 PIDGIN_STOCK_ANIMATION_CONNECT19,
169 PIDGIN_STOCK_ANIMATION_CONNECT20,
170 PIDGIN_STOCK_ANIMATION_CONNECT21,
171 PIDGIN_STOCK_ANIMATION_CONNECT22,
172 PIDGIN_STOCK_ANIMATION_CONNECT23,
173 PIDGIN_STOCK_ANIMATION_CONNECT24,
174 PIDGIN_STOCK_ANIMATION_CONNECT25,
175 PIDGIN_STOCK_ANIMATION_CONNECT26,
176 PIDGIN_STOCK_ANIMATION_CONNECT27,
177 PIDGIN_STOCK_ANIMATION_CONNECT28,
178 PIDGIN_STOCK_ANIMATION_CONNECT29,
179 PIDGIN_STOCK_ANIMATION_CONNECT30,
180 NULL
183 static GtkContainerClass *parent_class = NULL;
185 static void pidgin_status_box_class_init (PidginStatusBoxClass *klass);
186 static void pidgin_status_box_init (PidginStatusBox *status_box);
188 GType
189 pidgin_status_box_get_type (void)
191 static GType status_box_type = 0;
193 if (!status_box_type)
195 static const GTypeInfo status_box_info =
197 sizeof (PidginStatusBoxClass),
198 NULL, /* base_init */
199 NULL, /* base_finalize */
200 (GClassInitFunc) pidgin_status_box_class_init,
201 NULL, /* class_finalize */
202 NULL, /* class_data */
203 sizeof (PidginStatusBox),
205 (GInstanceInitFunc) pidgin_status_box_init,
206 NULL /* value_table */
209 status_box_type = g_type_register_static(GTK_TYPE_CONTAINER,
210 "PidginStatusBox",
211 &status_box_info,
215 return status_box_type;
218 static void
219 pidgin_status_box_get_property(GObject *object, guint param_id,
220 GValue *value, GParamSpec *psec)
222 PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(object);
224 switch (param_id) {
225 case PROP_ACCOUNT:
226 g_value_set_pointer(value, statusbox->account);
227 break;
228 case PROP_ICON_SEL:
229 g_value_set_boolean(value, statusbox->icon_box != NULL);
230 break;
231 default:
232 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, psec);
233 break;
237 static void
238 update_to_reflect_account_status(PidginStatusBox *status_box, PurpleAccount *account, PurpleStatus *newstatus)
240 GList *l;
241 int status_no = -1;
242 const PurpleStatusType *statustype = NULL;
243 const char *message;
245 statustype = purple_status_type_find_with_id((GList *)purple_account_get_status_types(account),
246 (char *)purple_status_type_get_id(purple_status_get_status_type(newstatus)));
248 for (l = purple_account_get_status_types(account); l != NULL; l = l->next) {
249 PurpleStatusType *status_type = (PurpleStatusType *)l->data;
251 if (!purple_status_type_is_user_settable(status_type) ||
252 purple_status_type_is_independent(status_type))
253 continue;
254 status_no++;
255 if (statustype == status_type)
256 break;
259 #if 0
260 /* TODO WebKit: Doesn't do this? */
261 pidgin_webview_set_populate_primary_clipboard(
262 PIDGIN_WEBVIEW(status_box->webview), TRUE);
263 #endif
265 if (status_no != -1) {
266 GtkTreePath *path;
267 gtk_widget_set_sensitive(GTK_WIDGET(status_box), FALSE);
268 path = gtk_tree_path_new_from_indices(status_no, -1);
269 if (status_box->active_row)
270 gtk_tree_row_reference_free(status_box->active_row);
271 status_box->active_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box->dropdown_store), path);
272 gtk_tree_path_free(path);
274 message = purple_status_get_attr_string(newstatus, "message");
276 if (!message || !*message)
278 gtk_widget_hide(status_box->vbox);
279 status_box->webview_visible = FALSE;
281 else
283 gtk_widget_show_all(status_box->vbox);
284 status_box->webview_visible = TRUE;
285 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(status_box->webview), message);
287 gtk_widget_set_sensitive(GTK_WIDGET(status_box), TRUE);
288 pidgin_status_box_refresh(status_box);
292 static void
293 account_status_changed_cb(PurpleAccount *account, PurpleStatus *oldstatus, PurpleStatus *newstatus, PidginStatusBox *status_box)
295 if (status_box->account == account)
296 update_to_reflect_account_status(status_box, account, newstatus);
297 else if (status_box->token_status_account == account)
298 status_menu_refresh_iter(status_box, TRUE);
301 static gboolean
302 icon_box_press_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
304 if (event->button == 3) {
305 GtkWidget *menu_item;
306 const char *path;
308 if (box->icon_box_menu)
309 gtk_widget_destroy(box->icon_box_menu);
311 box->icon_box_menu = gtk_menu_new();
313 pidgin_new_menu_item(box->icon_box_menu,
314 _("Select Buddy Icon"), GTK_STOCK_ADD,
315 G_CALLBACK(choose_buddy_icon_cb), box);
317 menu_item = pidgin_new_menu_item(box->icon_box_menu, _("Remove"), GTK_STOCK_REMOVE,
318 G_CALLBACK(remove_buddy_icon_cb), box);
319 if (!(path = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon"))
320 || !*path)
321 gtk_widget_set_sensitive(menu_item, FALSE);
323 gtk_menu_popup(GTK_MENU(box->icon_box_menu), NULL, NULL, NULL, NULL,
324 event->button, event->time);
326 } else {
327 choose_buddy_icon_cb(widget, box);
329 return FALSE;
332 static void
333 icon_box_dnd_cb(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
334 GtkSelectionData *sd, guint info, guint t, PidginStatusBox *box)
336 gchar *name = (gchar *) gtk_selection_data_get_data(sd);
338 if ((gtk_selection_data_get_length(sd) >= 0)
339 && (gtk_selection_data_get_format(sd) == 8)) {
340 /* Well, it looks like the drag event was cool.
341 * Let's do something with it */
342 if (!g_ascii_strncasecmp(name, "file://", 7)) {
343 GError *converr = NULL;
344 gchar *tmp, *rtmp;
346 if(!(tmp = g_filename_from_uri(name, NULL, &converr))) {
347 purple_debug(PURPLE_DEBUG_ERROR, "buddyicon", "%s\n",
348 (converr ? converr->message :
349 "g_filename_from_uri error"));
350 return;
352 if ((rtmp = strchr(tmp, '\r')) || (rtmp = strchr(tmp, '\n')))
353 *rtmp = '\0';
354 icon_choose_cb(tmp, box);
355 g_free(tmp);
357 gtk_drag_finish(dc, TRUE, FALSE, t);
359 gtk_drag_finish(dc, FALSE, FALSE, t);
362 static gboolean
363 icon_box_enter_cb(GtkWidget *widget, GdkEventCrossing *event, PidginStatusBox *box)
365 gdk_window_set_cursor(gtk_widget_get_window(widget), box->hand_cursor);
366 gtk_image_set_from_pixbuf(GTK_IMAGE(box->icon), box->buddy_icon_hover);
367 return FALSE;
370 static gboolean
371 icon_box_leave_cb(GtkWidget *widget, GdkEventCrossing *event, PidginStatusBox *box)
373 gdk_window_set_cursor(gtk_widget_get_window(widget), box->arrow_cursor);
374 gtk_image_set_from_pixbuf(GTK_IMAGE(box->icon), box->buddy_icon) ;
375 return FALSE;
379 static const GtkTargetEntry dnd_targets[] = {
380 {"text/plain", 0, 0},
381 {"text/uri-list", 0, 1},
382 {"STRING", 0, 2}
385 static void
386 setup_icon_box(PidginStatusBox *status_box)
388 GdkDisplay *display;
390 if (status_box->icon_box != NULL)
391 return;
393 status_box->icon = gtk_image_new();
394 status_box->icon_box = gtk_event_box_new();
395 gtk_widget_set_parent(status_box->icon_box, GTK_WIDGET(status_box));
396 gtk_widget_show(status_box->icon_box);
398 gtk_widget_set_tooltip_text(status_box->icon_box,
399 status_box->account ? _("Click to change your buddyicon for this account.") :
400 _("Click to change your buddyicon for all accounts."));
402 if (status_box->account &&
403 !purple_account_get_bool(status_box->account, "use-global-buddyicon", TRUE))
405 PurpleImage *img = purple_buddy_icons_find_account_icon(status_box->account);
406 pidgin_status_box_set_buddy_icon(status_box, img);
407 g_object_unref(img);
409 else
411 const char *filename = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon");
412 PurpleImage *img = NULL;
414 if (filename && *filename)
415 img = purple_image_new_from_file(filename, TRUE);
417 pidgin_status_box_set_buddy_icon(status_box, img);
418 if (img)
419 g_object_unref(img);
422 display = gtk_widget_get_display(status_box->icon_box);
423 status_box->hand_cursor = gdk_cursor_new_for_display(display, GDK_HAND2);
424 status_box->arrow_cursor = gdk_cursor_new_for_display(display, GDK_LEFT_PTR);
426 /* Set up DND */
427 gtk_drag_dest_set(status_box->icon_box,
428 GTK_DEST_DEFAULT_MOTION |
429 GTK_DEST_DEFAULT_DROP,
430 dnd_targets,
431 sizeof(dnd_targets) / sizeof(GtkTargetEntry),
432 GDK_ACTION_COPY);
434 g_signal_connect(G_OBJECT(status_box->icon_box), "drag_data_received", G_CALLBACK(icon_box_dnd_cb), status_box);
435 g_signal_connect(G_OBJECT(status_box->icon_box), "enter-notify-event", G_CALLBACK(icon_box_enter_cb), status_box);
436 g_signal_connect(G_OBJECT(status_box->icon_box), "leave-notify-event", G_CALLBACK(icon_box_leave_cb), status_box);
437 g_signal_connect(G_OBJECT(status_box->icon_box), "button-press-event", G_CALLBACK(icon_box_press_cb), status_box);
439 gtk_container_add(GTK_CONTAINER(status_box->icon_box), status_box->icon);
440 gtk_widget_show(status_box->icon);
443 static void
444 destroy_icon_box(PidginStatusBox *statusbox)
446 if (statusbox->icon_box == NULL)
447 return;
449 gtk_widget_destroy(statusbox->icon_box);
451 g_object_unref(statusbox->hand_cursor);
452 g_object_unref(statusbox->arrow_cursor);
454 if (statusbox->buddy_icon_img)
455 g_object_unref(statusbox->buddy_icon_img);
457 g_object_unref(G_OBJECT(statusbox->buddy_icon));
458 g_object_unref(G_OBJECT(statusbox->buddy_icon_hover));
460 if (statusbox->buddy_icon_sel)
461 gtk_widget_destroy(statusbox->buddy_icon_sel);
463 if (statusbox->icon_box_menu)
464 gtk_widget_destroy(statusbox->icon_box_menu);
466 statusbox->icon = NULL;
467 statusbox->icon_box = NULL;
468 statusbox->icon_box_menu = NULL;
469 statusbox->buddy_icon_img = NULL;
470 statusbox->buddy_icon = NULL;
471 statusbox->buddy_icon_hover = NULL;
472 statusbox->hand_cursor = NULL;
473 statusbox->arrow_cursor = NULL;
476 static void
477 pidgin_status_box_set_property(GObject *object, guint param_id,
478 const GValue *value, GParamSpec *pspec)
480 PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(object);
482 switch (param_id) {
483 case PROP_ICON_SEL:
484 if (g_value_get_boolean(value)) {
485 if (statusbox->account) {
486 PurpleBuddyIconSpec *icon_spec = NULL;
487 PurpleProtocol *protocol =
488 purple_protocols_find(purple_account_get_protocol_id(statusbox->account));
489 if (protocol)
490 icon_spec = purple_protocol_get_icon_spec(protocol);
491 if (icon_spec && icon_spec->format != NULL)
492 setup_icon_box(statusbox);
493 } else {
494 setup_icon_box(statusbox);
496 } else {
497 destroy_icon_box(statusbox);
499 break;
500 case PROP_ACCOUNT:
501 statusbox->account = g_value_get_pointer(value);
502 if (statusbox->account)
503 statusbox->token_status_account = NULL;
504 else
505 statusbox->token_status_account = check_active_accounts_for_identical_statuses();
507 pidgin_status_box_regenerate(statusbox, TRUE);
509 break;
510 default:
511 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
512 break;
516 static void
517 pidgin_status_box_finalize(GObject *obj)
519 PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(obj);
520 gsize i;
522 purple_signals_disconnect_by_handle(statusbox);
523 purple_prefs_disconnect_by_handle(statusbox);
525 destroy_icon_box(statusbox);
527 if (statusbox->active_row)
528 gtk_tree_row_reference_free(statusbox->active_row);
530 for (i = 0; i < G_N_ELEMENTS(statusbox->connecting_pixbufs); i++) {
531 if (statusbox->connecting_pixbufs[i] != NULL)
532 g_object_unref(G_OBJECT(statusbox->connecting_pixbufs[i]));
535 for (i = 0; i < G_N_ELEMENTS(statusbox->typing_pixbufs); i++) {
536 if (statusbox->typing_pixbufs[i] != NULL)
537 g_object_unref(G_OBJECT(statusbox->typing_pixbufs[i]));
540 g_object_unref(G_OBJECT(statusbox->store));
541 g_object_unref(G_OBJECT(statusbox->dropdown_store));
542 G_OBJECT_CLASS(parent_class)->finalize(obj);
545 static GType
546 pidgin_status_box_child_type (GtkContainer *container)
548 return GTK_TYPE_WIDGET;
551 static void
552 pidgin_status_box_class_init (PidginStatusBoxClass *klass)
554 GObjectClass *object_class;
555 GtkWidgetClass *widget_class;
556 GtkContainerClass *container_class = (GtkContainerClass*)klass;
558 parent_class = g_type_class_peek_parent(klass);
560 widget_class = (GtkWidgetClass*)klass;
561 widget_class->get_preferred_height = pidgin_status_box_get_preferred_height;
562 widget_class->draw = pidgin_status_box_draw;
563 widget_class->size_allocate = pidgin_status_box_size_allocate;
565 container_class->child_type = pidgin_status_box_child_type;
566 container_class->forall = pidgin_status_box_forall;
567 container_class->remove = NULL;
569 object_class = (GObjectClass *)klass;
571 object_class->finalize = pidgin_status_box_finalize;
573 object_class->get_property = pidgin_status_box_get_property;
574 object_class->set_property = pidgin_status_box_set_property;
576 g_object_class_install_property(object_class,
577 PROP_ACCOUNT,
578 g_param_spec_pointer("account",
579 "Account",
580 "The account, or NULL for all accounts",
581 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
584 g_object_class_install_property(object_class,
585 PROP_ICON_SEL,
586 g_param_spec_boolean("iconsel",
587 "Icon Selector",
588 "Whether the icon selector should be displayed or not.",
589 FALSE,
590 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
596 * This updates the text displayed on the status box so that it shows
597 * the current status. This is the only function in this file that
598 * should modify status_box->store
600 static void
601 pidgin_status_box_refresh(PidginStatusBox *status_box)
603 const char *aa_color;
604 PurpleSavedStatus *saved_status;
605 char *primary, *secondary, *text;
606 const char *stock = NULL;
607 GdkPixbuf *emblem = NULL;
608 GtkTreePath *path;
609 gboolean account_status = FALSE;
610 PurpleAccount *acct = (status_box->account) ? status_box->account : status_box->token_status_account;
612 saved_status = purple_savedstatus_get_current();
614 if (status_box->account || (status_box->token_status_account
615 && purple_savedstatus_is_transient(saved_status)))
616 account_status = TRUE;
618 /* Primary */
619 if (status_box->typing != 0)
621 GtkTreeIter iter;
622 PidginStatusBoxItemType type;
623 gpointer data;
625 /* Primary (get the status selected in the dropdown) */
626 path = gtk_tree_row_reference_get_path(status_box->active_row);
627 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
628 return;
629 gtk_tree_path_free(path);
631 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
632 TYPE_COLUMN, &type,
633 DATA_COLUMN, &data,
634 -1);
635 if (type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE)
636 primary = g_strdup(purple_primitive_get_name_from_type(GPOINTER_TO_INT(data)));
637 else
638 /* This should never happen, but just in case... */
639 primary = g_strdup("New status");
641 else if (account_status)
642 primary = g_strdup(purple_status_get_name(purple_account_get_active_status(acct)));
643 else if (purple_savedstatus_is_transient(saved_status))
644 primary = g_strdup(purple_primitive_get_name_from_type(purple_savedstatus_get_primitive_type(saved_status)));
645 else
646 primary = g_markup_escape_text(purple_savedstatus_get_title(saved_status), -1);
648 /* Secondary */
649 if (status_box->typing != 0)
650 secondary = g_strdup(_("Typing"));
651 else if (status_box->connecting)
652 secondary = g_strdup(_("Connecting"));
653 else if (!status_box->network_available)
654 secondary = g_strdup(_("Waiting for network connection"));
655 else if (purple_savedstatus_is_transient(saved_status))
656 secondary = NULL;
657 else
659 const char *message;
660 char *tmp;
661 message = purple_savedstatus_get_message(saved_status);
662 if (message != NULL)
664 tmp = purple_markup_strip_html(message);
665 purple_util_chrreplace(tmp, '\n', ' ');
666 secondary = g_markup_escape_text(tmp, -1);
667 g_free(tmp);
669 else
670 secondary = NULL;
673 /* Pixbuf */
674 if (status_box->typing != 0)
675 stock = typing_stock_ids[status_box->typing_index];
676 else if (status_box->connecting)
677 stock = connecting_stock_ids[status_box->connecting_index];
678 else
680 PurpleStatusType *status_type;
681 PurpleStatusPrimitive prim;
682 if (account_status) {
683 status_type = purple_status_get_status_type(purple_account_get_active_status(acct));
684 prim = purple_status_type_get_primitive(status_type);
685 } else {
686 prim = purple_savedstatus_get_primitive_type(saved_status);
689 stock = pidgin_stock_id_from_status_primitive(prim);
692 aa_color = pidgin_get_dim_grey_string(GTK_WIDGET(status_box));
693 if (status_box->account != NULL) {
694 text = g_strdup_printf("%s - <span size=\"smaller\" color=\"%s\">%s</span>",
695 purple_account_get_username(status_box->account),
696 aa_color, secondary ? secondary : primary);
697 emblem = pidgin_create_protocol_icon(status_box->account, PIDGIN_PROTOCOL_ICON_SMALL);
698 } else if (secondary != NULL) {
699 text = g_strdup_printf("%s<span size=\"smaller\" color=\"%s\"> - %s</span>",
700 primary, aa_color, secondary);
701 } else {
702 text = g_strdup(primary);
704 g_free(primary);
705 g_free(secondary);
708 * Only two columns are used in this list store (does it
709 * really need to be a list store?)
711 gtk_list_store_set(status_box->store, &(status_box->iter),
712 ICON_STOCK_COLUMN, (gpointer)stock,
713 TEXT_COLUMN, text,
714 EMBLEM_COLUMN, emblem,
715 EMBLEM_VISIBLE_COLUMN, (emblem != NULL),
716 -1);
717 g_free(text);
718 if (emblem)
719 g_object_unref(emblem);
721 /* Make sure to activate the only row in the tree view */
722 path = gtk_tree_path_new_from_string("0");
723 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(status_box->cell_view), path);
724 gtk_tree_path_free(path);
726 update_size(status_box);
729 static PurpleStatusType *
730 find_status_type_by_index(const PurpleAccount *account, gint active)
732 GList *l = purple_account_get_status_types(account);
733 gint i;
735 for (i = 0; l; l = l->next) {
736 PurpleStatusType *status_type = l->data;
737 if (!purple_status_type_is_user_settable(status_type) ||
738 purple_status_type_is_independent(status_type))
739 continue;
741 if (active == i)
742 return status_type;
743 i++;
746 return NULL;
750 * This updates the GtkTreeView so that it correctly shows the state
751 * we are currently using. It is used when the current state is
752 * updated from somewhere other than the GtkStatusBox (from a plugin,
753 * or when signing on with the "-n" option, for example). It is
754 * also used when the user selects the "New..." option.
756 * Maybe we could accomplish this by triggering off the mouse and
757 * keyboard signals instead of the changed signal?
759 static void
760 status_menu_refresh_iter(PidginStatusBox *status_box, gboolean status_changed)
762 PurpleSavedStatus *saved_status;
763 PurpleStatusPrimitive primitive;
764 gint index;
765 const char *message;
766 GtkTreePath *path = NULL;
768 /* this function is inappropriate for ones with accounts */
769 if (status_box->account)
770 return;
772 saved_status = purple_savedstatus_get_current();
775 * Suppress the "changed" signal because the status
776 * was changed programmatically.
778 gtk_widget_set_sensitive(GTK_WIDGET(status_box), FALSE);
781 * If there is a token-account, then select the primitive from the
782 * dropdown using a loop. Otherwise select from the default list.
784 primitive = purple_savedstatus_get_primitive_type(saved_status);
785 if (!status_box->token_status_account && purple_savedstatus_is_transient(saved_status) &&
786 ((primitive == PURPLE_STATUS_AVAILABLE) || (primitive == PURPLE_STATUS_AWAY) ||
787 (primitive == PURPLE_STATUS_INVISIBLE) || (primitive == PURPLE_STATUS_OFFLINE) ||
788 (primitive == PURPLE_STATUS_UNAVAILABLE)) &&
789 (!purple_savedstatus_has_substatuses(saved_status)))
791 index = get_statusbox_index(status_box, saved_status);
792 path = gtk_tree_path_new_from_indices(index, -1);
794 else
796 GtkTreeIter iter;
797 PidginStatusBoxItemType type;
798 gpointer data;
800 /* If this saved status is in the list store, then set it as the active item */
801 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(status_box->dropdown_store), &iter))
805 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
806 TYPE_COLUMN, &type,
807 DATA_COLUMN, &data,
808 -1);
810 /* This is a special case because Primitives for the token_status_account are actually
811 * saved statuses with substatuses for the enabled accounts */
812 if (status_box->token_status_account && purple_savedstatus_is_transient(saved_status)
813 && type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE && primitive == (PurpleStatusPrimitive)GPOINTER_TO_INT(data))
815 char *name;
816 const char *acct_status_name = purple_status_get_name(
817 purple_account_get_active_status(status_box->token_status_account));
819 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
820 TEXT_COLUMN, &name, -1);
822 if (!purple_savedstatus_has_substatuses(saved_status)
823 || !strcmp(name, acct_status_name))
825 /* Found! */
826 path = gtk_tree_model_get_path(GTK_TREE_MODEL(status_box->dropdown_store), &iter);
827 g_free(name);
828 break;
830 g_free(name);
832 } else if ((type == PIDGIN_STATUS_BOX_TYPE_POPULAR) &&
833 (GPOINTER_TO_INT(data) == purple_savedstatus_get_creation_time(saved_status)))
835 /* Found! */
836 path = gtk_tree_model_get_path(GTK_TREE_MODEL(status_box->dropdown_store), &iter);
837 break;
839 } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(status_box->dropdown_store), &iter));
843 if (status_box->active_row)
844 gtk_tree_row_reference_free(status_box->active_row);
845 if (path) { /* path should never be NULL */
846 status_box->active_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box->dropdown_store), path);
847 gtk_tree_path_free(path);
848 } else
849 status_box->active_row = NULL;
851 if (status_changed) {
852 message = purple_savedstatus_get_message(saved_status);
855 * If we are going to hide the webview, don't retain the
856 * message because showing the old message later is
857 * confusing. If we are going to set the message to a pre-set,
858 * then we need to do this anyway
860 * Suppress the "changed" signal because the status
861 * was changed programmatically.
863 gtk_widget_set_sensitive(GTK_WIDGET(status_box->webview), FALSE);
865 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(status_box->webview), "");
866 pidgin_webview_clear_formatting(PIDGIN_WEBVIEW(status_box->webview));
868 if (!purple_savedstatus_is_transient(saved_status) || !message || !*message)
870 status_box->webview_visible = FALSE;
871 gtk_widget_hide(status_box->vbox);
873 else
875 status_box->webview_visible = TRUE;
876 gtk_widget_show_all(status_box->vbox);
878 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(status_box->webview), message);
881 gtk_widget_set_sensitive(GTK_WIDGET(status_box->webview), TRUE);
882 update_size(status_box);
885 /* Stop suppressing the "changed" signal. */
886 gtk_widget_set_sensitive(GTK_WIDGET(status_box), TRUE);
889 static void
890 add_popular_statuses(PidginStatusBox *statusbox)
892 GList *list, *cur;
894 list = purple_savedstatuses_get_popular(6);
895 if (list == NULL)
896 /* Odd... oh well, nothing we can do about it. */
897 return;
899 pidgin_status_box_add_separator(statusbox);
901 for (cur = list; cur != NULL; cur = cur->next)
903 PurpleSavedStatus *saved = cur->data;
904 const gchar *message;
905 gchar *stripped = NULL;
906 PidginStatusBoxItemType type;
908 if (purple_savedstatus_is_transient(saved))
911 * Transient statuses do not have a title, so the savedstatus
912 * API returns the message when purple_savedstatus_get_title() is
913 * called, so we don't need to get the message a second time.
915 type = PIDGIN_STATUS_BOX_TYPE_POPULAR;
917 else
919 type = PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR;
921 message = purple_savedstatus_get_message(saved);
922 if (message != NULL)
924 stripped = purple_markup_strip_html(message);
925 purple_util_chrreplace(stripped, '\n', ' ');
929 pidgin_status_box_add(statusbox, type,
930 NULL, purple_savedstatus_get_title(saved), stripped,
931 GINT_TO_POINTER(purple_savedstatus_get_creation_time(saved)));
932 g_free(stripped);
935 g_list_free(list);
938 /* This returns NULL if the active accounts don't have identical
939 * statuses and a token account if they do */
940 static PurpleAccount* check_active_accounts_for_identical_statuses(void)
942 GList *iter, *active_accts = purple_accounts_get_all_active();
943 PurpleAccount *acct1 = NULL;
944 const char *proto1 = NULL;
946 if (active_accts) {
947 acct1 = active_accts->data;
948 proto1 = purple_account_get_protocol_id(acct1);
949 } else {
950 /* there's no enabled account */
951 return NULL;
954 /* start at the second account */
955 for (iter = active_accts->next; iter; iter = iter->next) {
956 PurpleAccount *acct2 = iter->data;
957 GList *s1, *s2;
959 if (!g_str_equal(proto1, purple_account_get_protocol_id(acct2))) {
960 acct1 = NULL;
961 break;
964 for (s1 = purple_account_get_status_types(acct1),
965 s2 = purple_account_get_status_types(acct2); s1 && s2;
966 s1 = s1->next, s2 = s2->next) {
967 PurpleStatusType *st1 = s1->data, *st2 = s2->data;
968 /* TODO: Are these enough to consider the statuses identical? */
969 if (purple_status_type_get_primitive(st1) != purple_status_type_get_primitive(st2)
970 || strcmp(purple_status_type_get_id(st1), purple_status_type_get_id(st2))
971 || strcmp(purple_status_type_get_name(st1), purple_status_type_get_name(st2))) {
972 acct1 = NULL;
973 break;
977 if (s1 != s2) {/* Will both be NULL if matched */
978 acct1 = NULL;
979 break;
983 g_list_free(active_accts);
985 return acct1;
988 static void
989 add_account_statuses(PidginStatusBox *status_box, PurpleAccount *account)
991 /* Per-account */
992 GList *l;
994 for (l = purple_account_get_status_types(account); l != NULL; l = l->next)
996 PurpleStatusType *status_type = (PurpleStatusType *)l->data;
997 PurpleStatusPrimitive prim;
999 if (!purple_status_type_is_user_settable(status_type) ||
1000 purple_status_type_is_independent(status_type))
1001 continue;
1003 prim = purple_status_type_get_primitive(status_type);
1005 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box),
1006 PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL,
1007 purple_status_type_get_name(status_type),
1008 NULL,
1009 GINT_TO_POINTER(prim));
1013 static void
1014 pidgin_status_box_regenerate(PidginStatusBox *status_box, gboolean status_changed)
1016 /* Unset the model while clearing it */
1017 gtk_tree_view_set_model(GTK_TREE_VIEW(status_box->tree_view), NULL);
1018 gtk_list_store_clear(status_box->dropdown_store);
1019 /* Don't set the model until the new statuses have been added to the box.
1020 * What is presumably a bug in Gtk < 2.4 causes things to get all confused
1021 * if we do this here. */
1022 /* gtk_combo_box_set_model(GTK_COMBO_BOX(status_box), GTK_TREE_MODEL(status_box->dropdown_store)); */
1024 if (status_box->account == NULL)
1026 /* Do all the currently enabled accounts have the same statuses?
1027 * If so, display them instead of our global list.
1029 if (status_box->token_status_account) {
1030 add_account_statuses(status_box, status_box->token_status_account);
1031 } else {
1032 /* Global */
1033 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL, _("Available"), NULL, GINT_TO_POINTER(PURPLE_STATUS_AVAILABLE));
1034 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL, _("Away"), NULL, GINT_TO_POINTER(PURPLE_STATUS_AWAY));
1035 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));
1036 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL, _("Invisible"), NULL, GINT_TO_POINTER(PURPLE_STATUS_INVISIBLE));
1037 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL, _("Offline"), NULL, GINT_TO_POINTER(PURPLE_STATUS_OFFLINE));
1040 add_popular_statuses(status_box);
1042 pidgin_status_box_add_separator(PIDGIN_STATUS_BOX(status_box));
1043 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_CUSTOM, NULL, _("New status..."), NULL, NULL);
1044 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_SAVED, NULL, _("Saved statuses..."), NULL, NULL);
1046 status_menu_refresh_iter(status_box, status_changed);
1047 pidgin_status_box_refresh(status_box);
1049 } else {
1050 add_account_statuses(status_box, status_box->account);
1051 update_to_reflect_account_status(status_box, status_box->account,
1052 purple_account_get_active_status(status_box->account));
1054 gtk_tree_view_set_model(GTK_TREE_VIEW(status_box->tree_view), GTK_TREE_MODEL(status_box->dropdown_store));
1055 gtk_tree_view_set_search_column(GTK_TREE_VIEW(status_box->tree_view), TEXT_COLUMN);
1058 static gboolean
1059 combo_box_scroll_event_cb(GtkWidget *w, GdkEventScroll *event, PidginWebView *webview)
1061 pidgin_status_box_popup(PIDGIN_STATUS_BOX(w), (GdkEvent *)event);
1062 return TRUE;
1065 static gboolean
1066 webview_scroll_event_cb(GtkWidget *w, GdkEventScroll *event, PidginWebView *webview)
1068 if (event->direction == GDK_SCROLL_UP)
1069 pidgin_webview_page_up(webview);
1070 else if (event->direction == GDK_SCROLL_DOWN)
1071 pidgin_webview_page_down(webview);
1072 return TRUE;
1075 static gboolean
1076 webview_remove_focus(GtkWidget *w, GdkEventKey *event, PidginStatusBox *status_box)
1078 if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) {
1079 remove_typing_cb(status_box);
1080 return TRUE;
1082 else if (event->keyval == GDK_KEY_Tab || event->keyval == GDK_KEY_KP_Tab || event->keyval == GDK_KEY_ISO_Left_Tab)
1084 /* If last inserted character is a tab, then remove the focus from here */
1085 GtkWidget *top = gtk_widget_get_toplevel(w);
1086 g_signal_emit_by_name(G_OBJECT(top), "move-focus",
1087 (event->state & GDK_SHIFT_MASK) ?
1088 GTK_DIR_TAB_BACKWARD: GTK_DIR_TAB_FORWARD);
1089 return TRUE;
1091 if (status_box->typing == 0)
1092 return FALSE;
1094 /* Reset the status if Escape was pressed */
1095 if (event->keyval == GDK_KEY_Escape)
1097 purple_timeout_remove(status_box->typing);
1098 status_box->typing = 0;
1099 #if 0
1100 /* TODO WebKit: Doesn't do this? */
1101 pidgin_webview_set_populate_primary_clipboard(
1102 PIDGIN_WEBVIEW(status_box->webview), TRUE);
1103 #endif
1104 if (status_box->account != NULL)
1105 update_to_reflect_account_status(status_box, status_box->account,
1106 purple_account_get_active_status(status_box->account));
1107 else {
1108 status_menu_refresh_iter(status_box, TRUE);
1109 pidgin_status_box_refresh(status_box);
1111 return TRUE;
1114 pidgin_status_box_pulse_typing(status_box);
1115 purple_timeout_remove(status_box->typing);
1116 status_box->typing = purple_timeout_add_seconds(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
1118 return FALSE;
1121 static gboolean
1122 dropdown_store_row_separator_func(GtkTreeModel *model,
1123 GtkTreeIter *iter, gpointer data)
1125 PidginStatusBoxItemType type;
1127 gtk_tree_model_get(model, iter, TYPE_COLUMN, &type, -1);
1129 if (type == PIDGIN_STATUS_BOX_TYPE_SEPARATOR)
1130 return TRUE;
1132 return FALSE;
1135 static void
1136 cache_pixbufs(PidginStatusBox *status_box)
1138 GtkIconSize icon_size;
1139 gsize i;
1141 g_object_set(G_OBJECT(status_box->icon_rend), "xpad", 3, NULL);
1142 icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL);
1144 for (i = 0; i < G_N_ELEMENTS(status_box->connecting_pixbufs); i++) {
1145 if (status_box->connecting_pixbufs[i] != NULL)
1146 g_object_unref(G_OBJECT(status_box->connecting_pixbufs[i]));
1147 if (connecting_stock_ids[i])
1148 status_box->connecting_pixbufs[i] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox),
1149 connecting_stock_ids[i], icon_size, "PidginStatusBox");
1150 else
1151 status_box->connecting_pixbufs[i] = NULL;
1153 status_box->connecting_index = 0;
1156 for (i = 0; i < G_N_ELEMENTS(status_box->typing_pixbufs); i++) {
1157 if (status_box->typing_pixbufs[i] != NULL)
1158 g_object_unref(G_OBJECT(status_box->typing_pixbufs[i]));
1159 if (typing_stock_ids[i])
1160 status_box->typing_pixbufs[i] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox),
1161 typing_stock_ids[i], icon_size, "PidginStatusBox");
1162 else
1163 status_box->typing_pixbufs[i] = NULL;
1165 status_box->typing_index = 0;
1168 static void account_enabled_cb(PurpleAccount *acct, PidginStatusBox *status_box)
1170 PurpleAccount *initial_token_acct = status_box->token_status_account;
1172 if (status_box->account)
1173 return;
1175 status_box->token_status_account = check_active_accounts_for_identical_statuses();
1177 /* Regenerate the list if it has changed */
1178 if (initial_token_acct != status_box->token_status_account) {
1179 pidgin_status_box_regenerate(status_box, TRUE);
1184 static void
1185 current_savedstatus_changed_cb(PurpleSavedStatus *now, PurpleSavedStatus *old, PidginStatusBox *status_box)
1187 /* Make sure our current status is added to the list of popular statuses */
1188 pidgin_status_box_regenerate(status_box, TRUE);
1191 static void
1192 saved_status_updated_cb(PurpleSavedStatus *status, PidginStatusBox *status_box)
1194 pidgin_status_box_regenerate(status_box,
1195 purple_savedstatus_get_current() == status);
1198 static void
1199 spellcheck_prefs_cb(const char *name, PurplePrefType type,
1200 gconstpointer value, gpointer data)
1202 PidginStatusBox *status_box = (PidginStatusBox *)data;
1204 pidgin_webview_set_spellcheck(PIDGIN_WEBVIEW(status_box->webview),
1205 (gboolean)GPOINTER_TO_INT(value));
1208 #if 0
1209 static gboolean button_released_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
1212 if (event->button != 1)
1213 return FALSE;
1214 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box->toggle_button), FALSE);
1215 if (!box->webview_visible)
1216 g_signal_emit_by_name(G_OBJECT(box), "changed", NULL, NULL);
1217 return TRUE;
1220 static gboolean button_pressed_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
1222 if (event->button != 1)
1223 return FALSE;
1224 gtk_combo_box_popup(GTK_COMBO_BOX(box));
1225 /* Disabled until button_released_cb works */
1226 #if 0
1227 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box->toggle_button), TRUE);
1228 #endif
1229 return TRUE;
1231 #endif
1233 static void
1234 pidgin_status_box_list_position (PidginStatusBox *status_box, int *x, int *y, int *width, int *height)
1236 GdkScreen *screen;
1237 gint monitor_num;
1238 GdkRectangle monitor;
1239 GtkRequisition popup_req;
1240 GtkPolicyType hpolicy, vpolicy;
1241 GtkAllocation allocation;
1243 gtk_widget_get_allocation(GTK_WIDGET(status_box), &allocation);
1244 gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(status_box)), x, y);
1246 *x += allocation.x;
1247 *y += allocation.y;
1249 *width = allocation.width;
1251 hpolicy = vpolicy = GTK_POLICY_NEVER;
1252 g_object_set(G_OBJECT(status_box->scrolled_window),
1253 "hscrollbar-policy", hpolicy,
1254 "vscrollbar-policy", vpolicy,
1255 NULL);
1256 gtk_widget_get_preferred_size(status_box->popup_frame, NULL, &popup_req);
1258 if (popup_req.width > *width) {
1259 hpolicy = GTK_POLICY_ALWAYS;
1260 g_object_set(G_OBJECT(status_box->scrolled_window),
1261 "hscrollbar-policy", hpolicy,
1262 "vscrollbar-policy", vpolicy,
1263 NULL);
1264 gtk_widget_get_preferred_size(status_box->popup_frame, NULL, &popup_req);
1267 *height = popup_req.height;
1269 screen = gtk_widget_get_screen(GTK_WIDGET(status_box));
1270 monitor_num = gdk_screen_get_monitor_at_window(screen,
1271 gtk_widget_get_window(GTK_WIDGET(status_box)));
1272 gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor);
1274 if (*x < monitor.x)
1275 *x = monitor.x;
1276 else if (*x + *width > monitor.x + monitor.width)
1277 *x = monitor.x + monitor.width - *width;
1279 if (*y + allocation.height + *height <= monitor.y + monitor.height)
1280 *y += allocation.height;
1281 else if (*y - *height >= monitor.y)
1282 *y -= *height;
1283 else if (monitor.y + monitor.height - (*y + allocation.height) > *y - monitor.y)
1285 *y += allocation.height;
1286 *height = monitor.y + monitor.height - *y;
1288 else
1290 *height = *y - monitor.y;
1291 *y = monitor.y;
1294 if (popup_req.height > *height)
1296 vpolicy = GTK_POLICY_ALWAYS;
1298 g_object_set(G_OBJECT(status_box->scrolled_window),
1299 "hscrollbar-policy", hpolicy,
1300 "vscrollbar-policy", vpolicy,
1301 NULL);
1305 static gboolean
1306 popup_grab_on_window(GdkWindow *window, GdkEvent *event)
1308 guint32 activate_time = gdk_event_get_time(event);
1309 GdkDevice *device = gdk_event_get_device(event);
1310 GdkGrabStatus status;
1312 status = gdk_device_grab(device, window, GDK_OWNERSHIP_WINDOW, TRUE,
1313 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1314 GDK_POINTER_MOTION_MASK | GDK_KEY_PRESS_MASK |
1315 GDK_KEY_RELEASE_MASK, NULL, activate_time);
1316 if (status == GDK_GRAB_SUCCESS) {
1317 status = gdk_device_grab(gdk_device_get_associated_device(device),
1318 window, GDK_OWNERSHIP_WINDOW, TRUE,
1319 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1320 GDK_POINTER_MOTION_MASK | GDK_KEY_PRESS_MASK |
1321 GDK_KEY_RELEASE_MASK, NULL, activate_time);
1322 if (status == GDK_GRAB_SUCCESS)
1323 return TRUE;
1324 else
1325 gdk_device_ungrab(device, activate_time);
1328 return FALSE;
1332 static void
1333 pidgin_status_box_popup(PidginStatusBox *box, GdkEvent *event)
1335 int width, height, x, y;
1336 pidgin_status_box_list_position (box, &x, &y, &width, &height);
1338 gtk_widget_set_size_request (box->popup_window, width, height);
1339 gtk_window_move (GTK_WINDOW (box->popup_window), x, y);
1340 gtk_widget_show(box->popup_window);
1341 gtk_widget_grab_focus (box->tree_view);
1342 if (!popup_grab_on_window(gtk_widget_get_window(box->popup_window), event)) {
1343 gtk_widget_hide (box->popup_window);
1344 return;
1346 gtk_grab_add (box->popup_window);
1347 /*box->popup_in_progress = TRUE;*/
1348 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (box->toggle_button),
1349 TRUE);
1351 if (box->active_row) {
1352 GtkTreePath *path = gtk_tree_row_reference_get_path(box->active_row);
1353 gtk_tree_view_set_cursor(GTK_TREE_VIEW(box->tree_view), path, NULL, FALSE);
1354 gtk_tree_path_free(path);
1358 static void
1359 pidgin_status_box_popdown(PidginStatusBox *box, GdkEvent *event)
1361 guint32 time;
1362 GdkDevice *device;
1363 gtk_widget_hide(box->popup_window);
1364 box->popup_in_progress = FALSE;
1365 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box->toggle_button), FALSE);
1366 gtk_grab_remove(box->popup_window);
1367 time = gdk_event_get_time(event);
1368 device = gdk_event_get_device(event);
1369 gdk_device_ungrab(device, time);
1370 gdk_device_ungrab(gdk_device_get_associated_device(device), time);
1373 static gboolean
1374 toggle_key_press_cb(GtkWidget *widget, GdkEventKey *event, PidginStatusBox *box)
1376 switch (event->keyval) {
1377 case GDK_KEY_Return:
1378 case GDK_KEY_KP_Enter:
1379 case GDK_KEY_KP_Space:
1380 case GDK_KEY_space:
1381 if (!box->popup_in_progress) {
1382 pidgin_status_box_popup(box, (GdkEvent *)event);
1383 box->popup_in_progress = TRUE;
1384 } else {
1385 pidgin_status_box_popdown(box, (GdkEvent *)event);
1387 return TRUE;
1388 default:
1389 return FALSE;
1393 static gboolean
1394 toggled_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
1396 if (!box->popup_in_progress)
1397 pidgin_status_box_popup(box, (GdkEvent *)event);
1398 else
1399 pidgin_status_box_popdown(box, (GdkEvent *)event);
1400 return TRUE;
1403 static void
1404 buddy_icon_set_cb(const char *filename, PidginStatusBox *box)
1406 PurpleImage *img = NULL;
1407 PurpleBuddyIconSpec *icon_spec = NULL;
1409 if (box->account) {
1410 PurpleProtocol *protocol =
1411 purple_protocols_find(purple_account_get_protocol_id(box->account));
1412 if (protocol)
1413 icon_spec = purple_protocol_get_icon_spec(protocol);
1414 if (icon_spec && icon_spec->format) {
1415 gpointer data = NULL;
1416 size_t len = 0;
1417 if (filename)
1418 data = pidgin_convert_buddy_icon(protocol, filename, &len);
1419 img = purple_buddy_icons_set_account_icon(box->account, data, len);
1420 if (img) {
1422 * set_account_icon doesn't give us a reference, but we
1423 * unref one below (for the other code path)
1425 g_object_ref(img);
1428 purple_account_set_buddy_icon_path(box->account, filename);
1430 purple_account_set_bool(box->account, "use-global-buddyicon", (filename != NULL));
1432 } else {
1433 GList *accounts;
1434 for (accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) {
1435 PurpleAccount *account = accounts->data;
1436 PurpleProtocol *protocol =
1437 purple_protocols_find(purple_account_get_protocol_id(account));
1438 if (protocol)
1439 icon_spec = purple_protocol_get_icon_spec(protocol);
1440 if (icon_spec && icon_spec->format &&
1441 purple_account_get_bool(account, "use-global-buddyicon", TRUE)) {
1442 gpointer data = NULL;
1443 size_t len = 0;
1444 if (filename)
1445 data = pidgin_convert_buddy_icon(protocol, filename, &len);
1446 purple_buddy_icons_set_account_icon(account, data, len);
1447 purple_account_set_buddy_icon_path(account, filename);
1451 /* Even if no accounts were processed, load the icon that was set. */
1452 if (filename != NULL)
1453 img = purple_image_new_from_file(filename, TRUE);
1456 pidgin_status_box_set_buddy_icon(box, img);
1457 if (img)
1458 g_object_unref(img);
1461 static void
1462 remove_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box)
1464 if (box->account == NULL)
1465 /* The pref-connect callback does the actual work */
1466 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon", NULL);
1467 else
1468 buddy_icon_set_cb(NULL, box);
1470 gtk_widget_destroy(box->icon_box_menu);
1471 box->icon_box_menu = NULL;
1474 static void
1475 choose_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box)
1477 if (box->buddy_icon_sel) {
1478 gtk_window_present(GTK_WINDOW(box->buddy_icon_sel));
1479 } else {
1480 box->buddy_icon_sel = pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtk_widget_get_toplevel(w)), icon_choose_cb, box);
1481 gtk_widget_show_all(box->buddy_icon_sel);
1485 static void
1486 icon_choose_cb(const char *filename, gpointer data)
1488 PidginStatusBox *box = data;
1489 if (filename) {
1490 if (box->account == NULL)
1491 /* The pref-connect callback does the actual work */
1492 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon", filename);
1493 else
1494 buddy_icon_set_cb(filename, box);
1497 box->buddy_icon_sel = NULL;
1500 static void
1501 update_buddyicon_cb(const char *name, PurplePrefType type,
1502 gconstpointer value, gpointer data)
1504 buddy_icon_set_cb(value, (PidginStatusBox*) data);
1507 static void
1508 treeview_activate_current_selection(PidginStatusBox *status_box, GtkTreePath *path, GdkEvent *event)
1510 if (status_box->active_row)
1511 gtk_tree_row_reference_free(status_box->active_row);
1513 status_box->active_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box->dropdown_store), path);
1514 pidgin_status_box_popdown(status_box, event);
1515 pidgin_status_box_changed(status_box);
1518 static void tree_view_delete_current_selection_cb(gpointer data)
1520 PurpleSavedStatus *saved;
1522 saved = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
1523 g_return_if_fail(saved != NULL);
1525 if (purple_savedstatus_get_current() != saved)
1526 purple_savedstatus_delete_by_status(saved);
1529 static void
1530 tree_view_delete_current_selection(PidginStatusBox *status_box, GtkTreePath *path, GdkEvent *event)
1532 GtkTreeIter iter;
1533 gpointer data;
1534 PurpleSavedStatus *saved;
1535 gchar *msg;
1537 if (status_box->active_row) {
1538 /* don't delete active status */
1539 if (gtk_tree_path_compare(path, gtk_tree_row_reference_get_path(status_box->active_row)) == 0)
1540 return;
1543 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
1544 return;
1546 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
1547 DATA_COLUMN, &data,
1548 -1);
1550 saved = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
1551 g_return_if_fail(saved != NULL);
1552 if (saved == purple_savedstatus_get_current())
1553 return;
1555 msg = g_strdup_printf(_("Are you sure you want to delete %s?"), purple_savedstatus_get_title(saved));
1557 purple_request_action(saved, NULL, msg, NULL, 0,
1558 NULL,
1559 data, 2,
1560 _("Delete"), tree_view_delete_current_selection_cb,
1561 _("Cancel"), NULL);
1563 g_free(msg);
1565 pidgin_status_box_popdown(status_box, event);
1568 static gboolean
1569 treeview_button_release_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *status_box)
1571 GtkTreePath *path = NULL;
1572 int ret;
1573 GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);
1575 if (ewidget != status_box->tree_view) {
1576 if (ewidget == status_box->toggle_button &&
1577 status_box->popup_in_progress &&
1578 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (status_box->toggle_button))) {
1579 pidgin_status_box_popdown(status_box, (GdkEvent *)event);
1580 return TRUE;
1581 } else if (ewidget == status_box->toggle_button) {
1582 status_box->popup_in_progress = TRUE;
1585 /* released outside treeview */
1586 if (ewidget != status_box->toggle_button) {
1587 pidgin_status_box_popdown(status_box, (GdkEvent *)event);
1588 return TRUE;
1591 return FALSE;
1594 ret = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (status_box->tree_view),
1595 event->x, event->y,
1596 &path,
1597 NULL, NULL, NULL);
1599 if (!ret)
1600 return TRUE; /* clicked outside window? */
1602 treeview_activate_current_selection(status_box, path, (GdkEvent *)event);
1603 gtk_tree_path_free (path);
1605 return TRUE;
1608 static gboolean
1609 treeview_key_press_event(GtkWidget *widget,
1610 GdkEventKey *event, PidginStatusBox *box)
1612 if (box->popup_in_progress) {
1613 if (event->keyval == GDK_KEY_Escape) {
1614 pidgin_status_box_popdown(box, (GdkEvent *)event);
1615 return TRUE;
1616 } else {
1617 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(box->tree_view));
1618 GtkTreeIter iter;
1619 GtkTreePath *path;
1621 if (gtk_tree_selection_get_selected(sel, NULL, &iter)) {
1622 gboolean ret = TRUE;
1623 path = gtk_tree_model_get_path(GTK_TREE_MODEL(box->dropdown_store), &iter);
1624 if (event->keyval == GDK_KEY_Return) {
1625 treeview_activate_current_selection(box, path, (GdkEvent *)event);
1626 } else if (event->keyval == GDK_KEY_Delete) {
1627 tree_view_delete_current_selection(box, path, (GdkEvent *)event);
1628 } else
1629 ret = FALSE;
1631 gtk_tree_path_free (path);
1632 return ret;
1636 return FALSE;
1639 static void
1640 webview_cursor_moved_cb(gpointer data, PidginWebView *webview)
1642 /* Restart the typing timeout if arrow keys are pressed while editing the message */
1643 PidginStatusBox *status_box = data;
1644 if (status_box->typing == 0)
1645 return;
1646 webview_changed_cb(NULL, status_box);
1649 static void
1650 treeview_cursor_changed_cb(GtkTreeView *treeview, gpointer data)
1652 GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview);
1653 GtkTreeModel *model = GTK_TREE_MODEL (data);
1654 GtkTreeIter iter;
1655 GtkTreePath *cursor;
1656 GtkTreePath *selection;
1657 gint cmp;
1659 if (gtk_tree_selection_get_selected (sel, NULL, &iter)) {
1660 if ((selection = gtk_tree_model_get_path (model, &iter)) == NULL) {
1661 /* Shouldn't happen, but ignore anyway */
1662 return;
1664 } else {
1665 /* I don't think this can happen, but we'll just ignore it */
1666 return;
1669 gtk_tree_view_get_cursor (treeview, &cursor, NULL);
1670 if (cursor == NULL) {
1671 /* Probably won't happen in a 'cursor-changed' event? */
1672 gtk_tree_path_free (selection);
1673 return;
1676 cmp = gtk_tree_path_compare (cursor, selection);
1677 if (cmp < 0) {
1678 /* The cursor moved up without moving the selection, so move it up again */
1679 gtk_tree_path_prev (cursor);
1680 gtk_tree_view_set_cursor (treeview, cursor, NULL, FALSE);
1681 } else if (cmp > 0) {
1682 /* The cursor moved down without moving the selection, so move it down again */
1683 gtk_tree_path_next (cursor);
1684 gtk_tree_view_set_cursor (treeview, cursor, NULL, FALSE);
1687 gtk_tree_path_free (selection);
1688 gtk_tree_path_free (cursor);
1691 static void
1692 pidgin_status_box_init (PidginStatusBox *status_box)
1694 GtkCellRenderer *text_rend;
1695 GtkCellRenderer *icon_rend;
1696 GtkCellRenderer *emblem_rend;
1697 GtkWidget *toplevel;
1698 GtkTreeSelection *sel;
1700 gtk_widget_set_has_window(GTK_WIDGET(status_box), FALSE);
1701 status_box->webview_visible = FALSE;
1702 status_box->network_available = purple_network_is_available();
1703 status_box->connecting = FALSE;
1704 status_box->typing = 0;
1705 status_box->toggle_button = gtk_toggle_button_new();
1706 status_box->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
1707 status_box->cell_view = gtk_cell_view_new();
1708 status_box->vsep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
1709 #if GTK_CHECK_VERSION(3,14,0)
1710 status_box->arrow = gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON);
1711 #else
1712 status_box->arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
1713 #endif
1715 status_box->store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_INT, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING,
1716 G_TYPE_STRING, G_TYPE_POINTER, GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN);
1717 status_box->dropdown_store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_INT, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_STRING,
1718 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
1720 gtk_cell_view_set_model(GTK_CELL_VIEW(status_box->cell_view), GTK_TREE_MODEL(status_box->store));
1721 gtk_list_store_append(status_box->store, &(status_box->iter));
1723 atk_object_set_name(gtk_widget_get_accessible(status_box->toggle_button), _("Status Selector"));
1725 gtk_container_add(GTK_CONTAINER(status_box->toggle_button), status_box->hbox);
1726 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->cell_view, TRUE, TRUE, 0);
1727 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->vsep, FALSE, FALSE, 0);
1728 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->arrow, FALSE, FALSE, 0);
1729 gtk_widget_show_all(status_box->toggle_button);
1730 gtk_button_set_focus_on_click(GTK_BUTTON(status_box->toggle_button), FALSE);
1732 text_rend = gtk_cell_renderer_text_new();
1733 icon_rend = gtk_cell_renderer_pixbuf_new();
1734 emblem_rend = gtk_cell_renderer_pixbuf_new();
1735 status_box->popup_window = gtk_window_new (GTK_WINDOW_POPUP);
1737 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (status_box));
1738 if (GTK_IS_WINDOW (toplevel)) {
1739 gtk_window_set_transient_for (GTK_WINDOW (status_box->popup_window),
1740 GTK_WINDOW (toplevel));
1743 gtk_window_set_resizable (GTK_WINDOW (status_box->popup_window), FALSE);
1744 gtk_window_set_type_hint (GTK_WINDOW (status_box->popup_window),
1745 GDK_WINDOW_TYPE_HINT_POPUP_MENU);
1746 gtk_window_set_screen (GTK_WINDOW (status_box->popup_window),
1747 gtk_widget_get_screen (GTK_WIDGET (status_box)));
1748 status_box->popup_frame = gtk_frame_new (NULL);
1749 gtk_frame_set_shadow_type (GTK_FRAME (status_box->popup_frame),
1750 GTK_SHADOW_ETCHED_IN);
1751 gtk_container_add (GTK_CONTAINER (status_box->popup_window),
1752 status_box->popup_frame);
1754 gtk_widget_show (status_box->popup_frame);
1756 status_box->tree_view = gtk_tree_view_new ();
1757 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (status_box->tree_view));
1758 gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE);
1759 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (status_box->tree_view),
1760 FALSE);
1761 gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (status_box->tree_view),
1762 TRUE);
1763 gtk_tree_view_set_model (GTK_TREE_VIEW (status_box->tree_view),
1764 GTK_TREE_MODEL(status_box->dropdown_store));
1765 status_box->column = gtk_tree_view_column_new ();
1766 gtk_tree_view_append_column (GTK_TREE_VIEW (status_box->tree_view),
1767 status_box->column);
1768 gtk_tree_view_column_pack_start(status_box->column, icon_rend, FALSE);
1769 gtk_tree_view_column_pack_start(status_box->column, text_rend, TRUE);
1770 gtk_tree_view_column_pack_start(status_box->column, emblem_rend, FALSE);
1771 gtk_tree_view_column_set_attributes(status_box->column, icon_rend, "stock-id", ICON_STOCK_COLUMN, NULL);
1772 gtk_tree_view_column_set_attributes(status_box->column, text_rend, "markup", TEXT_COLUMN, NULL);
1773 gtk_tree_view_column_set_attributes(status_box->column, emblem_rend, "stock-id", EMBLEM_COLUMN, "visible", EMBLEM_VISIBLE_COLUMN, NULL);
1775 status_box->scrolled_window = pidgin_make_scrollable(status_box->tree_view, GTK_POLICY_NEVER, GTK_POLICY_NEVER, GTK_SHADOW_NONE, -1, -1);
1776 gtk_container_add (GTK_CONTAINER (status_box->popup_frame),
1777 status_box->scrolled_window);
1779 gtk_widget_show(status_box->tree_view);
1780 gtk_tree_view_set_search_column(GTK_TREE_VIEW(status_box->tree_view), TEXT_COLUMN);
1781 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(status_box->tree_view),
1782 pidgin_tree_view_search_equal_func, NULL, NULL);
1784 g_object_set(text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1786 status_box->icon_rend = gtk_cell_renderer_pixbuf_new();
1787 status_box->text_rend = gtk_cell_renderer_text_new();
1788 emblem_rend = gtk_cell_renderer_pixbuf_new();
1789 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), status_box->icon_rend, FALSE);
1790 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), status_box->text_rend, TRUE);
1791 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), emblem_rend, FALSE);
1792 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), status_box->icon_rend, "stock-id", ICON_STOCK_COLUMN, NULL);
1793 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), status_box->text_rend, "markup", TEXT_COLUMN, NULL);
1794 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), emblem_rend, "pixbuf", EMBLEM_COLUMN, "visible", EMBLEM_VISIBLE_COLUMN, NULL);
1795 g_object_set(status_box->text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1797 status_box->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, FALSE);
1798 status_box->sw = pidgin_create_webview(TRUE, &status_box->webview, NULL);
1799 pidgin_webview_hide_toolbar(PIDGIN_WEBVIEW(status_box->webview));
1801 #if 0
1802 g_signal_connect(G_OBJECT(status_box->toggle_button), "button-press-event",
1803 G_CALLBACK(button_pressed_cb), status_box);
1804 g_signal_connect(G_OBJECT(status_box->toggle_button), "button-release-event",
1805 G_CALLBACK(button_released_cb), status_box);
1806 #endif
1807 g_signal_connect(G_OBJECT(status_box->toggle_button), "key-press-event",
1808 G_CALLBACK(toggle_key_press_cb), status_box);
1809 g_signal_connect(G_OBJECT(status_box->toggle_button), "button-press-event",
1810 G_CALLBACK(toggled_cb), status_box);
1811 g_signal_connect(G_OBJECT(status_box->webview), "changed",
1812 G_CALLBACK(webview_changed_cb), status_box);
1813 g_signal_connect(G_OBJECT(status_box->webview), "format-toggled",
1814 G_CALLBACK(webview_format_changed_cb), status_box);
1815 g_signal_connect_swapped(G_OBJECT(status_box->webview), "selection-changed",
1816 G_CALLBACK(webview_cursor_moved_cb), status_box);
1817 g_signal_connect(G_OBJECT(status_box->webview), "key-press-event",
1818 G_CALLBACK(webview_remove_focus), status_box);
1820 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck"))
1821 pidgin_webview_set_spellcheck(PIDGIN_WEBVIEW(status_box->webview), TRUE);
1822 gtk_widget_set_parent(status_box->vbox, GTK_WIDGET(status_box));
1823 gtk_widget_show_all(status_box->vbox);
1825 gtk_widget_set_parent(status_box->toggle_button, GTK_WIDGET(status_box));
1827 gtk_box_pack_start(GTK_BOX(status_box->vbox), status_box->sw, TRUE, TRUE, 0);
1829 g_signal_connect(G_OBJECT(status_box), "scroll-event", G_CALLBACK(combo_box_scroll_event_cb), NULL);
1830 g_signal_connect(G_OBJECT(status_box->webview), "scroll-event",
1831 G_CALLBACK(webview_scroll_event_cb), status_box->webview);
1832 g_signal_connect(G_OBJECT(status_box->popup_window), "button_release_event", G_CALLBACK(treeview_button_release_cb), status_box);
1833 g_signal_connect(G_OBJECT(status_box->popup_window), "key_press_event", G_CALLBACK(treeview_key_press_event), status_box);
1834 g_signal_connect(G_OBJECT(status_box->tree_view), "cursor-changed",
1835 G_CALLBACK(treeview_cursor_changed_cb), status_box->dropdown_store);
1837 gtk_tree_view_set_row_separator_func(GTK_TREE_VIEW(status_box->tree_view), dropdown_store_row_separator_func, NULL, NULL);
1839 status_box->token_status_account = check_active_accounts_for_identical_statuses();
1841 cache_pixbufs(status_box);
1842 pidgin_status_box_regenerate(status_box, TRUE);
1844 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed",
1845 status_box,
1846 PURPLE_CALLBACK(current_savedstatus_changed_cb),
1847 status_box);
1848 purple_signal_connect(purple_savedstatuses_get_handle(),
1849 "savedstatus-added", status_box,
1850 PURPLE_CALLBACK(saved_status_updated_cb), status_box);
1851 purple_signal_connect(purple_savedstatuses_get_handle(),
1852 "savedstatus-deleted", status_box,
1853 PURPLE_CALLBACK(saved_status_updated_cb), status_box);
1854 purple_signal_connect(purple_savedstatuses_get_handle(),
1855 "savedstatus-modified", status_box,
1856 PURPLE_CALLBACK(saved_status_updated_cb), status_box);
1857 purple_signal_connect(purple_accounts_get_handle(), "account-enabled", status_box,
1858 PURPLE_CALLBACK(account_enabled_cb),
1859 status_box);
1860 purple_signal_connect(purple_accounts_get_handle(), "account-disabled", status_box,
1861 PURPLE_CALLBACK(account_enabled_cb),
1862 status_box);
1863 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed", status_box,
1864 PURPLE_CALLBACK(account_status_changed_cb),
1865 status_box);
1867 purple_prefs_connect_callback(status_box, PIDGIN_PREFS_ROOT "/conversations/spellcheck",
1868 spellcheck_prefs_cb, status_box);
1869 purple_prefs_connect_callback(status_box, PIDGIN_PREFS_ROOT "/accounts/buddyicon",
1870 update_buddyicon_cb, status_box);
1874 static void
1875 pidgin_status_box_get_preferred_height(GtkWidget *widget, gint *minimum_height,
1876 gint *natural_height)
1878 gint box_min_height, box_nat_height;
1879 gint border_width = gtk_container_get_border_width(GTK_CONTAINER (widget));
1881 gtk_widget_get_preferred_height(PIDGIN_STATUS_BOX(widget)->toggle_button,
1882 minimum_height, natural_height);
1884 *minimum_height = MAX(*minimum_height, 34) + border_width * 2;
1885 *natural_height = MAX(*natural_height, 34) + border_width * 2;
1887 /* If the gtkwebview is visible, then add some additional padding */
1888 if (PIDGIN_STATUS_BOX(widget)->webview_visible) {
1889 gtk_widget_get_preferred_height(PIDGIN_STATUS_BOX(widget)->vbox,
1890 &box_min_height, &box_nat_height);
1892 if (box_min_height > 1)
1893 *minimum_height += box_min_height + border_width * 2;
1895 if (box_nat_height > 1)
1896 *natural_height += box_nat_height + border_width * 2;
1900 /* From gnome-panel */
1901 static void
1902 do_colorshift (GdkPixbuf *dest, GdkPixbuf *src, int shift)
1904 gint i, j;
1905 gint width, height, has_alpha, srcrowstride, destrowstride;
1906 guchar *target_pixels;
1907 guchar *original_pixels;
1908 guchar *pixsrc;
1909 guchar *pixdest;
1910 int val;
1911 guchar r,g,b;
1913 has_alpha = gdk_pixbuf_get_has_alpha (src);
1914 width = gdk_pixbuf_get_width (src);
1915 height = gdk_pixbuf_get_height (src);
1916 srcrowstride = gdk_pixbuf_get_rowstride (src);
1917 destrowstride = gdk_pixbuf_get_rowstride (dest);
1918 target_pixels = gdk_pixbuf_get_pixels (dest);
1919 original_pixels = gdk_pixbuf_get_pixels (src);
1921 for (i = 0; i < height; i++) {
1922 pixdest = target_pixels + i*destrowstride;
1923 pixsrc = original_pixels + i*srcrowstride;
1924 for (j = 0; j < width; j++) {
1925 r = *(pixsrc++);
1926 g = *(pixsrc++);
1927 b = *(pixsrc++);
1928 val = r + shift;
1929 *(pixdest++) = CLAMP(val, 0, 255);
1930 val = g + shift;
1931 *(pixdest++) = CLAMP(val, 0, 255);
1932 val = b + shift;
1933 *(pixdest++) = CLAMP(val, 0, 255);
1934 if (has_alpha)
1935 *(pixdest++) = *(pixsrc++);
1940 static void
1941 pidgin_status_box_size_allocate(GtkWidget *widget,
1942 GtkAllocation *allocation)
1944 PidginStatusBox *status_box = PIDGIN_STATUS_BOX(widget);
1945 GtkRequisition req = {0,0};
1946 GtkAllocation parent_alc, box_alc, icon_alc;
1947 gint border_width = gtk_container_get_border_width(GTK_CONTAINER (widget));
1949 gtk_widget_get_preferred_size(status_box->toggle_button, NULL, &req);
1950 /* Make this icon the same size as other buddy icons in the list; unless it already wants to be bigger */
1952 req.height = MAX(req.height, 34);
1953 req.height += border_width * 2;
1955 box_alc = *allocation;
1957 box_alc.width -= (border_width * 2);
1958 box_alc.height = MAX(1, ((allocation->height - req.height) - (border_width*2)));
1959 box_alc.x += border_width;
1960 box_alc.y += req.height + border_width;
1961 gtk_widget_size_allocate(status_box->vbox, &box_alc);
1963 parent_alc = *allocation;
1964 parent_alc.height = MAX(1,req.height - (border_width *2));
1965 parent_alc.width -= (border_width * 2);
1966 parent_alc.x += border_width;
1967 parent_alc.y += border_width;
1969 if (status_box->icon_box)
1971 parent_alc.width -= (parent_alc.height + border_width);
1972 icon_alc = parent_alc;
1973 icon_alc.height = MAX(1, icon_alc.height) - 2;
1974 icon_alc.width = icon_alc.height;
1975 icon_alc.x += allocation->width - (icon_alc.width + border_width + 1);
1976 icon_alc.y += 1;
1978 if (status_box->icon_size != icon_alc.height)
1980 status_box->icon_size = icon_alc.height;
1981 pidgin_status_box_redisplay_buddy_icon(status_box);
1983 gtk_widget_size_allocate(status_box->icon_box, &icon_alc);
1985 gtk_widget_size_allocate(status_box->toggle_button, &parent_alc);
1986 gtk_widget_set_allocation(GTK_WIDGET(status_box), allocation);
1989 static gboolean
1990 pidgin_status_box_draw(GtkWidget *widget, cairo_t *cr)
1992 PidginStatusBox *status_box = PIDGIN_STATUS_BOX(widget);
1993 gtk_widget_draw(status_box->toggle_button, cr);
1995 gtk_container_propagate_draw(GTK_CONTAINER(widget), status_box->vbox, cr);
1997 if (status_box->icon_box) {
1998 gtk_container_propagate_draw(GTK_CONTAINER(widget),
1999 status_box->icon_box, cr);
2001 if (status_box->icon_opaque) {
2002 GtkAllocation allocation;
2003 GtkStyleContext *context;
2005 gtk_widget_get_allocation(status_box->icon_box, &allocation);
2006 context = gtk_widget_get_style_context(widget);
2007 gtk_style_context_add_class(context, GTK_STYLE_CLASS_BUTTON);
2008 gtk_render_frame(context, cr, allocation.x-1, allocation.y-1, 34, 34);
2011 return FALSE;
2014 static void
2015 pidgin_status_box_forall(GtkContainer *container,
2016 gboolean include_internals,
2017 GtkCallback callback,
2018 gpointer callback_data)
2020 PidginStatusBox *status_box = PIDGIN_STATUS_BOX (container);
2022 if (include_internals)
2024 (* callback) (status_box->vbox, callback_data);
2025 (* callback) (status_box->toggle_button, callback_data);
2026 (* callback) (status_box->arrow, callback_data);
2027 if (status_box->icon_box)
2028 (* callback) (status_box->icon_box, callback_data);
2032 GtkWidget *
2033 pidgin_status_box_new()
2035 return g_object_new(PIDGIN_TYPE_STATUS_BOX, "account", NULL,
2036 "iconsel", TRUE, NULL);
2039 GtkWidget *
2040 pidgin_status_box_new_with_account(PurpleAccount *account)
2042 return g_object_new(PIDGIN_TYPE_STATUS_BOX, "account", account,
2043 "iconsel", TRUE, NULL);
2047 * pidgin_status_box_add:
2048 * @status_box: The status box itself.
2049 * @type: A PidginStatusBoxItemType.
2050 * @pixbuf: The icon to associate with this row in the menu. The
2051 * function will try to decide a pixbuf if none is given.
2052 * @title: The title of this item. For the primitive entries,
2053 * this is something like "Available" or "Away." For
2054 * the saved statuses, this is something like
2055 * "My favorite away message!" This should be
2056 * plaintext (non-markedup) (this function escapes it).
2057 * @desc: The secondary text for this item. This will be
2058 * placed on the row below the title, in a dimmer
2059 * font (generally gray). This text should be plaintext
2060 * (non-markedup) (this function escapes it).
2061 * @data: Data to be associated with this row in the dropdown
2062 * menu. For primitives this is the value of the
2063 * PurpleStatusPrimitive. For saved statuses this is the
2064 * creation timestamp.
2066 * Add a row to the dropdown menu.
2068 void
2069 pidgin_status_box_add(PidginStatusBox *status_box, PidginStatusBoxItemType type, GdkPixbuf *pixbuf,
2070 const char *title, const char *desc, gpointer data)
2072 GtkTreeIter iter;
2073 char *text;
2074 const char *stock = NULL;
2076 if (desc == NULL)
2078 text = g_markup_escape_text(title, -1);
2080 else
2082 const char *aa_color;
2083 gchar *escaped_title, *escaped_desc;
2085 aa_color = pidgin_get_dim_grey_string(GTK_WIDGET(status_box));
2087 escaped_title = g_markup_escape_text(title, -1);
2088 escaped_desc = g_markup_escape_text(desc, -1);
2089 text = g_strdup_printf("%s - <span color=\"%s\" size=\"smaller\">%s</span>",
2090 escaped_title,
2091 aa_color, escaped_desc);
2092 g_free(escaped_title);
2093 g_free(escaped_desc);
2096 if (!pixbuf) {
2097 PurpleStatusPrimitive prim = PURPLE_STATUS_UNSET;
2098 if (type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE) {
2099 prim = GPOINTER_TO_INT(data);
2100 } else if (type == PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR ||
2101 type == PIDGIN_STATUS_BOX_TYPE_POPULAR) {
2102 PurpleSavedStatus *saved = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
2103 if (saved) {
2104 prim = purple_savedstatus_get_primitive_type(saved);
2108 stock = pidgin_stock_id_from_status_primitive(prim);
2111 gtk_list_store_append(status_box->dropdown_store, &iter);
2112 gtk_list_store_set(status_box->dropdown_store, &iter,
2113 TYPE_COLUMN, type,
2114 ICON_STOCK_COLUMN, stock,
2115 TEXT_COLUMN, text,
2116 TITLE_COLUMN, title,
2117 DESC_COLUMN, desc,
2118 DATA_COLUMN, data,
2119 EMBLEM_VISIBLE_COLUMN, type == PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR,
2120 EMBLEM_COLUMN, GTK_STOCK_SAVE,
2121 -1);
2122 g_free(text);
2125 void
2126 pidgin_status_box_add_separator(PidginStatusBox *status_box)
2128 /* Don't do anything unless GTK actually supports
2129 * gtk_combo_box_set_row_separator_func */
2130 GtkTreeIter iter;
2132 gtk_list_store_append(status_box->dropdown_store, &iter);
2133 gtk_list_store_set(status_box->dropdown_store, &iter,
2134 TYPE_COLUMN, PIDGIN_STATUS_BOX_TYPE_SEPARATOR,
2135 -1);
2138 void
2139 pidgin_status_box_set_network_available(PidginStatusBox *status_box, gboolean available)
2141 if (!status_box)
2142 return;
2143 status_box->network_available = available;
2144 pidgin_status_box_refresh(status_box);
2147 void
2148 pidgin_status_box_set_connecting(PidginStatusBox *status_box, gboolean connecting)
2150 if (!status_box)
2151 return;
2152 status_box->connecting = connecting;
2153 pidgin_status_box_refresh(status_box);
2156 static void
2157 pixbuf_size_prepared_cb(GdkPixbufLoader *loader, int width, int height, gpointer data)
2159 int w, h;
2160 GtkIconSize icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM);
2161 gtk_icon_size_lookup(icon_size, &w, &h);
2162 if (height > width)
2163 w = width * h / height;
2164 else if (width > height)
2165 h = height * w / width;
2166 gdk_pixbuf_loader_set_size(loader, w, h);
2169 static void
2170 pidgin_status_box_redisplay_buddy_icon(PidginStatusBox *status_box)
2173 /* This is sometimes called before the box is shown, and we will not have a size */
2174 if (status_box->icon_size <= 0)
2175 return;
2177 if (status_box->buddy_icon)
2178 g_object_unref(status_box->buddy_icon);
2179 if (status_box->buddy_icon_hover)
2180 g_object_unref(status_box->buddy_icon_hover);
2181 status_box->buddy_icon = NULL;
2182 status_box->buddy_icon_hover = NULL;
2184 if (status_box->buddy_icon_img != NULL)
2186 GdkPixbufLoader *loader;
2187 GError *error = NULL;
2189 loader = gdk_pixbuf_loader_new();
2191 g_signal_connect(G_OBJECT(loader), "size-prepared", G_CALLBACK(pixbuf_size_prepared_cb), NULL);
2192 if (!gdk_pixbuf_loader_write(loader,
2193 purple_image_get_data(status_box->buddy_icon_img),
2194 purple_image_get_size(status_box->buddy_icon_img),
2195 &error) || error)
2197 purple_debug_warning("gtkstatusbox",
2198 "gdk_pixbuf_loader_write() failed with size=%"
2199 G_GSIZE_FORMAT ": %s", purple_image_get_size(
2200 status_box->buddy_icon_img),
2201 error ? error->message : "(no error message)");
2202 if (error)
2203 g_error_free(error);
2204 } else if (!gdk_pixbuf_loader_close(loader, &error) || error) {
2205 purple_debug_warning("gtkstatusbox",
2206 "gdk_pixbuf_loader_close() failed for image of "
2207 "size %" G_GSIZE_FORMAT ": %s",
2208 purple_image_get_size(status_box->buddy_icon_img),
2209 error ? error->message : "(no error message)");
2210 if (error)
2211 g_error_free(error);
2212 } else {
2213 GdkPixbuf *buf, *scale;
2214 int scale_width, scale_height;
2216 buf = gdk_pixbuf_loader_get_pixbuf(loader);
2217 scale_width = gdk_pixbuf_get_width(buf);
2218 scale_height = gdk_pixbuf_get_height(buf);
2219 scale = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
2220 gdk_pixbuf_fill(scale, 0x00000000);
2221 gdk_pixbuf_copy_area(buf, 0, 0, scale_width, scale_height, scale, 0, 0);
2222 if (pidgin_gdk_pixbuf_is_opaque(scale))
2223 pidgin_gdk_pixbuf_make_round(scale);
2224 status_box->buddy_icon = scale;
2227 g_object_unref(loader);
2230 if (status_box->buddy_icon == NULL)
2232 /* Show a placeholder icon */
2233 GtkIconSize icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL);
2234 status_box->buddy_icon = gtk_widget_render_icon(GTK_WIDGET(status_box),
2235 PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR,
2236 icon_size, "PidginStatusBox");
2239 if (status_box->buddy_icon != NULL) {
2240 status_box->icon_opaque = pidgin_gdk_pixbuf_is_opaque(status_box->buddy_icon);
2241 gtk_image_set_from_pixbuf(GTK_IMAGE(status_box->icon), status_box->buddy_icon);
2242 status_box->buddy_icon_hover = gdk_pixbuf_copy(status_box->buddy_icon);
2243 do_colorshift(status_box->buddy_icon_hover, status_box->buddy_icon_hover, 32);
2244 gtk_widget_queue_resize(GTK_WIDGET(status_box));
2248 void
2249 pidgin_status_box_set_buddy_icon(PidginStatusBox *status_box, PurpleImage *img)
2251 if (status_box->buddy_icon_img)
2252 g_object_unref(status_box->buddy_icon_img);
2253 status_box->buddy_icon_img = img;
2254 if (status_box->buddy_icon_img != NULL)
2255 g_object_ref(status_box->buddy_icon_img);
2257 pidgin_status_box_redisplay_buddy_icon(status_box);
2260 void
2261 pidgin_status_box_pulse_connecting(PidginStatusBox *status_box)
2263 if (!status_box)
2264 return;
2265 if (!connecting_stock_ids[++status_box->connecting_index])
2266 status_box->connecting_index = 0;
2267 pidgin_status_box_refresh(status_box);
2270 static void
2271 pidgin_status_box_pulse_typing(PidginStatusBox *status_box)
2273 if (!typing_stock_ids[++status_box->typing_index])
2274 status_box->typing_index = 0;
2275 pidgin_status_box_refresh(status_box);
2278 static void
2279 activate_currently_selected_status(PidginStatusBox *status_box)
2281 PidginStatusBoxItemType type;
2282 gpointer data;
2283 gchar *title;
2284 GtkTreeIter iter;
2285 GtkTreePath *path;
2286 char *message;
2287 PurpleSavedStatus *saved_status = NULL;
2288 gboolean changed = TRUE;
2290 path = gtk_tree_row_reference_get_path(status_box->active_row);
2291 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
2292 return;
2293 gtk_tree_path_free(path);
2295 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
2296 TYPE_COLUMN, &type,
2297 DATA_COLUMN, &data,
2298 -1);
2301 * If the currently selected status is "New..." or
2302 * "Saved..." or a popular status then do nothing.
2303 * Popular statuses are
2304 * activated elsewhere, and we update the status_box
2305 * accordingly by connecting to the savedstatus-changed
2306 * signal and then calling status_menu_refresh_iter()
2308 if (type != PIDGIN_STATUS_BOX_TYPE_PRIMITIVE)
2309 return;
2311 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
2312 TITLE_COLUMN, &title, -1);
2314 message = pidgin_status_box_get_message(status_box);
2315 if (!message || !*message)
2317 gtk_widget_hide(status_box->vbox);
2318 status_box->webview_visible = FALSE;
2319 g_free(message);
2320 message = NULL;
2323 if (status_box->account == NULL) {
2324 PurpleStatusType *acct_status_type = NULL;
2325 const char *id = NULL; /* id of acct_status_type */
2326 PurpleStatusPrimitive primitive = GPOINTER_TO_INT(data);
2327 /* Global */
2328 /* Save the newly selected status to prefs.xml and status.xml */
2330 /* Has the status really been changed? */
2331 if (status_box->token_status_account) {
2332 gint active;
2333 PurpleStatus *status;
2334 GtkTreePath *path = gtk_tree_row_reference_get_path(status_box->active_row);
2335 active = gtk_tree_path_get_indices(path)[0];
2337 gtk_tree_path_free(path);
2339 status = purple_account_get_active_status(status_box->token_status_account);
2341 acct_status_type = find_status_type_by_index(status_box->token_status_account, active);
2342 id = purple_status_type_get_id(acct_status_type);
2344 if (g_str_equal(id, purple_status_get_id(status)) &&
2345 purple_strequal(message, purple_status_get_attr_string(status, "message")))
2347 /* Selected status and previous status is the same */
2348 PurpleSavedStatus *ss = purple_savedstatus_get_current();
2349 /* Make sure that statusbox displays the correct thing.
2350 * It can get messed up if the previous selection was a
2351 * saved status that wasn't supported by this account */
2352 if ((purple_savedstatus_get_primitive_type(ss) == primitive)
2353 && purple_savedstatus_is_transient(ss)
2354 && purple_savedstatus_has_substatuses(ss))
2355 changed = FALSE;
2357 } else {
2358 saved_status = purple_savedstatus_get_current();
2359 if (purple_savedstatus_get_primitive_type(saved_status) == primitive &&
2360 !purple_savedstatus_has_substatuses(saved_status) &&
2361 purple_strequal(purple_savedstatus_get_message(saved_status), message))
2363 changed = FALSE;
2367 if (changed)
2369 /* Manually find the appropriate transient status */
2370 if (status_box->token_status_account) {
2371 GList *iter = purple_savedstatuses_get_all();
2372 GList *tmp, *active_accts = purple_accounts_get_all_active();
2374 for (; iter != NULL; iter = iter->next) {
2375 PurpleSavedStatus *ss = iter->data;
2376 const char *ss_msg = purple_savedstatus_get_message(ss);
2377 /* find a known transient status that is the same as the
2378 * new selected one */
2379 if ((purple_savedstatus_get_primitive_type(ss) == primitive) && purple_savedstatus_is_transient(ss) &&
2380 purple_savedstatus_has_substatuses(ss) && /* Must have substatuses */
2381 purple_strequal(ss_msg, message))
2383 gboolean found = FALSE;
2384 /* this status must have substatuses for all the active accts */
2385 for(tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2386 PurpleAccount *acct = tmp->data;
2387 PurpleSavedStatusSub *sub = purple_savedstatus_get_substatus(ss, acct);
2388 if (sub) {
2389 const PurpleStatusType *sub_type =
2390 purple_savedstatus_substatus_get_status_type(sub);
2391 const char *subtype_status_id = purple_status_type_get_id(sub_type);
2392 if (purple_strequal(subtype_status_id, id)) {
2393 found = TRUE;
2394 break;
2399 if (found) {
2400 saved_status = ss;
2401 break;
2406 g_list_free(active_accts);
2408 } else {
2409 /* If we've used this type+message before, lookup the transient status */
2410 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2413 /* If this type+message is unique then create a new transient saved status */
2414 if (saved_status == NULL)
2416 saved_status = purple_savedstatus_new(NULL, primitive);
2417 purple_savedstatus_set_message(saved_status, message);
2418 if (status_box->token_status_account) {
2419 GList *tmp, *active_accts = purple_accounts_get_all_active();
2420 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2421 purple_savedstatus_set_substatus(saved_status,
2422 (PurpleAccount*) tmp->data, acct_status_type, message);
2424 g_list_free(active_accts);
2428 /* Set the status for each account */
2429 purple_savedstatus_activate(saved_status);
2431 } else {
2432 /* Per-account */
2433 gint active;
2434 PurpleStatusType *status_type;
2435 PurpleStatus *status;
2436 const char *id = NULL;
2438 status = purple_account_get_active_status(status_box->account);
2440 active = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(status_box), "active"));
2442 status_type = find_status_type_by_index(status_box->account, active);
2443 id = purple_status_type_get_id(status_type);
2445 if (g_str_equal(id, purple_status_get_id(status)) &&
2446 purple_strequal(message, purple_status_get_attr_string(status, "message")))
2448 /* Selected status and previous status is the same */
2449 changed = FALSE;
2452 if (changed)
2454 if (message)
2455 purple_account_set_status(status_box->account, id,
2456 TRUE, "message", message, NULL);
2457 else
2458 purple_account_set_status(status_box->account, id,
2459 TRUE, NULL);
2461 saved_status = purple_savedstatus_get_current();
2462 if (purple_savedstatus_is_transient(saved_status))
2463 purple_savedstatus_set_substatus(saved_status, status_box->account,
2464 status_type, message);
2468 g_free(title);
2469 g_free(message);
2472 static void update_size(PidginStatusBox *status_box)
2474 #if 0
2475 /* TODO WebKit Sizing */
2476 GtkTextBuffer *buffer;
2477 GtkTextIter iter;
2478 int display_lines;
2479 int lines;
2480 GdkRectangle oneline;
2481 int height;
2482 int pad_top, pad_inside, pad_bottom;
2483 gboolean interior_focus;
2484 int focus_width;
2485 #endif
2487 if (!status_box->webview_visible)
2489 if (status_box->vbox != NULL)
2490 gtk_widget_set_size_request(status_box->vbox, -1, -1);
2491 return;
2494 #if 0
2495 /* TODO WebKit: Entry sizing */
2496 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(status_box->webview));
2498 height = 0;
2499 display_lines = 1;
2500 gtk_text_buffer_get_start_iter(buffer, &iter);
2501 do {
2502 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(status_box->webview), &iter, &oneline);
2503 height += oneline.height;
2504 display_lines++;
2505 } while (display_lines <= 4 &&
2506 gtk_text_view_forward_display_line(GTK_TEXT_VIEW(status_box->webview), &iter));
2509 * This check fixes the case where the last character entered is a
2510 * newline (shift+return). For some reason the
2511 * gtk_text_view_forward_display_line() function doesn't treat this
2512 * like a new line, and so we think the input box only needs to be
2513 * two lines instead of three, for example. So we check if the
2514 * last character was a newline and add some extra height if so.
2516 if (display_lines <= 4
2517 && gtk_text_iter_backward_char(&iter)
2518 && gtk_text_iter_get_char(&iter) == '\n')
2520 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(status_box->webview), &iter, &oneline);
2521 height += oneline.height;
2522 display_lines++;
2525 lines = gtk_text_buffer_get_line_count(buffer);
2527 /* Show a maximum of 4 lines */
2528 lines = MIN(lines, 4);
2529 display_lines = MIN(display_lines, 4);
2531 pad_top = gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(status_box->webview));
2532 pad_bottom = gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(status_box->webview));
2533 pad_inside = gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(status_box->webview));
2535 height += (pad_top + pad_bottom) * lines;
2536 height += (pad_inside) * (display_lines - lines);
2538 gtk_widget_style_get(status_box->webview,
2539 "interior-focus", &interior_focus,
2540 "focus-line-width", &focus_width,
2541 NULL);
2542 if (!interior_focus)
2543 height += 2 * focus_width;
2545 gtk_widget_set_size_request(status_box->vbox, -1, height + PIDGIN_HIG_BOX_SPACE);
2546 #endif
2547 gtk_widget_set_size_request(status_box->vbox, -1, -1);
2550 static void remove_typing_cb(PidginStatusBox *status_box)
2552 if (status_box->typing == 0)
2554 /* Nothing has changed, so we don't need to do anything */
2555 status_menu_refresh_iter(status_box, FALSE);
2556 return;
2559 #if 0
2560 /* TODO WebKit: Doesn't do this? */
2561 pidgin_webview_set_populate_primary_clipboard(
2562 PIDGIN_WEBVIEW(status_box->webview), TRUE);
2563 #endif
2565 purple_timeout_remove(status_box->typing);
2566 status_box->typing = 0;
2568 activate_currently_selected_status(status_box);
2569 pidgin_status_box_refresh(status_box);
2572 static void pidgin_status_box_changed(PidginStatusBox *status_box)
2574 GtkTreePath *path = gtk_tree_row_reference_get_path(status_box->active_row);
2575 GtkTreeIter iter;
2576 PidginStatusBoxItemType type;
2577 gpointer data;
2578 GList *accounts = NULL, *node;
2579 int active;
2580 gboolean wastyping = FALSE;
2583 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
2584 return;
2585 active = gtk_tree_path_get_indices(path)[0];
2586 gtk_tree_path_free(path);
2587 g_object_set_data(G_OBJECT(status_box), "active", GINT_TO_POINTER(active));
2589 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
2590 TYPE_COLUMN, &type,
2591 DATA_COLUMN, &data,
2592 -1);
2593 if ((wastyping = (status_box->typing != 0)))
2594 purple_timeout_remove(status_box->typing);
2595 status_box->typing = 0;
2597 if (gtk_widget_get_sensitive(GTK_WIDGET(status_box)))
2599 if (type == PIDGIN_STATUS_BOX_TYPE_POPULAR || type == PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR)
2601 PurpleSavedStatus *saved;
2602 saved = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
2603 g_return_if_fail(saved != NULL);
2604 purple_savedstatus_activate(saved);
2605 return;
2608 if (type == PIDGIN_STATUS_BOX_TYPE_CUSTOM)
2610 PurpleSavedStatus *saved_status;
2611 saved_status = purple_savedstatus_get_current();
2612 if (purple_savedstatus_get_primitive_type(saved_status) == PURPLE_STATUS_AVAILABLE)
2613 saved_status = purple_savedstatus_new(NULL, PURPLE_STATUS_AWAY);
2614 pidgin_status_editor_show(FALSE,
2615 purple_savedstatus_is_transient(saved_status)
2616 ? saved_status : NULL);
2617 status_menu_refresh_iter(status_box, wastyping);
2618 if (wastyping)
2619 pidgin_status_box_refresh(status_box);
2620 return;
2623 if (type == PIDGIN_STATUS_BOX_TYPE_SAVED)
2625 pidgin_status_window_show();
2626 status_menu_refresh_iter(status_box, wastyping);
2627 if (wastyping)
2628 pidgin_status_box_refresh(status_box);
2629 return;
2634 * Show the message box whenever the primitive allows for a
2635 * message attribute on any protocol that is enabled,
2636 * or our protocol, if we have account set
2638 if (status_box->account)
2639 accounts = g_list_prepend(accounts, status_box->account);
2640 else
2641 accounts = purple_accounts_get_all_active();
2642 status_box->webview_visible = FALSE;
2643 for (node = accounts; node != NULL; node = node->next)
2645 PurpleAccount *account;
2646 PurpleStatusType *status_type;
2648 account = node->data;
2649 status_type = purple_account_get_status_type_with_primitive(account, GPOINTER_TO_INT(data));
2650 if ((status_type != NULL) &&
2651 (purple_status_type_get_attr(status_type, "message") != NULL))
2653 status_box->webview_visible = TRUE;
2654 break;
2657 g_list_free(accounts);
2659 if (gtk_widget_get_sensitive(GTK_WIDGET(status_box)))
2661 if (status_box->webview_visible)
2663 gtk_widget_show_all(status_box->vbox);
2664 status_box->typing = purple_timeout_add_seconds(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
2665 gtk_widget_grab_focus(status_box->webview);
2666 #if 0
2667 /* TODO WebKit: Doesn't do this? */
2668 pidgin_webview_set_populate_primary_clipboard(
2669 PIDGIN_WEBVIEW(status_box->webview), FALSE);
2670 #endif
2672 webkit_web_view_select_all(WEBKIT_WEB_VIEW(status_box->webview));
2674 else
2676 gtk_widget_hide(status_box->vbox);
2677 activate_currently_selected_status(status_box); /* This is where we actually set the status */
2680 pidgin_status_box_refresh(status_box);
2683 static gint
2684 get_statusbox_index(PidginStatusBox *box, PurpleSavedStatus *saved_status)
2686 gint index = -1;
2688 switch (purple_savedstatus_get_primitive_type(saved_status))
2690 /* In reverse order */
2691 case PURPLE_STATUS_OFFLINE:
2692 index++;
2693 /* fall through */
2694 case PURPLE_STATUS_INVISIBLE:
2695 index++;
2696 /* fall through */
2697 case PURPLE_STATUS_UNAVAILABLE:
2698 index++;
2699 /* fall through */
2700 case PURPLE_STATUS_AWAY:
2701 index++;
2702 /* fall through */
2703 case PURPLE_STATUS_AVAILABLE:
2704 index++;
2705 break;
2706 default:
2707 break;
2710 return index;
2713 static void
2714 webview_changed_cb(PidginWebView *webview, void *data)
2716 PidginStatusBox *status_box = (PidginStatusBox*)data;
2717 if (gtk_widget_get_sensitive(GTK_WIDGET(status_box)))
2719 if (status_box->typing != 0) {
2720 pidgin_status_box_pulse_typing(status_box);
2721 purple_timeout_remove(status_box->typing);
2723 status_box->typing = purple_timeout_add_seconds(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
2725 pidgin_status_box_refresh(status_box);
2728 static void
2729 webview_format_changed_cb(PidginWebView *webview, PidginWebViewButtons buttons, void *data)
2731 webview_changed_cb(NULL, data);
2734 char *pidgin_status_box_get_message(PidginStatusBox *status_box)
2736 if (status_box->webview_visible)
2737 return g_strstrip(pidgin_webview_get_body_text(PIDGIN_WEBVIEW(status_box->webview)));
2738 else
2739 return NULL;