I guess I'll release this later today. Is that ok with everyone?
[pidgin-git.git] / pidgin / gtkstatusbox.c
blobff75b29c195f9752df41ebbc21bf1e6ed6b48805
1 /*
2 * @file gtkstatusbox.c GTK+ Status Selection Widget
3 * @ingroup pidgin
4 */
6 /* pidgin
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
28 * The status box is made up of two main pieces:
29 * - The box that displays the current status, which is made
30 * of a GtkListStore ("status_box->store") and GtkCellView
31 * ("status_box->cell_view"). There is always exactly 1 row
32 * in this list store. Only the TYPE_ICON and TYPE_TEXT
33 * columns are used in this list store.
34 * - The dropdown menu that lets users select a status, which
35 * is made of a GtkComboBox ("status_box") and GtkListStore
36 * ("status_box->dropdown_store"). This dropdown is shown
37 * when the user clicks on the box that displays the current
38 * status. This list store contains one row for Available,
39 * one row for Away, etc., a few rows for popular statuses,
40 * and the "New..." and "Saved..." options.
43 #include <gdk/gdkkeysyms.h>
45 #include "internal.h"
47 #include "account.h"
48 #include "buddyicon.h"
49 #include "core.h"
50 #include "imgstore.h"
51 #include "network.h"
52 #include "request.h"
53 #include "savedstatuses.h"
54 #include "status.h"
55 #include "debug.h"
57 #include "pidgin.h"
58 #include "gtksavedstatuses.h"
59 #include "pidginstock.h"
60 #include "gtkstatusbox.h"
61 #include "gtkutils.h"
63 #ifdef USE_GTKSPELL
64 # include <gtkspell/gtkspell.h>
65 # ifdef _WIN32
66 # include "wspell.h"
67 # endif
68 #endif
70 /* Timeout for typing notifications in seconds */
71 #define TYPING_TIMEOUT 4
73 static void imhtml_changed_cb(GtkTextBuffer *buffer, void *data);
74 static void imhtml_format_changed_cb(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons, void *data);
75 static void remove_typing_cb(PidginStatusBox *box);
76 static void update_size (PidginStatusBox *box);
77 static gint get_statusbox_index(PidginStatusBox *box, PurpleSavedStatus *saved_status);
78 static PurpleAccount* check_active_accounts_for_identical_statuses(void);
80 static void pidgin_status_box_pulse_typing(PidginStatusBox *status_box);
81 static void pidgin_status_box_refresh(PidginStatusBox *status_box);
82 static void status_menu_refresh_iter(PidginStatusBox *status_box, gboolean status_changed);
83 static void pidgin_status_box_regenerate(PidginStatusBox *status_box, gboolean status_changed);
84 static void pidgin_status_box_changed(PidginStatusBox *box);
85 static void pidgin_status_box_size_request (GtkWidget *widget, GtkRequisition *requisition);
86 static void pidgin_status_box_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
87 static gboolean pidgin_status_box_expose_event (GtkWidget *widget, GdkEventExpose *event);
88 static void pidgin_status_box_redisplay_buddy_icon(PidginStatusBox *status_box);
89 static void pidgin_status_box_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data);
90 static void pidgin_status_box_popup(PidginStatusBox *box);
91 static void pidgin_status_box_popdown(PidginStatusBox *box);
93 static void do_colorshift (GdkPixbuf *dest, GdkPixbuf *src, int shift);
94 static void icon_choose_cb(const char *filename, gpointer data);
95 static void remove_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box);
96 static void choose_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box);
98 enum {
99 /** A PidginStatusBoxItemType */
100 TYPE_COLUMN,
102 /** This is the stock-id for the icon. */
103 ICON_STOCK_COLUMN,
106 * This is a GdkPixbuf (the other columns are strings).
107 * This column is visible.
109 ICON_COLUMN,
111 /** The text displayed on the status box. This column is visible. */
112 TEXT_COLUMN,
114 /** The plain-English title of this item */
115 TITLE_COLUMN,
117 /** A plain-English description of this item */
118 DESC_COLUMN,
121 * This value depends on TYPE_COLUMN. For POPULAR types,
122 * this is the creation time. For PRIMITIVE types,
123 * this is the PurpleStatusPrimitive.
125 DATA_COLUMN,
128 * This column stores the GdkPixbuf for the status emblem. Currently only 'saved' is stored.
129 * In the GtkTreeModel for the dropdown, this is the stock-id (gchararray), and for the
130 * GtkTreeModel for the cell_view (for the account-specific statusbox), this is the prpl-icon
131 * (GdkPixbuf) of the account.
133 EMBLEM_COLUMN,
136 * This column stores whether to show the emblem.
138 EMBLEM_VISIBLE_COLUMN,
140 NUM_COLUMNS
143 enum {
144 PROP_0,
145 PROP_ACCOUNT,
146 PROP_ICON_SEL,
149 static char *typing_stock_ids[7] = {
150 PIDGIN_STOCK_ANIMATION_TYPING0,
151 PIDGIN_STOCK_ANIMATION_TYPING1,
152 PIDGIN_STOCK_ANIMATION_TYPING2,
153 PIDGIN_STOCK_ANIMATION_TYPING3,
154 PIDGIN_STOCK_ANIMATION_TYPING4,
155 NULL
158 static char *connecting_stock_ids[] = {
159 PIDGIN_STOCK_ANIMATION_CONNECT0,
160 PIDGIN_STOCK_ANIMATION_CONNECT1,
161 PIDGIN_STOCK_ANIMATION_CONNECT2,
162 PIDGIN_STOCK_ANIMATION_CONNECT3,
163 PIDGIN_STOCK_ANIMATION_CONNECT4,
164 PIDGIN_STOCK_ANIMATION_CONNECT5,
165 PIDGIN_STOCK_ANIMATION_CONNECT6,
166 PIDGIN_STOCK_ANIMATION_CONNECT7,
167 PIDGIN_STOCK_ANIMATION_CONNECT8,
168 PIDGIN_STOCK_ANIMATION_CONNECT9,
169 PIDGIN_STOCK_ANIMATION_CONNECT10,
170 PIDGIN_STOCK_ANIMATION_CONNECT11,
171 PIDGIN_STOCK_ANIMATION_CONNECT12,
172 PIDGIN_STOCK_ANIMATION_CONNECT13,
173 PIDGIN_STOCK_ANIMATION_CONNECT14,
174 PIDGIN_STOCK_ANIMATION_CONNECT15,
175 PIDGIN_STOCK_ANIMATION_CONNECT16,
176 PIDGIN_STOCK_ANIMATION_CONNECT17,
177 PIDGIN_STOCK_ANIMATION_CONNECT18,
178 PIDGIN_STOCK_ANIMATION_CONNECT19,
179 PIDGIN_STOCK_ANIMATION_CONNECT20,
180 PIDGIN_STOCK_ANIMATION_CONNECT21,
181 PIDGIN_STOCK_ANIMATION_CONNECT22,
182 PIDGIN_STOCK_ANIMATION_CONNECT23,
183 PIDGIN_STOCK_ANIMATION_CONNECT24,
184 PIDGIN_STOCK_ANIMATION_CONNECT25,
185 PIDGIN_STOCK_ANIMATION_CONNECT26,
186 PIDGIN_STOCK_ANIMATION_CONNECT27,
187 PIDGIN_STOCK_ANIMATION_CONNECT28,
188 PIDGIN_STOCK_ANIMATION_CONNECT29,
189 PIDGIN_STOCK_ANIMATION_CONNECT30,
190 NULL
193 static GtkContainerClass *parent_class = NULL;
195 static void pidgin_status_box_class_init (PidginStatusBoxClass *klass);
196 static void pidgin_status_box_init (PidginStatusBox *status_box);
198 GType
199 pidgin_status_box_get_type (void)
201 static GType status_box_type = 0;
203 if (!status_box_type)
205 static const GTypeInfo status_box_info =
207 sizeof (PidginStatusBoxClass),
208 NULL, /* base_init */
209 NULL, /* base_finalize */
210 (GClassInitFunc) pidgin_status_box_class_init,
211 NULL, /* class_finalize */
212 NULL, /* class_data */
213 sizeof (PidginStatusBox),
215 (GInstanceInitFunc) pidgin_status_box_init,
216 NULL /* value_table */
219 status_box_type = g_type_register_static(GTK_TYPE_CONTAINER,
220 "PidginStatusBox",
221 &status_box_info,
225 return status_box_type;
228 static void
229 pidgin_status_box_get_property(GObject *object, guint param_id,
230 GValue *value, GParamSpec *psec)
232 PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(object);
234 switch (param_id) {
235 case PROP_ACCOUNT:
236 g_value_set_pointer(value, statusbox->account);
237 break;
238 case PROP_ICON_SEL:
239 g_value_set_boolean(value, statusbox->icon_box != NULL);
240 break;
241 default:
242 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, psec);
243 break;
247 static void
248 update_to_reflect_account_status(PidginStatusBox *status_box, PurpleAccount *account, PurpleStatus *newstatus)
250 GList *l;
251 int status_no = -1;
252 const PurpleStatusType *statustype = NULL;
253 const char *message;
255 statustype = purple_status_type_find_with_id((GList *)purple_account_get_status_types(account),
256 (char *)purple_status_type_get_id(purple_status_get_type(newstatus)));
258 for (l = purple_account_get_status_types(account); l != NULL; l = l->next) {
259 PurpleStatusType *status_type = (PurpleStatusType *)l->data;
261 if (!purple_status_type_is_user_settable(status_type) ||
262 purple_status_type_is_independent(status_type))
263 continue;
264 status_no++;
265 if (statustype == status_type)
266 break;
269 gtk_imhtml_set_populate_primary_clipboard(
270 GTK_IMHTML(status_box->imhtml), TRUE);
272 if (status_no != -1) {
273 GtkTreePath *path;
274 gtk_widget_set_sensitive(GTK_WIDGET(status_box), FALSE);
275 path = gtk_tree_path_new_from_indices(status_no, -1);
276 if (status_box->active_row)
277 gtk_tree_row_reference_free(status_box->active_row);
278 status_box->active_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box->dropdown_store), path);
279 gtk_tree_path_free(path);
281 message = purple_status_get_attr_string(newstatus, "message");
283 if (!message || !*message)
285 gtk_widget_hide_all(status_box->vbox);
286 status_box->imhtml_visible = FALSE;
288 else
290 gtk_widget_show_all(status_box->vbox);
291 status_box->imhtml_visible = TRUE;
292 gtk_imhtml_clear(GTK_IMHTML(status_box->imhtml));
293 gtk_imhtml_clear_formatting(GTK_IMHTML(status_box->imhtml));
294 gtk_imhtml_append_text(GTK_IMHTML(status_box->imhtml), message, 0);
296 gtk_widget_set_sensitive(GTK_WIDGET(status_box), TRUE);
297 pidgin_status_box_refresh(status_box);
301 static void
302 account_status_changed_cb(PurpleAccount *account, PurpleStatus *oldstatus, PurpleStatus *newstatus, PidginStatusBox *status_box)
304 if (status_box->account == account)
305 update_to_reflect_account_status(status_box, account, newstatus);
306 else if (status_box->token_status_account == account)
307 status_menu_refresh_iter(status_box, TRUE);
310 static gboolean
311 icon_box_press_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
313 if (event->button == 3) {
314 GtkWidget *menu_item;
315 const char *path;
317 if (box->icon_box_menu)
318 gtk_widget_destroy(box->icon_box_menu);
320 box->icon_box_menu = gtk_menu_new();
322 menu_item = pidgin_new_item_from_stock(box->icon_box_menu, _("Select Buddy Icon"), GTK_STOCK_ADD,
323 G_CALLBACK(choose_buddy_icon_cb),
324 box, 0, 0, NULL);
326 menu_item = pidgin_new_item_from_stock(box->icon_box_menu, _("Remove"), GTK_STOCK_REMOVE,
327 G_CALLBACK(remove_buddy_icon_cb),
328 box, 0, 0, NULL);
329 if (!(path = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon"))
330 || !*path)
331 gtk_widget_set_sensitive(menu_item, FALSE);
333 gtk_menu_popup(GTK_MENU(box->icon_box_menu), NULL, NULL, NULL, NULL,
334 event->button, event->time);
336 } else {
337 choose_buddy_icon_cb(widget, box);
339 return FALSE;
342 static void
343 icon_box_dnd_cb(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
344 GtkSelectionData *sd, guint info, guint t, PidginStatusBox *box)
346 gchar *name = (gchar *)sd->data;
348 if ((sd->length >= 0) && (sd->format == 8)) {
349 /* Well, it looks like the drag event was cool.
350 * Let's do something with it */
351 if (!g_ascii_strncasecmp(name, "file://", 7)) {
352 GError *converr = NULL;
353 gchar *tmp, *rtmp;
355 if(!(tmp = g_filename_from_uri(name, NULL, &converr))) {
356 purple_debug(PURPLE_DEBUG_ERROR, "buddyicon", "%s\n",
357 (converr ? converr->message :
358 "g_filename_from_uri error"));
359 return;
361 if ((rtmp = strchr(tmp, '\r')) || (rtmp = strchr(tmp, '\n')))
362 *rtmp = '\0';
363 icon_choose_cb(tmp, box);
364 g_free(tmp);
366 gtk_drag_finish(dc, TRUE, FALSE, t);
368 gtk_drag_finish(dc, FALSE, FALSE, t);
371 static void
372 statusbox_got_url(PurpleUtilFetchUrlData *url_data, gpointer user_data,
373 const gchar *themedata, size_t len, const gchar *error_message)
375 FILE *f;
376 gchar *path;
377 size_t wc;
379 if ((error_message != NULL) || (len == 0))
380 return;
382 f = purple_mkstemp(&path, TRUE);
383 wc = fwrite(themedata, len, 1, f);
384 if (wc != 1) {
385 purple_debug_warning("theme_got_url", "Unable to write theme data.\n");
386 fclose(f);
387 g_unlink(path);
388 g_free(path);
389 return;
391 fclose(f);
393 icon_choose_cb(path, user_data);
395 g_unlink(path);
396 g_free(path);
400 static gboolean
401 statusbox_uri_handler(const char *proto, const char *cmd, GHashTable *params, void *data)
403 const char *src;
405 if (g_ascii_strcasecmp(proto, "aim"))
406 return FALSE;
408 if (g_ascii_strcasecmp(cmd, "buddyicon"))
409 return FALSE;
411 src = g_hash_table_lookup(params, "account");
412 if (src == NULL)
413 return FALSE;
415 purple_util_fetch_url(src, TRUE, NULL, FALSE, statusbox_got_url, data);
416 return TRUE;
419 static gboolean
420 icon_box_enter_cb(GtkWidget *widget, GdkEventCrossing *event, PidginStatusBox *box)
422 gdk_window_set_cursor(widget->window, box->hand_cursor);
423 gtk_image_set_from_pixbuf(GTK_IMAGE(box->icon), box->buddy_icon_hover);
424 return FALSE;
427 static gboolean
428 icon_box_leave_cb(GtkWidget *widget, GdkEventCrossing *event, PidginStatusBox *box)
430 gdk_window_set_cursor(widget->window, box->arrow_cursor);
431 gtk_image_set_from_pixbuf(GTK_IMAGE(box->icon), box->buddy_icon) ;
432 return FALSE;
436 static const GtkTargetEntry dnd_targets[] = {
437 {"text/plain", 0, 0},
438 {"text/uri-list", 0, 1},
439 {"STRING", 0, 2}
442 static void
443 setup_icon_box(PidginStatusBox *status_box)
445 if (status_box->icon_box != NULL)
446 return;
448 status_box->icon = gtk_image_new();
449 status_box->icon_box = gtk_event_box_new();
450 gtk_widget_set_parent(status_box->icon_box, GTK_WIDGET(status_box));
451 gtk_widget_show(status_box->icon_box);
453 #if GTK_CHECK_VERSION(2,12,0)
454 gtk_widget_set_tooltip_text(status_box->icon_box,
455 status_box->account ? _("Click to change your buddyicon for this account.") :
456 _("Click to change your buddyicon for all accounts."));
457 #endif
459 if (status_box->account &&
460 !purple_account_get_bool(status_box->account, "use-global-buddyicon", TRUE))
462 PurpleStoredImage *img = purple_buddy_icons_find_account_icon(status_box->account);
463 pidgin_status_box_set_buddy_icon(status_box, img);
464 purple_imgstore_unref(img);
466 else
468 const char *filename = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon");
469 PurpleStoredImage *img = NULL;
471 if (filename && *filename)
472 img = purple_imgstore_new_from_file(filename);
474 pidgin_status_box_set_buddy_icon(status_box, img);
475 if (img)
477 * purple_imgstore_new gives us a reference and
478 * pidgin_status_box_set_buddy_icon also takes one.
480 purple_imgstore_unref(img);
483 status_box->hand_cursor = gdk_cursor_new (GDK_HAND2);
484 status_box->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR);
486 /* Set up DND */
487 gtk_drag_dest_set(status_box->icon_box,
488 GTK_DEST_DEFAULT_MOTION |
489 GTK_DEST_DEFAULT_DROP,
490 dnd_targets,
491 sizeof(dnd_targets) / sizeof(GtkTargetEntry),
492 GDK_ACTION_COPY);
494 g_signal_connect(G_OBJECT(status_box->icon_box), "drag_data_received", G_CALLBACK(icon_box_dnd_cb), status_box);
495 g_signal_connect(G_OBJECT(status_box->icon_box), "enter-notify-event", G_CALLBACK(icon_box_enter_cb), status_box);
496 g_signal_connect(G_OBJECT(status_box->icon_box), "leave-notify-event", G_CALLBACK(icon_box_leave_cb), status_box);
497 g_signal_connect(G_OBJECT(status_box->icon_box), "button-press-event", G_CALLBACK(icon_box_press_cb), status_box);
499 gtk_container_add(GTK_CONTAINER(status_box->icon_box), status_box->icon);
500 gtk_widget_show(status_box->icon);
503 static void
504 destroy_icon_box(PidginStatusBox *statusbox)
506 if (statusbox->icon_box == NULL)
507 return;
509 gtk_widget_destroy(statusbox->icon_box);
510 gdk_cursor_unref(statusbox->hand_cursor);
511 gdk_cursor_unref(statusbox->arrow_cursor);
513 purple_imgstore_unref(statusbox->buddy_icon_img);
515 g_object_unref(G_OBJECT(statusbox->buddy_icon));
516 g_object_unref(G_OBJECT(statusbox->buddy_icon_hover));
518 if (statusbox->buddy_icon_sel)
519 gtk_widget_destroy(statusbox->buddy_icon_sel);
521 if (statusbox->icon_box_menu)
522 gtk_widget_destroy(statusbox->icon_box_menu);
524 statusbox->icon = NULL;
525 statusbox->icon_box = NULL;
526 statusbox->icon_box_menu = NULL;
527 statusbox->buddy_icon_img = NULL;
528 statusbox->buddy_icon = NULL;
529 statusbox->buddy_icon_hover = NULL;
530 statusbox->hand_cursor = NULL;
531 statusbox->arrow_cursor = NULL;
534 static void
535 pidgin_status_box_set_property(GObject *object, guint param_id,
536 const GValue *value, GParamSpec *pspec)
538 PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(object);
540 switch (param_id) {
541 case PROP_ICON_SEL:
542 if (g_value_get_boolean(value)) {
543 if (statusbox->account) {
544 PurplePlugin *plug = purple_plugins_find_with_id(purple_account_get_protocol_id(statusbox->account));
545 if (plug) {
546 PurplePluginProtocolInfo *prplinfo = PURPLE_PLUGIN_PROTOCOL_INFO(plug);
547 if (prplinfo && prplinfo->icon_spec.format != NULL)
548 setup_icon_box(statusbox);
550 } else {
551 setup_icon_box(statusbox);
553 } else {
554 destroy_icon_box(statusbox);
556 break;
557 case PROP_ACCOUNT:
558 statusbox->account = g_value_get_pointer(value);
559 if (statusbox->account)
560 statusbox->token_status_account = NULL;
561 else
562 statusbox->token_status_account = check_active_accounts_for_identical_statuses();
564 pidgin_status_box_regenerate(statusbox, TRUE);
566 break;
567 default:
568 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
569 break;
573 static void
574 pidgin_status_box_finalize(GObject *obj)
576 PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(obj);
577 int i;
579 purple_signals_disconnect_by_handle(statusbox);
580 purple_prefs_disconnect_by_handle(statusbox);
582 destroy_icon_box(statusbox);
584 if (statusbox->active_row)
585 gtk_tree_row_reference_free(statusbox->active_row);
587 for (i = 0; i < G_N_ELEMENTS(statusbox->connecting_pixbufs); i++) {
588 if (statusbox->connecting_pixbufs[i] != NULL)
589 g_object_unref(G_OBJECT(statusbox->connecting_pixbufs[i]));
592 for (i = 0; i < G_N_ELEMENTS(statusbox->typing_pixbufs); i++) {
593 if (statusbox->typing_pixbufs[i] != NULL)
594 g_object_unref(G_OBJECT(statusbox->typing_pixbufs[i]));
597 g_object_unref(G_OBJECT(statusbox->store));
598 g_object_unref(G_OBJECT(statusbox->dropdown_store));
599 G_OBJECT_CLASS(parent_class)->finalize(obj);
602 static GType
603 pidgin_status_box_child_type (GtkContainer *container)
605 return GTK_TYPE_WIDGET;
608 static void
609 pidgin_status_box_class_init (PidginStatusBoxClass *klass)
611 GObjectClass *object_class;
612 GtkWidgetClass *widget_class;
613 GtkContainerClass *container_class = (GtkContainerClass*)klass;
615 parent_class = g_type_class_peek_parent(klass);
617 widget_class = (GtkWidgetClass*)klass;
618 widget_class->size_request = pidgin_status_box_size_request;
619 widget_class->size_allocate = pidgin_status_box_size_allocate;
620 widget_class->expose_event = pidgin_status_box_expose_event;
622 container_class->child_type = pidgin_status_box_child_type;
623 container_class->forall = pidgin_status_box_forall;
624 container_class->remove = NULL;
626 object_class = (GObjectClass *)klass;
628 object_class->finalize = pidgin_status_box_finalize;
630 object_class->get_property = pidgin_status_box_get_property;
631 object_class->set_property = pidgin_status_box_set_property;
633 g_object_class_install_property(object_class,
634 PROP_ACCOUNT,
635 g_param_spec_pointer("account",
636 "Account",
637 "The account, or NULL for all accounts",
638 G_PARAM_READWRITE
641 g_object_class_install_property(object_class,
642 PROP_ICON_SEL,
643 g_param_spec_boolean("iconsel",
644 "Icon Selector",
645 "Whether the icon selector should be displayed or not.",
646 FALSE,
647 G_PARAM_READWRITE
653 * This updates the text displayed on the status box so that it shows
654 * the current status. This is the only function in this file that
655 * should modify status_box->store
657 static void
658 pidgin_status_box_refresh(PidginStatusBox *status_box)
660 GtkStyle *style;
661 char aa_color[8];
662 PurpleSavedStatus *saved_status;
663 char *primary, *secondary, *text;
664 const char *stock = NULL;
665 GdkPixbuf *emblem = NULL;
666 GtkTreePath *path;
667 gboolean account_status = FALSE;
668 PurpleAccount *acct = (status_box->account) ? status_box->account : status_box->token_status_account;
670 style = gtk_widget_get_style(GTK_WIDGET(status_box));
671 snprintf(aa_color, sizeof(aa_color), "#%02x%02x%02x",
672 style->text_aa[GTK_STATE_NORMAL].red >> 8,
673 style->text_aa[GTK_STATE_NORMAL].green >> 8,
674 style->text_aa[GTK_STATE_NORMAL].blue >> 8);
676 saved_status = purple_savedstatus_get_current();
678 if (status_box->account || (status_box->token_status_account
679 && purple_savedstatus_is_transient(saved_status)))
680 account_status = TRUE;
682 /* Primary */
683 if (status_box->typing != 0)
685 GtkTreeIter iter;
686 PidginStatusBoxItemType type;
687 gpointer data;
689 /* Primary (get the status selected in the dropdown) */
690 path = gtk_tree_row_reference_get_path(status_box->active_row);
691 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
692 return;
693 gtk_tree_path_free(path);
695 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
696 TYPE_COLUMN, &type,
697 DATA_COLUMN, &data,
698 -1);
699 if (type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE)
700 primary = g_strdup(purple_primitive_get_name_from_type(GPOINTER_TO_INT(data)));
701 else
702 /* This should never happen, but just in case... */
703 primary = g_strdup("New status");
705 else if (account_status)
706 primary = g_strdup(purple_status_get_name(purple_account_get_active_status(acct)));
707 else if (purple_savedstatus_is_transient(saved_status))
708 primary = g_strdup(purple_primitive_get_name_from_type(purple_savedstatus_get_type(saved_status)));
709 else
710 primary = g_markup_escape_text(purple_savedstatus_get_title(saved_status), -1);
712 /* Secondary */
713 if (status_box->typing != 0)
714 secondary = g_strdup(_("Typing"));
715 else if (status_box->connecting)
716 secondary = g_strdup(_("Connecting"));
717 else if (!status_box->network_available)
718 secondary = g_strdup(_("Waiting for network connection"));
719 else if (purple_savedstatus_is_transient(saved_status))
720 secondary = NULL;
721 else
723 const char *message;
724 char *tmp;
725 message = purple_savedstatus_get_message(saved_status);
726 if (message != NULL)
728 tmp = purple_markup_strip_html(message);
729 purple_util_chrreplace(tmp, '\n', ' ');
730 secondary = g_markup_escape_text(tmp, -1);
731 g_free(tmp);
733 else
734 secondary = NULL;
737 /* Pixbuf */
738 if (status_box->typing != 0)
739 stock = typing_stock_ids[status_box->typing_index];
740 else if (status_box->connecting)
741 stock = connecting_stock_ids[status_box->connecting_index];
742 else
744 PurpleStatusType *status_type;
745 PurpleStatusPrimitive prim;
746 if (account_status) {
747 status_type = purple_status_get_type(purple_account_get_active_status(acct));
748 prim = purple_status_type_get_primitive(status_type);
749 } else {
750 prim = purple_savedstatus_get_type(saved_status);
753 stock = pidgin_stock_id_from_status_primitive(prim);
756 if (status_box->account != NULL) {
757 text = g_strdup_printf("%s - <span size=\"smaller\" color=\"%s\">%s</span>",
758 purple_account_get_username(status_box->account),
759 aa_color, secondary ? secondary : primary);
760 emblem = pidgin_create_prpl_icon(status_box->account, PIDGIN_PRPL_ICON_SMALL);
761 } else if (secondary != NULL) {
762 text = g_strdup_printf("%s<span size=\"smaller\" color=\"%s\"> - %s</span>",
763 primary, aa_color, secondary);
764 } else {
765 text = g_strdup(primary);
767 g_free(primary);
768 g_free(secondary);
771 * Only two columns are used in this list store (does it
772 * really need to be a list store?)
774 gtk_list_store_set(status_box->store, &(status_box->iter),
775 ICON_STOCK_COLUMN, (gpointer)stock,
776 TEXT_COLUMN, text,
777 EMBLEM_COLUMN, emblem,
778 EMBLEM_VISIBLE_COLUMN, (emblem != NULL),
779 -1);
780 g_free(text);
781 if (emblem)
782 g_object_unref(emblem);
784 /* Make sure to activate the only row in the tree view */
785 path = gtk_tree_path_new_from_string("0");
786 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(status_box->cell_view), path);
787 gtk_tree_path_free(path);
789 update_size(status_box);
792 static PurpleStatusType *
793 find_status_type_by_index(const PurpleAccount *account, gint active)
795 GList *l = purple_account_get_status_types(account);
796 gint i;
798 for (i = 0; l; l = l->next) {
799 PurpleStatusType *status_type = l->data;
800 if (!purple_status_type_is_user_settable(status_type) ||
801 purple_status_type_is_independent(status_type))
802 continue;
804 if (active == i)
805 return status_type;
806 i++;
809 return NULL;
813 * This updates the GtkTreeView so that it correctly shows the state
814 * we are currently using. It is used when the current state is
815 * updated from somewhere other than the GtkStatusBox (from a plugin,
816 * or when signing on with the "-n" option, for example). It is
817 * also used when the user selects the "New..." option.
819 * Maybe we could accomplish this by triggering off the mouse and
820 * keyboard signals instead of the changed signal?
822 static void
823 status_menu_refresh_iter(PidginStatusBox *status_box, gboolean status_changed)
825 PurpleSavedStatus *saved_status;
826 PurpleStatusPrimitive primitive;
827 gint index;
828 const char *message;
829 GtkTreePath *path = NULL;
831 /* this function is inappropriate for ones with accounts */
832 if (status_box->account)
833 return;
835 saved_status = purple_savedstatus_get_current();
838 * Suppress the "changed" signal because the status
839 * was changed programmatically.
841 gtk_widget_set_sensitive(GTK_WIDGET(status_box), FALSE);
844 * If there is a token-account, then select the primitive from the
845 * dropdown using a loop. Otherwise select from the default list.
847 primitive = purple_savedstatus_get_type(saved_status);
848 if (!status_box->token_status_account && purple_savedstatus_is_transient(saved_status) &&
849 ((primitive == PURPLE_STATUS_AVAILABLE) || (primitive == PURPLE_STATUS_AWAY) ||
850 (primitive == PURPLE_STATUS_INVISIBLE) || (primitive == PURPLE_STATUS_OFFLINE) ||
851 (primitive == PURPLE_STATUS_UNAVAILABLE)) &&
852 (!purple_savedstatus_has_substatuses(saved_status)))
854 index = get_statusbox_index(status_box, saved_status);
855 path = gtk_tree_path_new_from_indices(index, -1);
857 else
859 GtkTreeIter iter;
860 PidginStatusBoxItemType type;
861 gpointer data;
863 /* If this saved status is in the list store, then set it as the active item */
864 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(status_box->dropdown_store), &iter))
868 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
869 TYPE_COLUMN, &type,
870 DATA_COLUMN, &data,
871 -1);
873 /* This is a special case because Primitives for the token_status_account are actually
874 * saved statuses with substatuses for the enabled accounts */
875 if (status_box->token_status_account && purple_savedstatus_is_transient(saved_status)
876 && type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE && primitive == GPOINTER_TO_INT(data))
878 char *name;
879 const char *acct_status_name = purple_status_get_name(
880 purple_account_get_active_status(status_box->token_status_account));
882 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
883 TEXT_COLUMN, &name, -1);
885 if (!purple_savedstatus_has_substatuses(saved_status)
886 || !strcmp(name, acct_status_name))
888 /* Found! */
889 path = gtk_tree_model_get_path(GTK_TREE_MODEL(status_box->dropdown_store), &iter);
890 g_free(name);
891 break;
893 g_free(name);
895 } else if ((type == PIDGIN_STATUS_BOX_TYPE_POPULAR) &&
896 (GPOINTER_TO_INT(data) == purple_savedstatus_get_creation_time(saved_status)))
898 /* Found! */
899 path = gtk_tree_model_get_path(GTK_TREE_MODEL(status_box->dropdown_store), &iter);
900 break;
902 } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(status_box->dropdown_store), &iter));
906 if (status_box->active_row)
907 gtk_tree_row_reference_free(status_box->active_row);
908 if (path) { /* path should never be NULL */
909 status_box->active_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box->dropdown_store), path);
910 gtk_tree_path_free(path);
911 } else
912 status_box->active_row = NULL;
914 if (status_changed) {
915 message = purple_savedstatus_get_message(saved_status);
918 * If we are going to hide the imhtml, don't retain the
919 * message because showing the old message later is
920 * confusing. If we are going to set the message to a pre-set,
921 * then we need to do this anyway
923 * Suppress the "changed" signal because the status
924 * was changed programmatically.
926 gtk_widget_set_sensitive(GTK_WIDGET(status_box->imhtml), FALSE);
928 gtk_imhtml_clear(GTK_IMHTML(status_box->imhtml));
929 gtk_imhtml_clear_formatting(GTK_IMHTML(status_box->imhtml));
931 if (!purple_savedstatus_is_transient(saved_status) || !message || !*message)
933 status_box->imhtml_visible = FALSE;
934 gtk_widget_hide_all(status_box->vbox);
936 else
938 status_box->imhtml_visible = TRUE;
939 gtk_widget_show_all(status_box->vbox);
941 gtk_imhtml_append_text(GTK_IMHTML(status_box->imhtml), message, 0);
944 gtk_widget_set_sensitive(GTK_WIDGET(status_box->imhtml), TRUE);
945 update_size(status_box);
948 /* Stop suppressing the "changed" signal. */
949 gtk_widget_set_sensitive(GTK_WIDGET(status_box), TRUE);
952 static void
953 add_popular_statuses(PidginStatusBox *statusbox)
955 GList *list, *cur;
957 list = purple_savedstatuses_get_popular(6);
958 if (list == NULL)
959 /* Odd... oh well, nothing we can do about it. */
960 return;
962 pidgin_status_box_add_separator(statusbox);
964 for (cur = list; cur != NULL; cur = cur->next)
966 PurpleSavedStatus *saved = cur->data;
967 const gchar *message;
968 gchar *stripped = NULL;
969 PidginStatusBoxItemType type;
971 if (purple_savedstatus_is_transient(saved))
974 * Transient statuses do not have a title, so the savedstatus
975 * API returns the message when purple_savedstatus_get_title() is
976 * called, so we don't need to get the message a second time.
978 type = PIDGIN_STATUS_BOX_TYPE_POPULAR;
980 else
982 type = PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR;
984 message = purple_savedstatus_get_message(saved);
985 if (message != NULL)
987 stripped = purple_markup_strip_html(message);
988 purple_util_chrreplace(stripped, '\n', ' ');
992 pidgin_status_box_add(statusbox, type,
993 NULL, purple_savedstatus_get_title(saved), stripped,
994 GINT_TO_POINTER(purple_savedstatus_get_creation_time(saved)));
995 g_free(stripped);
998 g_list_free(list);
1001 /* This returns NULL if the active accounts don't have identical
1002 * statuses and a token account if they do */
1003 static PurpleAccount* check_active_accounts_for_identical_statuses(void)
1005 GList *iter, *active_accts = purple_accounts_get_all_active();
1006 PurpleAccount *acct1 = NULL;
1007 const char *prpl1 = NULL;
1009 if (active_accts) {
1010 acct1 = active_accts->data;
1011 prpl1 = purple_account_get_protocol_id(acct1);
1012 } else {
1013 /* there's no enabled account */
1014 return NULL;
1017 /* start at the second account */
1018 for (iter = active_accts->next; iter; iter = iter->next) {
1019 PurpleAccount *acct2 = iter->data;
1020 GList *s1, *s2;
1022 if (!g_str_equal(prpl1, purple_account_get_protocol_id(acct2))) {
1023 acct1 = NULL;
1024 break;
1027 for (s1 = purple_account_get_status_types(acct1),
1028 s2 = purple_account_get_status_types(acct2); s1 && s2;
1029 s1 = s1->next, s2 = s2->next) {
1030 PurpleStatusType *st1 = s1->data, *st2 = s2->data;
1031 /* TODO: Are these enough to consider the statuses identical? */
1032 if (purple_status_type_get_primitive(st1) != purple_status_type_get_primitive(st2)
1033 || strcmp(purple_status_type_get_id(st1), purple_status_type_get_id(st2))
1034 || strcmp(purple_status_type_get_name(st1), purple_status_type_get_name(st2))) {
1035 acct1 = NULL;
1036 break;
1040 if (s1 != s2) {/* Will both be NULL if matched */
1041 acct1 = NULL;
1042 break;
1046 g_list_free(active_accts);
1048 return acct1;
1051 static void
1052 add_account_statuses(PidginStatusBox *status_box, PurpleAccount *account)
1054 /* Per-account */
1055 GList *l;
1057 for (l = purple_account_get_status_types(account); l != NULL; l = l->next)
1059 PurpleStatusType *status_type = (PurpleStatusType *)l->data;
1060 PurpleStatusPrimitive prim;
1062 if (!purple_status_type_is_user_settable(status_type) ||
1063 purple_status_type_is_independent(status_type))
1064 continue;
1066 prim = purple_status_type_get_primitive(status_type);
1068 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box),
1069 PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL,
1070 purple_status_type_get_name(status_type),
1071 NULL,
1072 GINT_TO_POINTER(prim));
1076 static void
1077 pidgin_status_box_regenerate(PidginStatusBox *status_box, gboolean status_changed)
1079 /* Unset the model while clearing it */
1080 gtk_tree_view_set_model(GTK_TREE_VIEW(status_box->tree_view), NULL);
1081 gtk_list_store_clear(status_box->dropdown_store);
1082 /* Don't set the model until the new statuses have been added to the box.
1083 * What is presumably a bug in Gtk < 2.4 causes things to get all confused
1084 * if we do this here. */
1085 /* gtk_combo_box_set_model(GTK_COMBO_BOX(status_box), GTK_TREE_MODEL(status_box->dropdown_store)); */
1087 if (status_box->account == NULL)
1089 /* Do all the currently enabled accounts have the same statuses?
1090 * If so, display them instead of our global list.
1092 if (status_box->token_status_account) {
1093 add_account_statuses(status_box, status_box->token_status_account);
1094 } else {
1095 /* Global */
1096 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL, _("Available"), NULL, GINT_TO_POINTER(PURPLE_STATUS_AVAILABLE));
1097 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL, _("Away"), NULL, GINT_TO_POINTER(PURPLE_STATUS_AWAY));
1098 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));
1099 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL, _("Invisible"), NULL, GINT_TO_POINTER(PURPLE_STATUS_INVISIBLE));
1100 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, NULL, _("Offline"), NULL, GINT_TO_POINTER(PURPLE_STATUS_OFFLINE));
1103 add_popular_statuses(status_box);
1105 pidgin_status_box_add_separator(PIDGIN_STATUS_BOX(status_box));
1106 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_CUSTOM, NULL, _("New status..."), NULL, NULL);
1107 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_SAVED, NULL, _("Saved statuses..."), NULL, NULL);
1109 status_menu_refresh_iter(status_box, status_changed);
1110 pidgin_status_box_refresh(status_box);
1112 } else {
1113 add_account_statuses(status_box, status_box->account);
1114 update_to_reflect_account_status(status_box, status_box->account,
1115 purple_account_get_active_status(status_box->account));
1117 gtk_tree_view_set_model(GTK_TREE_VIEW(status_box->tree_view), GTK_TREE_MODEL(status_box->dropdown_store));
1118 gtk_tree_view_set_search_column(GTK_TREE_VIEW(status_box->tree_view), TEXT_COLUMN);
1121 static gboolean combo_box_scroll_event_cb(GtkWidget *w, GdkEventScroll *event, GtkIMHtml *imhtml)
1123 pidgin_status_box_popup(PIDGIN_STATUS_BOX(w));
1124 return TRUE;
1127 static gboolean imhtml_scroll_event_cb(GtkWidget *w, GdkEventScroll *event, GtkIMHtml *imhtml)
1129 if (event->direction == GDK_SCROLL_UP)
1130 gtk_imhtml_page_up(imhtml);
1131 else if (event->direction == GDK_SCROLL_DOWN)
1132 gtk_imhtml_page_down(imhtml);
1133 return TRUE;
1136 static gboolean imhtml_remove_focus(GtkWidget *w, GdkEventKey *event, PidginStatusBox *status_box)
1138 if (event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab || event->keyval == GDK_ISO_Left_Tab)
1140 /* If last inserted character is a tab, then remove the focus from here */
1141 GtkWidget *top = gtk_widget_get_toplevel(w);
1142 g_signal_emit_by_name(G_OBJECT(top), "move_focus",
1143 (event->state & GDK_SHIFT_MASK) ?
1144 GTK_DIR_TAB_BACKWARD: GTK_DIR_TAB_FORWARD);
1145 return TRUE;
1147 if (status_box->typing == 0)
1148 return FALSE;
1150 /* Reset the status if Escape was pressed */
1151 if (event->keyval == GDK_Escape)
1153 purple_timeout_remove(status_box->typing);
1154 status_box->typing = 0;
1155 gtk_imhtml_set_populate_primary_clipboard(
1156 GTK_IMHTML(status_box->imhtml), TRUE);
1157 if (status_box->account != NULL)
1158 update_to_reflect_account_status(status_box, status_box->account,
1159 purple_account_get_active_status(status_box->account));
1160 else {
1161 status_menu_refresh_iter(status_box, TRUE);
1162 pidgin_status_box_refresh(status_box);
1164 return TRUE;
1167 pidgin_status_box_pulse_typing(status_box);
1168 purple_timeout_remove(status_box->typing);
1169 status_box->typing = purple_timeout_add_seconds(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
1171 return FALSE;
1174 static gboolean
1175 dropdown_store_row_separator_func(GtkTreeModel *model,
1176 GtkTreeIter *iter, gpointer data)
1178 PidginStatusBoxItemType type;
1180 gtk_tree_model_get(model, iter, TYPE_COLUMN, &type, -1);
1182 if (type == PIDGIN_STATUS_BOX_TYPE_SEPARATOR)
1183 return TRUE;
1185 return FALSE;
1188 static void
1189 cache_pixbufs(PidginStatusBox *status_box)
1191 GtkIconSize icon_size;
1192 int i;
1194 g_object_set(G_OBJECT(status_box->icon_rend), "xpad", 3, NULL);
1195 icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL);
1197 for (i = 0; i < G_N_ELEMENTS(status_box->connecting_pixbufs); i++) {
1198 if (status_box->connecting_pixbufs[i] != NULL)
1199 g_object_unref(G_OBJECT(status_box->connecting_pixbufs[i]));
1200 if (connecting_stock_ids[i])
1201 status_box->connecting_pixbufs[i] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox),
1202 connecting_stock_ids[i], icon_size, "PidginStatusBox");
1203 else
1204 status_box->connecting_pixbufs[i] = NULL;
1206 status_box->connecting_index = 0;
1209 for (i = 0; i < G_N_ELEMENTS(status_box->typing_pixbufs); i++) {
1210 if (status_box->typing_pixbufs[i] != NULL)
1211 g_object_unref(G_OBJECT(status_box->typing_pixbufs[i]));
1212 if (typing_stock_ids[i])
1213 status_box->typing_pixbufs[i] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox),
1214 typing_stock_ids[i], icon_size, "PidginStatusBox");
1215 else
1216 status_box->typing_pixbufs[i] = NULL;
1218 status_box->typing_index = 0;
1221 static void account_enabled_cb(PurpleAccount *acct, PidginStatusBox *status_box)
1223 PurpleAccount *initial_token_acct = status_box->token_status_account;
1225 if (status_box->account)
1226 return;
1228 status_box->token_status_account = check_active_accounts_for_identical_statuses();
1230 /* Regenerate the list if it has changed */
1231 if (initial_token_acct != status_box->token_status_account) {
1232 pidgin_status_box_regenerate(status_box, TRUE);
1237 static void
1238 current_savedstatus_changed_cb(PurpleSavedStatus *now, PurpleSavedStatus *old, PidginStatusBox *status_box)
1240 /* Make sure our current status is added to the list of popular statuses */
1241 pidgin_status_box_regenerate(status_box, TRUE);
1244 static void
1245 saved_status_updated_cb(PurpleSavedStatus *status, PidginStatusBox *status_box)
1247 pidgin_status_box_regenerate(status_box,
1248 purple_savedstatus_get_current() == status);
1251 static void
1252 spellcheck_prefs_cb(const char *name, PurplePrefType type,
1253 gconstpointer value, gpointer data)
1255 #ifdef USE_GTKSPELL
1256 PidginStatusBox *status_box = (PidginStatusBox *)data;
1258 if (value)
1259 pidgin_setup_gtkspell(GTK_TEXT_VIEW(status_box->imhtml));
1260 else
1262 GtkSpell *spell;
1263 spell = gtkspell_get_from_text_view(GTK_TEXT_VIEW(status_box->imhtml));
1264 gtkspell_detach(spell);
1266 #endif
1269 #if 0
1270 static gboolean button_released_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
1273 if (event->button != 1)
1274 return FALSE;
1275 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box->toggle_button), FALSE);
1276 if (!box->imhtml_visible)
1277 g_signal_emit_by_name(G_OBJECT(box), "changed", NULL, NULL);
1278 return TRUE;
1281 static gboolean button_pressed_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
1283 if (event->button != 1)
1284 return FALSE;
1285 gtk_combo_box_popup(GTK_COMBO_BOX(box));
1286 /* Disabled until button_released_cb works */
1287 #if 0
1288 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box->toggle_button), TRUE);
1289 #endif
1290 return TRUE;
1292 #endif
1294 static void
1295 pidgin_status_box_list_position (PidginStatusBox *status_box, int *x, int *y, int *width, int *height)
1297 GdkScreen *screen;
1298 gint monitor_num;
1299 GdkRectangle monitor;
1300 GtkRequisition popup_req;
1301 GtkPolicyType hpolicy, vpolicy;
1303 gdk_window_get_origin (GTK_WIDGET(status_box)->window, x, y);
1305 *x += GTK_WIDGET(status_box)->allocation.x;
1306 *y += GTK_WIDGET(status_box)->allocation.y;
1308 *width = GTK_WIDGET(status_box)->allocation.width;
1310 hpolicy = vpolicy = GTK_POLICY_NEVER;
1311 g_object_set(G_OBJECT(status_box->scrolled_window),
1312 "hscrollbar-policy", hpolicy,
1313 "vscrollbar-policy", vpolicy,
1314 NULL);
1315 gtk_widget_size_request (status_box->popup_frame, &popup_req);
1317 if (popup_req.width > *width)
1319 hpolicy = GTK_POLICY_ALWAYS;
1320 g_object_set(G_OBJECT(status_box->scrolled_window),
1321 "hscrollbar-policy", hpolicy,
1322 "vscrollbar-policy", vpolicy,
1323 NULL);
1324 gtk_widget_size_request (status_box->popup_frame, &popup_req);
1327 *height = popup_req.height;
1329 screen = gtk_widget_get_screen (GTK_WIDGET (status_box));
1330 monitor_num = gdk_screen_get_monitor_at_window (screen,
1331 GTK_WIDGET (status_box)->window);
1332 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
1334 if (*x < monitor.x)
1335 *x = monitor.x;
1336 else if (*x + *width > monitor.x + monitor.width)
1337 *x = monitor.x + monitor.width - *width;
1339 if (*y + GTK_WIDGET(status_box)->allocation.height + *height <= monitor.y + monitor.height)
1340 *y += GTK_WIDGET(status_box)->allocation.height;
1341 else if (*y - *height >= monitor.y)
1342 *y -= *height;
1343 else if (monitor.y + monitor.height - (*y + GTK_WIDGET(status_box)->allocation.height) > *y - monitor.y)
1345 *y += GTK_WIDGET(status_box)->allocation.height;
1346 *height = monitor.y + monitor.height - *y;
1348 else
1350 *height = *y - monitor.y;
1351 *y = monitor.y;
1354 if (popup_req.height > *height)
1356 vpolicy = GTK_POLICY_ALWAYS;
1358 g_object_set(G_OBJECT(status_box->scrolled_window),
1359 "hscrollbar-policy", hpolicy,
1360 "vscrollbar-policy", vpolicy,
1361 NULL);
1365 static gboolean
1366 popup_grab_on_window (GdkWindow *window,
1367 guint32 activate_time,
1368 gboolean grab_keyboard)
1370 if ((gdk_pointer_grab (window, TRUE,
1371 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1372 GDK_POINTER_MOTION_MASK,
1373 NULL, NULL, activate_time) == 0))
1375 if (!grab_keyboard || gdk_keyboard_grab (window, TRUE, activate_time) == 0)
1376 return TRUE;
1377 else {
1378 gdk_display_pointer_ungrab (gdk_drawable_get_display (window), activate_time);
1379 return FALSE;
1383 return FALSE;
1387 static void
1388 pidgin_status_box_popup(PidginStatusBox *box)
1390 int width, height, x, y;
1391 pidgin_status_box_list_position (box, &x, &y, &width, &height);
1393 gtk_widget_set_size_request (box->popup_window, width, height);
1394 gtk_window_move (GTK_WINDOW (box->popup_window), x, y);
1395 gtk_widget_show(box->popup_window);
1396 gtk_widget_grab_focus (box->tree_view);
1397 if (!popup_grab_on_window (box->popup_window->window,
1398 GDK_CURRENT_TIME, TRUE)) {
1399 gtk_widget_hide (box->popup_window);
1400 return;
1402 gtk_grab_add (box->popup_window);
1403 /*box->popup_in_progress = TRUE;*/
1404 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (box->toggle_button),
1405 TRUE);
1407 if (box->active_row) {
1408 GtkTreePath *path = gtk_tree_row_reference_get_path(box->active_row);
1409 gtk_tree_view_set_cursor(GTK_TREE_VIEW(box->tree_view), path, NULL, FALSE);
1410 gtk_tree_path_free(path);
1414 static void
1415 pidgin_status_box_popdown(PidginStatusBox *box)
1417 gtk_widget_hide(box->popup_window);
1418 box->popup_in_progress = FALSE;
1419 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (box->toggle_button),
1420 FALSE);
1421 gtk_grab_remove (box->popup_window);
1424 static gboolean
1425 toggle_key_press_cb(GtkWidget *widget, GdkEventKey *event, PidginStatusBox *box)
1427 switch (event->keyval) {
1428 case GDK_Return:
1429 case GDK_KP_Enter:
1430 case GDK_KP_Space:
1431 case GDK_space:
1432 if (!box->popup_in_progress) {
1433 pidgin_status_box_popup (box);
1434 box->popup_in_progress = TRUE;
1435 } else {
1436 pidgin_status_box_popdown(box);
1438 return TRUE;
1439 default:
1440 return FALSE;
1444 static gboolean
1445 toggled_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
1447 if (!box->popup_in_progress)
1448 pidgin_status_box_popup (box);
1449 else
1450 pidgin_status_box_popdown(box);
1451 return TRUE;
1454 static void
1455 buddy_icon_set_cb(const char *filename, PidginStatusBox *box)
1457 PurpleStoredImage *img = NULL;
1459 if (box->account) {
1460 PurplePlugin *plug = purple_find_prpl(purple_account_get_protocol_id(box->account));
1461 if (plug) {
1462 PurplePluginProtocolInfo *prplinfo = PURPLE_PLUGIN_PROTOCOL_INFO(plug);
1463 if (prplinfo && prplinfo->icon_spec.format) {
1464 gpointer data = NULL;
1465 size_t len = 0;
1466 if (filename)
1467 data = pidgin_convert_buddy_icon(plug, filename, &len);
1468 img = purple_buddy_icons_set_account_icon(box->account, data, len);
1469 if (img)
1471 * set_account_icon doesn't give us a reference, but we
1472 * unref one below (for the other code path)
1474 purple_imgstore_ref(img);
1476 purple_account_set_buddy_icon_path(box->account, filename);
1478 purple_account_set_bool(box->account, "use-global-buddyicon", (filename != NULL));
1481 } else {
1482 GList *accounts;
1483 for (accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) {
1484 PurpleAccount *account = accounts->data;
1485 PurplePlugin *plug = purple_find_prpl(purple_account_get_protocol_id(account));
1486 if (plug) {
1487 PurplePluginProtocolInfo *prplinfo = PURPLE_PLUGIN_PROTOCOL_INFO(plug);
1488 if (prplinfo != NULL &&
1489 purple_account_get_bool(account, "use-global-buddyicon", TRUE) &&
1490 prplinfo->icon_spec.format) {
1491 gpointer data = NULL;
1492 size_t len = 0;
1493 if (filename)
1494 data = pidgin_convert_buddy_icon(plug, filename, &len);
1495 purple_buddy_icons_set_account_icon(account, data, len);
1496 purple_account_set_buddy_icon_path(account, filename);
1501 /* Even if no accounts were processed, load the icon that was set. */
1502 if (filename != NULL)
1503 img = purple_imgstore_new_from_file(filename);
1506 pidgin_status_box_set_buddy_icon(box, img);
1507 if (img)
1508 purple_imgstore_unref(img);
1511 static void
1512 remove_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box)
1514 if (box->account == NULL)
1515 /* The pref-connect callback does the actual work */
1516 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon", NULL);
1517 else
1518 buddy_icon_set_cb(NULL, box);
1520 gtk_widget_destroy(box->icon_box_menu);
1521 box->icon_box_menu = NULL;
1524 static void
1525 choose_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box)
1527 if (box->buddy_icon_sel) {
1528 gtk_window_present(GTK_WINDOW(box->buddy_icon_sel));
1529 } else {
1530 box->buddy_icon_sel = pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtk_widget_get_toplevel(w)), icon_choose_cb, box);
1531 gtk_widget_show_all(box->buddy_icon_sel);
1535 static void
1536 icon_choose_cb(const char *filename, gpointer data)
1538 PidginStatusBox *box = data;
1539 if (filename) {
1540 if (box->account == NULL)
1541 /* The pref-connect callback does the actual work */
1542 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon", filename);
1543 else
1544 buddy_icon_set_cb(filename, box);
1547 box->buddy_icon_sel = NULL;
1550 static void
1551 update_buddyicon_cb(const char *name, PurplePrefType type,
1552 gconstpointer value, gpointer data)
1554 buddy_icon_set_cb(value, (PidginStatusBox*) data);
1557 static void
1558 treeview_activate_current_selection(PidginStatusBox *status_box, GtkTreePath *path)
1560 if (status_box->active_row)
1561 gtk_tree_row_reference_free(status_box->active_row);
1563 status_box->active_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box->dropdown_store), path);
1564 pidgin_status_box_popdown (status_box);
1565 pidgin_status_box_changed(status_box);
1568 static void tree_view_delete_current_selection_cb(gpointer data)
1570 PurpleSavedStatus *saved;
1572 saved = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
1573 g_return_if_fail(saved != NULL);
1575 if (purple_savedstatus_get_current() != saved)
1576 purple_savedstatus_delete_by_status(saved);
1579 static void
1580 tree_view_delete_current_selection(PidginStatusBox *status_box, GtkTreePath *path)
1582 GtkTreeIter iter;
1583 gpointer data;
1584 PurpleSavedStatus *saved;
1585 gchar *msg;
1587 if (status_box->active_row) {
1588 /* don't delete active status */
1589 if (gtk_tree_path_compare(path, gtk_tree_row_reference_get_path(status_box->active_row)) == 0)
1590 return;
1593 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
1594 return;
1596 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
1597 DATA_COLUMN, &data,
1598 -1);
1600 saved = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
1601 g_return_if_fail(saved != NULL);
1602 if (saved == purple_savedstatus_get_current())
1603 return;
1605 msg = g_strdup_printf(_("Are you sure you want to delete %s?"), purple_savedstatus_get_title(saved));
1607 purple_request_action(saved, NULL, msg, NULL, 0,
1608 NULL, NULL, NULL,
1609 data, 2,
1610 _("Delete"), tree_view_delete_current_selection_cb,
1611 _("Cancel"), NULL);
1613 g_free(msg);
1615 pidgin_status_box_popdown(status_box);
1618 static gboolean
1619 treeview_button_release_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *status_box)
1621 GtkTreePath *path = NULL;
1622 int ret;
1623 GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);
1625 if (ewidget != status_box->tree_view) {
1626 if (ewidget == status_box->toggle_button &&
1627 status_box->popup_in_progress &&
1628 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (status_box->toggle_button))) {
1629 pidgin_status_box_popdown (status_box);
1630 return TRUE;
1631 } else if (ewidget == status_box->toggle_button) {
1632 status_box->popup_in_progress = TRUE;
1635 /* released outside treeview */
1636 if (ewidget != status_box->toggle_button) {
1637 pidgin_status_box_popdown (status_box);
1638 return TRUE;
1641 return FALSE;
1644 ret = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (status_box->tree_view),
1645 event->x, event->y,
1646 &path,
1647 NULL, NULL, NULL);
1649 if (!ret)
1650 return TRUE; /* clicked outside window? */
1652 treeview_activate_current_selection(status_box, path);
1653 gtk_tree_path_free (path);
1655 return TRUE;
1658 static gboolean
1659 treeview_key_press_event(GtkWidget *widget,
1660 GdkEventKey *event, PidginStatusBox *box)
1662 if (box->popup_in_progress) {
1663 if (event->keyval == GDK_Escape) {
1664 pidgin_status_box_popdown(box);
1665 return TRUE;
1666 } else {
1667 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(box->tree_view));
1668 GtkTreeIter iter;
1669 GtkTreePath *path;
1671 if (gtk_tree_selection_get_selected(sel, NULL, &iter)) {
1672 gboolean ret = TRUE;
1673 path = gtk_tree_model_get_path(GTK_TREE_MODEL(box->dropdown_store), &iter);
1674 if (event->keyval == GDK_Return) {
1675 treeview_activate_current_selection(box, path);
1676 } else if (event->keyval == GDK_Delete) {
1677 tree_view_delete_current_selection(box, path);
1678 } else
1679 ret = FALSE;
1681 gtk_tree_path_free (path);
1682 return ret;
1686 return FALSE;
1689 static void
1690 imhtml_cursor_moved_cb(gpointer data, GtkMovementStep step, gint count, gboolean extend,
1691 GtkWidget *widget)
1693 /* Restart the typing timeout if arrow keys are pressed while editing the message */
1694 PidginStatusBox *status_box = data;
1695 if (status_box->typing == 0)
1696 return;
1697 imhtml_changed_cb(NULL, status_box);
1700 static void
1701 treeview_cursor_changed_cb(GtkTreeView *treeview, gpointer data)
1703 GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview);
1704 GtkTreeModel *model = GTK_TREE_MODEL (data);
1705 GtkTreeIter iter;
1706 GtkTreePath *cursor;
1707 GtkTreePath *selection;
1708 gint cmp;
1710 if (gtk_tree_selection_get_selected (sel, NULL, &iter)) {
1711 if ((selection = gtk_tree_model_get_path (model, &iter)) == NULL) {
1712 /* Shouldn't happen, but ignore anyway */
1713 return;
1715 } else {
1716 /* I don't think this can happen, but we'll just ignore it */
1717 return;
1720 gtk_tree_view_get_cursor (treeview, &cursor, NULL);
1721 if (cursor == NULL) {
1722 /* Probably won't happen in a 'cursor-changed' event? */
1723 gtk_tree_path_free (selection);
1724 return;
1727 cmp = gtk_tree_path_compare (cursor, selection);
1728 if (cmp < 0) {
1729 /* The cursor moved up without moving the selection, so move it up again */
1730 gtk_tree_path_prev (cursor);
1731 gtk_tree_view_set_cursor (treeview, cursor, NULL, FALSE);
1732 } else if (cmp > 0) {
1733 /* The cursor moved down without moving the selection, so move it down again */
1734 gtk_tree_path_next (cursor);
1735 gtk_tree_view_set_cursor (treeview, cursor, NULL, FALSE);
1738 gtk_tree_path_free (selection);
1739 gtk_tree_path_free (cursor);
1742 static void
1743 pidgin_status_box_init (PidginStatusBox *status_box)
1745 GtkCellRenderer *text_rend;
1746 GtkCellRenderer *icon_rend;
1747 GtkCellRenderer *emblem_rend;
1748 GtkTextBuffer *buffer;
1749 GtkWidget *toplevel;
1750 GtkTreeSelection *sel;
1752 GTK_WIDGET_SET_FLAGS (status_box, GTK_NO_WINDOW);
1753 status_box->imhtml_visible = FALSE;
1754 status_box->network_available = purple_network_is_available();
1755 status_box->connecting = FALSE;
1756 status_box->typing = 0;
1757 status_box->toggle_button = gtk_toggle_button_new();
1758 status_box->hbox = gtk_hbox_new(FALSE, 6);
1759 status_box->cell_view = gtk_cell_view_new();
1760 status_box->vsep = gtk_vseparator_new();
1761 status_box->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
1763 status_box->store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_INT, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING,
1764 G_TYPE_STRING, G_TYPE_POINTER, GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN);
1765 status_box->dropdown_store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_INT, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_STRING,
1766 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
1768 gtk_cell_view_set_model(GTK_CELL_VIEW(status_box->cell_view), GTK_TREE_MODEL(status_box->store));
1769 gtk_list_store_append(status_box->store, &(status_box->iter));
1771 atk_object_set_name(gtk_widget_get_accessible(status_box->toggle_button), _("Status Selector"));
1773 gtk_container_add(GTK_CONTAINER(status_box->toggle_button), status_box->hbox);
1774 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->cell_view, TRUE, TRUE, 0);
1775 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->vsep, FALSE, FALSE, 0);
1776 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->arrow, FALSE, FALSE, 0);
1777 gtk_widget_show_all(status_box->toggle_button);
1778 gtk_button_set_focus_on_click(GTK_BUTTON(status_box->toggle_button), FALSE);
1780 text_rend = gtk_cell_renderer_text_new();
1781 icon_rend = gtk_cell_renderer_pixbuf_new();
1782 emblem_rend = gtk_cell_renderer_pixbuf_new();
1783 status_box->popup_window = gtk_window_new (GTK_WINDOW_POPUP);
1785 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (status_box));
1786 if (GTK_IS_WINDOW (toplevel)) {
1787 gtk_window_set_transient_for (GTK_WINDOW (status_box->popup_window),
1788 GTK_WINDOW (toplevel));
1791 gtk_window_set_resizable (GTK_WINDOW (status_box->popup_window), FALSE);
1792 gtk_window_set_type_hint (GTK_WINDOW (status_box->popup_window),
1793 GDK_WINDOW_TYPE_HINT_POPUP_MENU);
1794 gtk_window_set_screen (GTK_WINDOW (status_box->popup_window),
1795 gtk_widget_get_screen (GTK_WIDGET (status_box)));
1796 status_box->popup_frame = gtk_frame_new (NULL);
1797 gtk_frame_set_shadow_type (GTK_FRAME (status_box->popup_frame),
1798 GTK_SHADOW_ETCHED_IN);
1799 gtk_container_add (GTK_CONTAINER (status_box->popup_window),
1800 status_box->popup_frame);
1802 gtk_widget_show (status_box->popup_frame);
1804 status_box->tree_view = gtk_tree_view_new ();
1805 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (status_box->tree_view));
1806 gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE);
1807 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (status_box->tree_view),
1808 FALSE);
1809 gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (status_box->tree_view),
1810 TRUE);
1811 gtk_tree_view_set_model (GTK_TREE_VIEW (status_box->tree_view),
1812 GTK_TREE_MODEL(status_box->dropdown_store));
1813 status_box->column = gtk_tree_view_column_new ();
1814 gtk_tree_view_append_column (GTK_TREE_VIEW (status_box->tree_view),
1815 status_box->column);
1816 gtk_tree_view_column_pack_start(status_box->column, icon_rend, FALSE);
1817 gtk_tree_view_column_pack_start(status_box->column, text_rend, TRUE);
1818 gtk_tree_view_column_pack_start(status_box->column, emblem_rend, FALSE);
1819 gtk_tree_view_column_set_attributes(status_box->column, icon_rend, "stock-id", ICON_STOCK_COLUMN, NULL);
1820 gtk_tree_view_column_set_attributes(status_box->column, text_rend, "markup", TEXT_COLUMN, NULL);
1821 gtk_tree_view_column_set_attributes(status_box->column, emblem_rend, "stock-id", EMBLEM_COLUMN, "visible", EMBLEM_VISIBLE_COLUMN, NULL);
1823 status_box->scrolled_window = pidgin_make_scrollable(status_box->tree_view, GTK_POLICY_NEVER, GTK_POLICY_NEVER, GTK_SHADOW_NONE, -1, -1);
1824 gtk_container_add (GTK_CONTAINER (status_box->popup_frame),
1825 status_box->scrolled_window);
1827 gtk_widget_show(status_box->tree_view);
1828 gtk_tree_view_set_search_column(GTK_TREE_VIEW(status_box->tree_view), TEXT_COLUMN);
1829 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(status_box->tree_view),
1830 pidgin_tree_view_search_equal_func, NULL, NULL);
1832 g_object_set(text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1834 status_box->icon_rend = gtk_cell_renderer_pixbuf_new();
1835 status_box->text_rend = gtk_cell_renderer_text_new();
1836 emblem_rend = gtk_cell_renderer_pixbuf_new();
1837 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), status_box->icon_rend, FALSE);
1838 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), status_box->text_rend, TRUE);
1839 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), emblem_rend, FALSE);
1840 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), status_box->icon_rend, "stock-id", ICON_STOCK_COLUMN, NULL);
1841 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), status_box->text_rend, "markup", TEXT_COLUMN, NULL);
1842 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), emblem_rend, "pixbuf", EMBLEM_COLUMN, "visible", EMBLEM_VISIBLE_COLUMN, NULL);
1843 g_object_set(status_box->text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1845 status_box->vbox = gtk_vbox_new(0, FALSE);
1846 status_box->sw = pidgin_create_imhtml(FALSE, &status_box->imhtml, NULL, NULL);
1847 gtk_imhtml_set_editable(GTK_IMHTML(status_box->imhtml), TRUE);
1849 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(status_box->imhtml));
1850 #if 0
1851 g_signal_connect(G_OBJECT(status_box->toggle_button), "button-press-event",
1852 G_CALLBACK(button_pressed_cb), status_box);
1853 g_signal_connect(G_OBJECT(status_box->toggle_button), "button-release-event",
1854 G_CALLBACK(button_released_cb), status_box);
1855 #endif
1856 g_signal_connect(G_OBJECT(status_box->toggle_button), "key-press-event",
1857 G_CALLBACK(toggle_key_press_cb), status_box);
1858 g_signal_connect(G_OBJECT(status_box->toggle_button), "button-press-event",
1859 G_CALLBACK(toggled_cb), status_box);
1860 g_signal_connect(G_OBJECT(buffer), "changed", G_CALLBACK(imhtml_changed_cb), status_box);
1861 g_signal_connect(G_OBJECT(status_box->imhtml), "format_function_toggle",
1862 G_CALLBACK(imhtml_format_changed_cb), status_box);
1863 g_signal_connect_swapped(G_OBJECT(status_box->imhtml), "move_cursor",
1864 G_CALLBACK(imhtml_cursor_moved_cb), status_box);
1865 g_signal_connect(G_OBJECT(status_box->imhtml), "key_press_event",
1866 G_CALLBACK(imhtml_remove_focus), status_box);
1867 g_signal_connect_swapped(G_OBJECT(status_box->imhtml), "message_send", G_CALLBACK(remove_typing_cb), status_box);
1869 #ifdef USE_GTKSPELL
1870 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck"))
1871 pidgin_setup_gtkspell(GTK_TEXT_VIEW(status_box->imhtml));
1872 #endif
1873 gtk_widget_set_parent(status_box->vbox, GTK_WIDGET(status_box));
1874 gtk_widget_show_all(status_box->vbox);
1876 gtk_widget_set_parent(status_box->toggle_button, GTK_WIDGET(status_box));
1878 gtk_box_pack_start(GTK_BOX(status_box->vbox), status_box->sw, TRUE, TRUE, 0);
1880 g_signal_connect(G_OBJECT(status_box), "scroll_event", G_CALLBACK(combo_box_scroll_event_cb), NULL);
1881 g_signal_connect(G_OBJECT(status_box->imhtml), "scroll_event",
1882 G_CALLBACK(imhtml_scroll_event_cb), status_box->imhtml);
1883 g_signal_connect(G_OBJECT(status_box->popup_window), "button_release_event", G_CALLBACK(treeview_button_release_cb), status_box);
1884 g_signal_connect(G_OBJECT(status_box->popup_window), "key_press_event", G_CALLBACK(treeview_key_press_event), status_box);
1885 g_signal_connect(G_OBJECT(status_box->tree_view), "cursor-changed",
1886 G_CALLBACK(treeview_cursor_changed_cb), status_box->dropdown_store);
1888 gtk_tree_view_set_row_separator_func(GTK_TREE_VIEW(status_box->tree_view), dropdown_store_row_separator_func, NULL, NULL);
1890 status_box->token_status_account = check_active_accounts_for_identical_statuses();
1892 cache_pixbufs(status_box);
1893 pidgin_status_box_regenerate(status_box, TRUE);
1895 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed",
1896 status_box,
1897 PURPLE_CALLBACK(current_savedstatus_changed_cb),
1898 status_box);
1899 purple_signal_connect(purple_savedstatuses_get_handle(),
1900 "savedstatus-added", status_box,
1901 PURPLE_CALLBACK(saved_status_updated_cb), status_box);
1902 purple_signal_connect(purple_savedstatuses_get_handle(),
1903 "savedstatus-deleted", status_box,
1904 PURPLE_CALLBACK(saved_status_updated_cb), status_box);
1905 purple_signal_connect(purple_savedstatuses_get_handle(),
1906 "savedstatus-modified", status_box,
1907 PURPLE_CALLBACK(saved_status_updated_cb), status_box);
1908 purple_signal_connect(purple_accounts_get_handle(), "account-enabled", status_box,
1909 PURPLE_CALLBACK(account_enabled_cb),
1910 status_box);
1911 purple_signal_connect(purple_accounts_get_handle(), "account-disabled", status_box,
1912 PURPLE_CALLBACK(account_enabled_cb),
1913 status_box);
1914 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed", status_box,
1915 PURPLE_CALLBACK(account_status_changed_cb),
1916 status_box);
1918 purple_prefs_connect_callback(status_box, PIDGIN_PREFS_ROOT "/conversations/spellcheck",
1919 spellcheck_prefs_cb, status_box);
1920 purple_prefs_connect_callback(status_box, PIDGIN_PREFS_ROOT "/accounts/buddyicon",
1921 update_buddyicon_cb, status_box);
1922 purple_signal_connect(purple_get_core(), "uri-handler", status_box,
1923 PURPLE_CALLBACK(statusbox_uri_handler), status_box);
1927 static void
1928 pidgin_status_box_size_request(GtkWidget *widget,
1929 GtkRequisition *requisition)
1931 GtkRequisition box_req;
1932 gint border_width = GTK_CONTAINER (widget)->border_width;
1934 gtk_widget_size_request(PIDGIN_STATUS_BOX(widget)->toggle_button, requisition);
1936 /* Make this icon the same size as other buddy icons in the list; unless it already wants to be bigger */
1937 requisition->height = MAX(requisition->height, 34);
1938 requisition->height += border_width * 2;
1940 /* If the gtkimhtml is visible, then add some additional padding */
1941 gtk_widget_size_request(PIDGIN_STATUS_BOX(widget)->vbox, &box_req);
1942 if (box_req.height > 1)
1943 requisition->height += box_req.height + border_width * 2;
1945 requisition->width = 1;
1948 /* From gnome-panel */
1949 static void
1950 do_colorshift (GdkPixbuf *dest, GdkPixbuf *src, int shift)
1952 gint i, j;
1953 gint width, height, has_alpha, srcrowstride, destrowstride;
1954 guchar *target_pixels;
1955 guchar *original_pixels;
1956 guchar *pixsrc;
1957 guchar *pixdest;
1958 int val;
1959 guchar r,g,b;
1961 has_alpha = gdk_pixbuf_get_has_alpha (src);
1962 width = gdk_pixbuf_get_width (src);
1963 height = gdk_pixbuf_get_height (src);
1964 srcrowstride = gdk_pixbuf_get_rowstride (src);
1965 destrowstride = gdk_pixbuf_get_rowstride (dest);
1966 target_pixels = gdk_pixbuf_get_pixels (dest);
1967 original_pixels = gdk_pixbuf_get_pixels (src);
1969 for (i = 0; i < height; i++) {
1970 pixdest = target_pixels + i*destrowstride;
1971 pixsrc = original_pixels + i*srcrowstride;
1972 for (j = 0; j < width; j++) {
1973 r = *(pixsrc++);
1974 g = *(pixsrc++);
1975 b = *(pixsrc++);
1976 val = r + shift;
1977 *(pixdest++) = CLAMP(val, 0, 255);
1978 val = g + shift;
1979 *(pixdest++) = CLAMP(val, 0, 255);
1980 val = b + shift;
1981 *(pixdest++) = CLAMP(val, 0, 255);
1982 if (has_alpha)
1983 *(pixdest++) = *(pixsrc++);
1988 static void
1989 pidgin_status_box_size_allocate(GtkWidget *widget,
1990 GtkAllocation *allocation)
1992 PidginStatusBox *status_box = PIDGIN_STATUS_BOX(widget);
1993 GtkRequisition req = {0,0};
1994 GtkAllocation parent_alc, box_alc, icon_alc;
1995 gint border_width = GTK_CONTAINER (widget)->border_width;
1997 gtk_widget_size_request(status_box->toggle_button, &req);
1998 /* Make this icon the same size as other buddy icons in the list; unless it already wants to be bigger */
2000 req.height = MAX(req.height, 34);
2001 req.height += border_width * 2;
2003 box_alc = *allocation;
2005 box_alc.width -= (border_width * 2);
2006 box_alc.height = MAX(1, ((allocation->height - req.height) - (border_width*2)));
2007 box_alc.x += border_width;
2008 box_alc.y += req.height + border_width;
2009 gtk_widget_size_allocate(status_box->vbox, &box_alc);
2011 parent_alc = *allocation;
2012 parent_alc.height = MAX(1,req.height - (border_width *2));
2013 parent_alc.width -= (border_width * 2);
2014 parent_alc.x += border_width;
2015 parent_alc.y += border_width;
2017 if (status_box->icon_box)
2019 parent_alc.width -= (parent_alc.height + border_width);
2020 icon_alc = parent_alc;
2021 icon_alc.height = MAX(1, icon_alc.height) - 2;
2022 icon_alc.width = icon_alc.height;
2023 icon_alc.x = allocation->width - (icon_alc.width + border_width + 1);
2024 icon_alc.y += 1;
2026 if (status_box->icon_size != icon_alc.height)
2028 status_box->icon_size = icon_alc.height;
2029 pidgin_status_box_redisplay_buddy_icon(status_box);
2031 gtk_widget_size_allocate(status_box->icon_box, &icon_alc);
2033 gtk_widget_size_allocate(status_box->toggle_button, &parent_alc);
2034 widget->allocation = *allocation;
2037 static gboolean
2038 pidgin_status_box_expose_event(GtkWidget *widget,
2039 GdkEventExpose *event)
2041 PidginStatusBox *status_box = PIDGIN_STATUS_BOX(widget);
2042 gtk_container_propagate_expose(GTK_CONTAINER(widget), status_box->vbox, event);
2043 gtk_container_propagate_expose(GTK_CONTAINER(widget), status_box->toggle_button, event);
2044 if (status_box->icon_box && status_box->icon_opaque) {
2045 gtk_paint_box(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL,
2046 status_box->icon_box, "button", status_box->icon_box->allocation.x-1, status_box->icon_box->allocation.y-1,
2047 34, 34);
2049 return FALSE;
2052 static void
2053 pidgin_status_box_forall(GtkContainer *container,
2054 gboolean include_internals,
2055 GtkCallback callback,
2056 gpointer callback_data)
2058 PidginStatusBox *status_box = PIDGIN_STATUS_BOX (container);
2060 if (include_internals)
2062 (* callback) (status_box->vbox, callback_data);
2063 (* callback) (status_box->toggle_button, callback_data);
2064 (* callback) (status_box->arrow, callback_data);
2065 if (status_box->icon_box)
2066 (* callback) (status_box->icon_box, callback_data);
2070 GtkWidget *
2071 pidgin_status_box_new()
2073 return g_object_new(PIDGIN_TYPE_STATUS_BOX, "account", NULL,
2074 "iconsel", TRUE, NULL);
2077 GtkWidget *
2078 pidgin_status_box_new_with_account(PurpleAccount *account)
2080 return g_object_new(PIDGIN_TYPE_STATUS_BOX, "account", account,
2081 "iconsel", TRUE, NULL);
2085 * Add a row to the dropdown menu.
2087 * @param status_box The status box itself.
2088 * @param type A PidginStatusBoxItemType.
2089 * @param pixbuf The icon to associate with this row in the menu. The
2090 * function will try to decide a pixbuf if none is given.
2091 * @param title The title of this item. For the primitive entries,
2092 * this is something like "Available" or "Away." For
2093 * the saved statuses, this is something like
2094 * "My favorite away message!" This should be
2095 * plaintext (non-markedup) (this function escapes it).
2096 * @param desc The secondary text for this item. This will be
2097 * placed on the row below the title, in a dimmer
2098 * font (generally gray). This text should be plaintext
2099 * (non-markedup) (this function escapes it).
2100 * @param data Data to be associated with this row in the dropdown
2101 * menu. For primitives this is the value of the
2102 * PurpleStatusPrimitive. For saved statuses this is the
2103 * creation timestamp.
2105 void
2106 pidgin_status_box_add(PidginStatusBox *status_box, PidginStatusBoxItemType type, GdkPixbuf *pixbuf,
2107 const char *title, const char *desc, gpointer data)
2109 GtkTreeIter iter;
2110 char *text;
2111 const char *stock = NULL;
2113 if (desc == NULL)
2115 text = g_markup_escape_text(title, -1);
2117 else
2119 GtkStyle *style;
2120 char aa_color[8];
2121 gchar *escaped_title, *escaped_desc;
2123 style = gtk_widget_get_style(GTK_WIDGET(status_box));
2124 snprintf(aa_color, sizeof(aa_color), "#%02x%02x%02x",
2125 style->text_aa[GTK_STATE_NORMAL].red >> 8,
2126 style->text_aa[GTK_STATE_NORMAL].green >> 8,
2127 style->text_aa[GTK_STATE_NORMAL].blue >> 8);
2129 escaped_title = g_markup_escape_text(title, -1);
2130 escaped_desc = g_markup_escape_text(desc, -1);
2131 text = g_strdup_printf("%s - <span color=\"%s\" size=\"smaller\">%s</span>",
2132 escaped_title,
2133 aa_color, escaped_desc);
2134 g_free(escaped_title);
2135 g_free(escaped_desc);
2138 if (!pixbuf) {
2139 PurpleStatusPrimitive prim = PURPLE_STATUS_UNSET;
2140 if (type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE) {
2141 prim = GPOINTER_TO_INT(data);
2142 } else if (type == PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR ||
2143 type == PIDGIN_STATUS_BOX_TYPE_POPULAR) {
2144 PurpleSavedStatus *saved = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
2145 if (saved) {
2146 prim = purple_savedstatus_get_type(saved);
2150 stock = pidgin_stock_id_from_status_primitive(prim);
2153 gtk_list_store_append(status_box->dropdown_store, &iter);
2154 gtk_list_store_set(status_box->dropdown_store, &iter,
2155 TYPE_COLUMN, type,
2156 ICON_STOCK_COLUMN, stock,
2157 TEXT_COLUMN, text,
2158 TITLE_COLUMN, title,
2159 DESC_COLUMN, desc,
2160 DATA_COLUMN, data,
2161 EMBLEM_VISIBLE_COLUMN, type == PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR,
2162 EMBLEM_COLUMN, GTK_STOCK_SAVE,
2163 -1);
2164 g_free(text);
2167 void
2168 pidgin_status_box_add_separator(PidginStatusBox *status_box)
2170 /* Don't do anything unless GTK actually supports
2171 * gtk_combo_box_set_row_separator_func */
2172 GtkTreeIter iter;
2174 gtk_list_store_append(status_box->dropdown_store, &iter);
2175 gtk_list_store_set(status_box->dropdown_store, &iter,
2176 TYPE_COLUMN, PIDGIN_STATUS_BOX_TYPE_SEPARATOR,
2177 -1);
2180 void
2181 pidgin_status_box_set_network_available(PidginStatusBox *status_box, gboolean available)
2183 if (!status_box)
2184 return;
2185 status_box->network_available = available;
2186 pidgin_status_box_refresh(status_box);
2189 void
2190 pidgin_status_box_set_connecting(PidginStatusBox *status_box, gboolean connecting)
2192 if (!status_box)
2193 return;
2194 status_box->connecting = connecting;
2195 pidgin_status_box_refresh(status_box);
2198 static void
2199 pixbuf_size_prepared_cb(GdkPixbufLoader *loader, int width, int height, gpointer data)
2201 int w, h;
2202 GtkIconSize icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM);
2203 gtk_icon_size_lookup(icon_size, &w, &h);
2204 if (height > width)
2205 w = width * h / height;
2206 else if (width > height)
2207 h = height * w / width;
2208 gdk_pixbuf_loader_set_size(loader, w, h);
2211 static void
2212 pidgin_status_box_redisplay_buddy_icon(PidginStatusBox *status_box)
2215 /* This is sometimes called before the box is shown, and we will not have a size */
2216 if (status_box->icon_size <= 0)
2217 return;
2219 if (status_box->buddy_icon)
2220 g_object_unref(status_box->buddy_icon);
2221 if (status_box->buddy_icon_hover)
2222 g_object_unref(status_box->buddy_icon_hover);
2223 status_box->buddy_icon = NULL;
2224 status_box->buddy_icon_hover = NULL;
2226 if (status_box->buddy_icon_img != NULL)
2228 GdkPixbufLoader *loader;
2229 GError *error = NULL;
2231 loader = gdk_pixbuf_loader_new();
2233 g_signal_connect(G_OBJECT(loader), "size-prepared", G_CALLBACK(pixbuf_size_prepared_cb), NULL);
2234 if (!gdk_pixbuf_loader_write(loader,
2235 purple_imgstore_get_data(status_box->buddy_icon_img),
2236 purple_imgstore_get_size(status_box->buddy_icon_img),
2237 &error) || error)
2239 purple_debug_warning("gtkstatusbox", "gdk_pixbuf_loader_write() "
2240 "failed with size=%zu: %s\n",
2241 purple_imgstore_get_size(status_box->buddy_icon_img),
2242 error ? error->message : "(no error message)");
2243 if (error)
2244 g_error_free(error);
2245 } else if (!gdk_pixbuf_loader_close(loader, &error) || error) {
2246 purple_debug_warning("gtkstatusbox", "gdk_pixbuf_loader_close() "
2247 "failed for image of size %zu: %s\n",
2248 purple_imgstore_get_size(status_box->buddy_icon_img),
2249 error ? error->message : "(no error message)");
2250 if (error)
2251 g_error_free(error);
2252 } else {
2253 GdkPixbuf *buf, *scale;
2254 int scale_width, scale_height;
2256 buf = gdk_pixbuf_loader_get_pixbuf(loader);
2257 scale_width = gdk_pixbuf_get_width(buf);
2258 scale_height = gdk_pixbuf_get_height(buf);
2259 scale = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
2260 gdk_pixbuf_fill(scale, 0x00000000);
2261 gdk_pixbuf_copy_area(buf, 0, 0, scale_width, scale_height, scale, 0, 0);
2262 if (pidgin_gdk_pixbuf_is_opaque(scale))
2263 pidgin_gdk_pixbuf_make_round(scale);
2264 status_box->buddy_icon = scale;
2267 g_object_unref(loader);
2270 if (status_box->buddy_icon == NULL)
2272 /* Show a placeholder icon */
2273 GtkIconSize icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL);
2274 status_box->buddy_icon = gtk_widget_render_icon(GTK_WIDGET(status_box),
2275 PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR,
2276 icon_size, "PidginStatusBox");
2279 if (status_box->buddy_icon != NULL) {
2280 status_box->icon_opaque = pidgin_gdk_pixbuf_is_opaque(status_box->buddy_icon);
2281 gtk_image_set_from_pixbuf(GTK_IMAGE(status_box->icon), status_box->buddy_icon);
2282 status_box->buddy_icon_hover = gdk_pixbuf_copy(status_box->buddy_icon);
2283 do_colorshift(status_box->buddy_icon_hover, status_box->buddy_icon_hover, 32);
2284 gtk_widget_queue_resize(GTK_WIDGET(status_box));
2288 void
2289 pidgin_status_box_set_buddy_icon(PidginStatusBox *status_box, PurpleStoredImage *img)
2291 purple_imgstore_unref(status_box->buddy_icon_img);
2292 status_box->buddy_icon_img = img;
2293 if (status_box->buddy_icon_img != NULL)
2294 purple_imgstore_ref(status_box->buddy_icon_img);
2296 pidgin_status_box_redisplay_buddy_icon(status_box);
2299 void
2300 pidgin_status_box_pulse_connecting(PidginStatusBox *status_box)
2302 if (!status_box)
2303 return;
2304 if (!connecting_stock_ids[++status_box->connecting_index])
2305 status_box->connecting_index = 0;
2306 pidgin_status_box_refresh(status_box);
2309 static void
2310 pidgin_status_box_pulse_typing(PidginStatusBox *status_box)
2312 if (!typing_stock_ids[++status_box->typing_index])
2313 status_box->typing_index = 0;
2314 pidgin_status_box_refresh(status_box);
2317 static void
2318 activate_currently_selected_status(PidginStatusBox *status_box)
2320 PidginStatusBoxItemType type;
2321 gpointer data;
2322 gchar *title;
2323 GtkTreeIter iter;
2324 GtkTreePath *path;
2325 char *message;
2326 PurpleSavedStatus *saved_status = NULL;
2327 gboolean changed = TRUE;
2329 path = gtk_tree_row_reference_get_path(status_box->active_row);
2330 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
2331 return;
2332 gtk_tree_path_free(path);
2334 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
2335 TYPE_COLUMN, &type,
2336 DATA_COLUMN, &data,
2337 -1);
2340 * If the currently selected status is "New..." or
2341 * "Saved..." or a popular status then do nothing.
2342 * Popular statuses are
2343 * activated elsewhere, and we update the status_box
2344 * accordingly by connecting to the savedstatus-changed
2345 * signal and then calling status_menu_refresh_iter()
2347 if (type != PIDGIN_STATUS_BOX_TYPE_PRIMITIVE)
2348 return;
2350 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
2351 TITLE_COLUMN, &title, -1);
2353 message = pidgin_status_box_get_message(status_box);
2354 if (!message || !*message)
2356 gtk_widget_hide_all(status_box->vbox);
2357 status_box->imhtml_visible = FALSE;
2358 if (message != NULL)
2360 g_free(message);
2361 message = NULL;
2365 if (status_box->account == NULL) {
2366 PurpleStatusType *acct_status_type = NULL;
2367 const char *id = NULL; /* id of acct_status_type */
2368 PurpleStatusPrimitive primitive = GPOINTER_TO_INT(data);
2369 /* Global */
2370 /* Save the newly selected status to prefs.xml and status.xml */
2372 /* Has the status really been changed? */
2373 if (status_box->token_status_account) {
2374 gint active;
2375 PurpleStatus *status;
2376 GtkTreePath *path = gtk_tree_row_reference_get_path(status_box->active_row);
2377 active = gtk_tree_path_get_indices(path)[0];
2379 gtk_tree_path_free(path);
2381 status = purple_account_get_active_status(status_box->token_status_account);
2383 acct_status_type = find_status_type_by_index(status_box->token_status_account, active);
2384 id = purple_status_type_get_id(acct_status_type);
2386 if (g_str_equal(id, purple_status_get_id(status)) &&
2387 purple_strequal(message, purple_status_get_attr_string(status, "message")))
2389 /* Selected status and previous status is the same */
2390 PurpleSavedStatus *ss = purple_savedstatus_get_current();
2391 /* Make sure that statusbox displays the correct thing.
2392 * It can get messed up if the previous selection was a
2393 * saved status that wasn't supported by this account */
2394 if ((purple_savedstatus_get_type(ss) == primitive)
2395 && purple_savedstatus_is_transient(ss)
2396 && purple_savedstatus_has_substatuses(ss))
2397 changed = FALSE;
2399 } else {
2400 saved_status = purple_savedstatus_get_current();
2401 if (purple_savedstatus_get_type(saved_status) == primitive &&
2402 !purple_savedstatus_has_substatuses(saved_status) &&
2403 purple_strequal(purple_savedstatus_get_message(saved_status), message))
2405 changed = FALSE;
2409 if (changed)
2411 /* Manually find the appropriate transient status */
2412 if (status_box->token_status_account) {
2413 GList *iter = purple_savedstatuses_get_all();
2414 GList *tmp, *active_accts = purple_accounts_get_all_active();
2416 for (; iter != NULL; iter = iter->next) {
2417 PurpleSavedStatus *ss = iter->data;
2418 const char *ss_msg = purple_savedstatus_get_message(ss);
2419 /* find a known transient status that is the same as the
2420 * new selected one */
2421 if ((purple_savedstatus_get_type(ss) == primitive) && purple_savedstatus_is_transient(ss) &&
2422 purple_savedstatus_has_substatuses(ss) && /* Must have substatuses */
2423 purple_strequal(ss_msg, message))
2425 gboolean found = FALSE;
2426 /* this status must have substatuses for all the active accts */
2427 for(tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2428 PurpleAccount *acct = tmp->data;
2429 PurpleSavedStatusSub *sub = purple_savedstatus_get_substatus(ss, acct);
2430 if (sub) {
2431 const PurpleStatusType *sub_type = purple_savedstatus_substatus_get_type(sub);
2432 const char *subtype_status_id = purple_status_type_get_id(sub_type);
2433 if (purple_strequal(subtype_status_id, id)) {
2434 found = TRUE;
2435 break;
2440 if (found) {
2441 saved_status = ss;
2442 break;
2447 g_list_free(active_accts);
2449 } else {
2450 /* If we've used this type+message before, lookup the transient status */
2451 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2454 /* If this type+message is unique then create a new transient saved status */
2455 if (saved_status == NULL)
2457 saved_status = purple_savedstatus_new(NULL, primitive);
2458 purple_savedstatus_set_message(saved_status, message);
2459 if (status_box->token_status_account) {
2460 GList *tmp, *active_accts = purple_accounts_get_all_active();
2461 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2462 purple_savedstatus_set_substatus(saved_status,
2463 (PurpleAccount*) tmp->data, acct_status_type, message);
2465 g_list_free(active_accts);
2469 /* Set the status for each account */
2470 purple_savedstatus_activate(saved_status);
2472 } else {
2473 /* Per-account */
2474 gint active;
2475 PurpleStatusType *status_type;
2476 PurpleStatus *status;
2477 const char *id = NULL;
2479 status = purple_account_get_active_status(status_box->account);
2481 active = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(status_box), "active"));
2483 status_type = find_status_type_by_index(status_box->account, active);
2484 id = purple_status_type_get_id(status_type);
2486 if (g_str_equal(id, purple_status_get_id(status)) &&
2487 purple_strequal(message, purple_status_get_attr_string(status, "message")))
2489 /* Selected status and previous status is the same */
2490 changed = FALSE;
2493 if (changed)
2495 if (message)
2496 purple_account_set_status(status_box->account, id,
2497 TRUE, "message", message, NULL);
2498 else
2499 purple_account_set_status(status_box->account, id,
2500 TRUE, NULL);
2502 saved_status = purple_savedstatus_get_current();
2503 if (purple_savedstatus_is_transient(saved_status))
2504 purple_savedstatus_set_substatus(saved_status, status_box->account,
2505 status_type, message);
2509 g_free(title);
2510 g_free(message);
2513 static void update_size(PidginStatusBox *status_box)
2515 GtkTextBuffer *buffer;
2516 GtkTextIter iter;
2517 int display_lines;
2518 int lines;
2519 GdkRectangle oneline;
2520 int height;
2521 int pad_top, pad_inside, pad_bottom;
2522 gboolean interior_focus;
2523 int focus_width;
2525 if (!status_box->imhtml_visible)
2527 if (status_box->vbox != NULL)
2528 gtk_widget_set_size_request(status_box->vbox, -1, -1);
2529 return;
2532 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(status_box->imhtml));
2534 height = 0;
2535 display_lines = 1;
2536 gtk_text_buffer_get_start_iter(buffer, &iter);
2537 do {
2538 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(status_box->imhtml), &iter, &oneline);
2539 height += oneline.height;
2540 display_lines++;
2541 } while (display_lines <= 4 &&
2542 gtk_text_view_forward_display_line(GTK_TEXT_VIEW(status_box->imhtml), &iter));
2545 * This check fixes the case where the last character entered is a
2546 * newline (shift+return). For some reason the
2547 * gtk_text_view_forward_display_line() function doesn't treat this
2548 * like a new line, and so we think the input box only needs to be
2549 * two lines instead of three, for example. So we check if the
2550 * last character was a newline and add some extra height if so.
2552 if (display_lines <= 4
2553 && gtk_text_iter_backward_char(&iter)
2554 && gtk_text_iter_get_char(&iter) == '\n')
2556 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(status_box->imhtml), &iter, &oneline);
2557 height += oneline.height;
2558 display_lines++;
2561 lines = gtk_text_buffer_get_line_count(buffer);
2563 /* Show a maximum of 4 lines */
2564 lines = MIN(lines, 4);
2565 display_lines = MIN(display_lines, 4);
2567 pad_top = gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(status_box->imhtml));
2568 pad_bottom = gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(status_box->imhtml));
2569 pad_inside = gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(status_box->imhtml));
2571 height += (pad_top + pad_bottom) * lines;
2572 height += (pad_inside) * (display_lines - lines);
2574 gtk_widget_style_get(status_box->imhtml,
2575 "interior-focus", &interior_focus,
2576 "focus-line-width", &focus_width,
2577 NULL);
2578 if (!interior_focus)
2579 height += 2 * focus_width;
2581 gtk_widget_set_size_request(status_box->vbox, -1, height + PIDGIN_HIG_BOX_SPACE);
2584 static void remove_typing_cb(PidginStatusBox *status_box)
2586 if (status_box->typing == 0)
2588 /* Nothing has changed, so we don't need to do anything */
2589 status_menu_refresh_iter(status_box, FALSE);
2590 return;
2593 gtk_imhtml_set_populate_primary_clipboard(
2594 GTK_IMHTML(status_box->imhtml), TRUE);
2596 purple_timeout_remove(status_box->typing);
2597 status_box->typing = 0;
2599 activate_currently_selected_status(status_box);
2600 pidgin_status_box_refresh(status_box);
2603 static void pidgin_status_box_changed(PidginStatusBox *status_box)
2605 GtkTreePath *path = gtk_tree_row_reference_get_path(status_box->active_row);
2606 GtkTreeIter iter;
2607 PidginStatusBoxItemType type;
2608 gpointer data;
2609 GList *accounts = NULL, *node;
2610 int active;
2611 gboolean wastyping = FALSE;
2614 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
2615 return;
2616 active = gtk_tree_path_get_indices(path)[0];
2617 gtk_tree_path_free(path);
2618 g_object_set_data(G_OBJECT(status_box), "active", GINT_TO_POINTER(active));
2620 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
2621 TYPE_COLUMN, &type,
2622 DATA_COLUMN, &data,
2623 -1);
2624 if ((wastyping = (status_box->typing != 0)))
2625 purple_timeout_remove(status_box->typing);
2626 status_box->typing = 0;
2628 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box)))
2630 if (type == PIDGIN_STATUS_BOX_TYPE_POPULAR || type == PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR)
2632 PurpleSavedStatus *saved;
2633 saved = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
2634 g_return_if_fail(saved != NULL);
2635 purple_savedstatus_activate(saved);
2636 return;
2639 if (type == PIDGIN_STATUS_BOX_TYPE_CUSTOM)
2641 PurpleSavedStatus *saved_status;
2642 saved_status = purple_savedstatus_get_current();
2643 if (purple_savedstatus_get_type(saved_status) == PURPLE_STATUS_AVAILABLE)
2644 saved_status = purple_savedstatus_new(NULL, PURPLE_STATUS_AWAY);
2645 pidgin_status_editor_show(FALSE,
2646 purple_savedstatus_is_transient(saved_status)
2647 ? saved_status : NULL);
2648 status_menu_refresh_iter(status_box, wastyping);
2649 if (wastyping)
2650 pidgin_status_box_refresh(status_box);
2651 return;
2654 if (type == PIDGIN_STATUS_BOX_TYPE_SAVED)
2656 pidgin_status_window_show();
2657 status_menu_refresh_iter(status_box, wastyping);
2658 if (wastyping)
2659 pidgin_status_box_refresh(status_box);
2660 return;
2665 * Show the message box whenever the primitive allows for a
2666 * message attribute on any protocol that is enabled,
2667 * or our protocol, if we have account set
2669 if (status_box->account)
2670 accounts = g_list_prepend(accounts, status_box->account);
2671 else
2672 accounts = purple_accounts_get_all_active();
2673 status_box->imhtml_visible = FALSE;
2674 for (node = accounts; node != NULL; node = node->next)
2676 PurpleAccount *account;
2677 PurpleStatusType *status_type;
2679 account = node->data;
2680 status_type = purple_account_get_status_type_with_primitive(account, GPOINTER_TO_INT(data));
2681 if ((status_type != NULL) &&
2682 (purple_status_type_get_attr(status_type, "message") != NULL))
2684 status_box->imhtml_visible = TRUE;
2685 break;
2688 g_list_free(accounts);
2690 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box)))
2692 if (status_box->imhtml_visible)
2694 GtkTextIter start, end;
2695 GtkTextBuffer *buffer;
2696 gtk_widget_show_all(status_box->vbox);
2697 status_box->typing = purple_timeout_add_seconds(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
2698 gtk_widget_grab_focus(status_box->imhtml);
2699 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(status_box->imhtml));
2701 gtk_imhtml_set_populate_primary_clipboard(
2702 GTK_IMHTML(status_box->imhtml), FALSE);
2704 gtk_text_buffer_get_bounds(buffer, &start, &end);
2705 gtk_text_buffer_move_mark(buffer, gtk_text_buffer_get_mark(buffer, "insert"), &end);
2706 gtk_text_buffer_move_mark(buffer, gtk_text_buffer_get_mark(buffer, "selection_bound"), &start);
2708 else
2710 gtk_widget_hide_all(status_box->vbox);
2711 activate_currently_selected_status(status_box); /* This is where we actually set the status */
2714 pidgin_status_box_refresh(status_box);
2717 static gint
2718 get_statusbox_index(PidginStatusBox *box, PurpleSavedStatus *saved_status)
2720 gint index = -1;
2722 switch (purple_savedstatus_get_type(saved_status))
2724 /* In reverse order */
2725 case PURPLE_STATUS_OFFLINE:
2726 index++;
2727 case PURPLE_STATUS_INVISIBLE:
2728 index++;
2729 case PURPLE_STATUS_UNAVAILABLE:
2730 index++;
2731 case PURPLE_STATUS_AWAY:
2732 index++;
2733 case PURPLE_STATUS_AVAILABLE:
2734 index++;
2735 break;
2736 default:
2737 break;
2740 return index;
2743 static void imhtml_changed_cb(GtkTextBuffer *buffer, void *data)
2745 PidginStatusBox *status_box = (PidginStatusBox*)data;
2746 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box)))
2748 if (status_box->typing != 0) {
2749 pidgin_status_box_pulse_typing(status_box);
2750 purple_timeout_remove(status_box->typing);
2752 status_box->typing = purple_timeout_add_seconds(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
2754 pidgin_status_box_refresh(status_box);
2757 static void imhtml_format_changed_cb(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons, void *data)
2759 imhtml_changed_cb(NULL, data);
2762 char *pidgin_status_box_get_message(PidginStatusBox *status_box)
2764 if (status_box->imhtml_visible)
2765 return gtk_imhtml_get_markup(GTK_IMHTML(status_box->imhtml));
2766 else
2767 return NULL;