pidgin/gtkstatusbox.c

branch
cpw.khc.msnp14
changeset 20478
46933dc62880
parent 20472
6a6d2ef151e6
parent 16179
bdf68342e1ce
child 20481
65485e2ed8a3
equal deleted inserted replaced
20476:198222e01a7d 20478:46933dc62880
1 /*
2 * @file gtkstatusbox.c GTK+ Status Selection Widget
3 * @ingroup gtkui
4 *
5 * pidgin
6 *
7 * Pidgin is the legal property of its developers, whose names are too numerous
8 * to list here. Please refer to the COPYRIGHT file distributed with this
9 * source distribution.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 */
25
26 /*
27 * The status box is made up of two main pieces:
28 * - The box that displays the current status, which is made
29 * of a GtkListStore ("status_box->store") and GtkCellView
30 * ("status_box->cell_view"). There is always exactly 1 row
31 * in this list store. Only the TYPE_ICON and TYPE_TEXT
32 * columns are used in this list store.
33 * - The dropdown menu that lets users select a status, which
34 * is made of a GtkComboBox ("status_box") and GtkListStore
35 * ("status_box->dropdown_store"). This dropdown is shown
36 * when the user clicks on the box that displays the current
37 * status. This list store contains one row for Available,
38 * one row for Away, etc., a few rows for popular statuses,
39 * and the "New..." and "Saved..." options.
40 */
41
42 #include <gdk/gdkkeysyms.h>
43
44 #include "account.h"
45 #include "core.h"
46 #include "internal.h"
47 #include "network.h"
48 #include "savedstatuses.h"
49 #include "status.h"
50 #include "debug.h"
51
52 #include "pidgin.h"
53 #include "gtksavedstatuses.h"
54 #include "pidginstock.h"
55 #include "gtkstatusbox.h"
56 #include "gtkutils.h"
57
58 #ifdef USE_GTKSPELL
59 # include <gtkspell/gtkspell.h>
60 # ifdef _WIN32
61 # include "wspell.h"
62 # endif
63 #endif
64
65 #define TYPING_TIMEOUT 4000
66
67 static void imhtml_changed_cb(GtkTextBuffer *buffer, void *data);
68 static void imhtml_format_changed_cb(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons, void *data);
69 static void remove_typing_cb(PidginStatusBox *box);
70 static void update_size (PidginStatusBox *box);
71 static gint get_statusbox_index(PidginStatusBox *box, PurpleSavedStatus *saved_status);
72
73 static void pidgin_status_box_pulse_typing(PidginStatusBox *status_box);
74 static void pidgin_status_box_refresh(PidginStatusBox *status_box);
75 static void status_menu_refresh_iter(PidginStatusBox *status_box);
76 static void pidgin_status_box_regenerate(PidginStatusBox *status_box);
77 static void pidgin_status_box_changed(PidginStatusBox *box);
78 static void pidgin_status_box_size_request (GtkWidget *widget, GtkRequisition *requisition);
79 static void pidgin_status_box_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
80 static gboolean pidgin_status_box_expose_event (GtkWidget *widget, GdkEventExpose *event);
81 static void pidgin_status_box_redisplay_buddy_icon(PidginStatusBox *status_box);
82 static void pidgin_status_box_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data);
83 static void pidgin_status_box_popup(PidginStatusBox *box);
84 static void pidgin_status_box_popdown(PidginStatusBox *box);
85
86 static void do_colorshift (GdkPixbuf *dest, GdkPixbuf *src, int shift);
87 static void icon_choose_cb(const char *filename, gpointer data);
88 static void remove_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box);
89
90 enum {
91 /** A PidginStatusBoxItemType */
92 TYPE_COLUMN,
93
94 /**
95 * This is a GdkPixbuf (the other columns are strings).
96 * This column is visible.
97 */
98 ICON_COLUMN,
99
100 /** The text displayed on the status box. This column is visible. */
101 TEXT_COLUMN,
102
103 /** The plain-English title of this item */
104 TITLE_COLUMN,
105
106 /** A plain-English description of this item */
107 DESC_COLUMN,
108
109 /*
110 * This value depends on TYPE_COLUMN. For POPULAR types,
111 * this is the creation time. For PRIMITIVE types,
112 * this is the PurpleStatusPrimitive.
113 */
114 DATA_COLUMN,
115
116 NUM_COLUMNS
117 };
118
119 enum {
120 PROP_0,
121 PROP_ACCOUNT,
122 PROP_ICON_SEL,
123 };
124
125 GtkContainerClass *parent_class = NULL;
126
127 static void pidgin_status_box_class_init (PidginStatusBoxClass *klass);
128 static void pidgin_status_box_init (PidginStatusBox *status_box);
129
130 GType
131 pidgin_status_box_get_type (void)
132 {
133 static GType status_box_type = 0;
134
135 if (!status_box_type)
136 {
137 static const GTypeInfo status_box_info =
138 {
139 sizeof (PidginStatusBoxClass),
140 NULL, /* base_init */
141 NULL, /* base_finalize */
142 (GClassInitFunc) pidgin_status_box_class_init,
143 NULL, /* class_finalize */
144 NULL, /* class_data */
145 sizeof (PidginStatusBox),
146 0,
147 (GInstanceInitFunc) pidgin_status_box_init,
148 NULL /* value_table */
149 };
150
151 status_box_type = g_type_register_static(GTK_TYPE_CONTAINER,
152 "PidginStatusBox",
153 &status_box_info,
154 0);
155 }
156
157 return status_box_type;
158 }
159
160 static void
161 pidgin_status_box_get_property(GObject *object, guint param_id,
162 GValue *value, GParamSpec *psec)
163 {
164 PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(object);
165
166 switch (param_id) {
167 case PROP_ACCOUNT:
168 g_value_set_pointer(value, statusbox->account);
169 break;
170 case PROP_ICON_SEL:
171 g_value_set_boolean(value, statusbox->icon_box != NULL);
172 break;
173 default:
174 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, psec);
175 break;
176 }
177 }
178
179 static void
180 update_to_reflect_account_status(PidginStatusBox *status_box, PurpleAccount *account, PurpleStatus *newstatus)
181 {
182 const GList *l;
183 int status_no = -1;
184 const PurpleStatusType *statustype = NULL;
185 const char *message;
186
187 statustype = purple_status_type_find_with_id((GList *)purple_account_get_status_types(account),
188 (char *)purple_status_type_get_id(purple_status_get_type(newstatus)));
189
190 for (l = purple_account_get_status_types(account); l != NULL; l = l->next) {
191 PurpleStatusType *status_type = (PurpleStatusType *)l->data;
192
193 if (!purple_status_type_is_user_settable(status_type))
194 continue;
195 status_no++;
196 if (statustype == status_type)
197 break;
198 }
199
200 if (status_no != -1) {
201 GtkTreePath *path;
202 gtk_widget_set_sensitive(GTK_WIDGET(status_box), FALSE);
203 path = gtk_tree_path_new_from_indices(status_no, -1);
204 if (status_box->active_row)
205 gtk_tree_row_reference_free(status_box->active_row);
206 status_box->active_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box->dropdown_store), path);
207 gtk_tree_path_free(path);
208
209 message = purple_status_get_attr_string(newstatus, "message");
210
211 if (!message || !*message)
212 {
213 gtk_widget_hide_all(status_box->vbox);
214 status_box->imhtml_visible = FALSE;
215 }
216 else
217 {
218 gtk_widget_show_all(status_box->vbox);
219 status_box->imhtml_visible = TRUE;
220 gtk_imhtml_clear(GTK_IMHTML(status_box->imhtml));
221 gtk_imhtml_clear_formatting(GTK_IMHTML(status_box->imhtml));
222 gtk_imhtml_append_text(GTK_IMHTML(status_box->imhtml), message, 0);
223 }
224 gtk_widget_set_sensitive(GTK_WIDGET(status_box), TRUE);
225 pidgin_status_box_refresh(status_box);
226 }
227 }
228
229 static void
230 account_status_changed_cb(PurpleAccount *account, PurpleStatus *oldstatus, PurpleStatus *newstatus, PidginStatusBox *status_box)
231 {
232 if (status_box->account == account)
233 update_to_reflect_account_status(status_box, account, newstatus);
234 else if (status_box->token_status_account == account)
235 status_menu_refresh_iter(status_box);
236 }
237
238 static gboolean
239 icon_box_press_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
240 {
241 if (event->button == 3) {
242 GtkWidget *menu_item;
243
244 if (box->icon_box_menu)
245 gtk_widget_destroy(box->icon_box_menu);
246
247 box->icon_box_menu = gtk_menu_new();
248
249 menu_item = pidgin_new_item_from_stock(box->icon_box_menu, _("Remove"), GTK_STOCK_REMOVE,
250 G_CALLBACK(remove_buddy_icon_cb),
251 box, 0, 0, NULL);
252 if (purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon") == NULL)
253 gtk_widget_set_sensitive(menu_item, FALSE);
254
255 gtk_menu_popup(GTK_MENU(box->icon_box_menu), NULL, NULL, NULL, NULL,
256 event->button, event->time);
257
258 } else {
259 if (box->buddy_icon_sel) {
260 gtk_window_present(GTK_WINDOW(box->buddy_icon_sel));
261 return FALSE;
262 }
263
264 box->buddy_icon_sel = pidgin_buddy_icon_chooser_new(NULL, icon_choose_cb, box);
265 gtk_widget_show_all(box->buddy_icon_sel);
266 }
267 return FALSE;
268 }
269
270 static void
271 icon_box_dnd_cb(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
272 GtkSelectionData *sd, guint info, guint t, PidginStatusBox *box)
273 {
274 gchar *name = (gchar *)sd->data;
275
276 if ((sd->length >= 0) && (sd->format == 8)) {
277 /* Well, it looks like the drag event was cool.
278 * Let's do something with it */
279 if (!g_ascii_strncasecmp(name, "file://", 7)) {
280 GError *converr = NULL;
281 gchar *tmp, *rtmp;
282
283 if(!(tmp = g_filename_from_uri(name, NULL, &converr))) {
284 purple_debug(PURPLE_DEBUG_ERROR, "buddyicon", "%s\n",
285 (converr ? converr->message :
286 "g_filename_from_uri error"));
287 return;
288 }
289 if ((rtmp = strchr(tmp, '\r')) || (rtmp = strchr(tmp, '\n')))
290 *rtmp = '\0';
291 icon_choose_cb(tmp, box);
292 g_free(tmp);
293 }
294 gtk_drag_finish(dc, TRUE, FALSE, t);
295 }
296 gtk_drag_finish(dc, FALSE, FALSE, t);
297 }
298
299 static void
300 statusbox_got_url(PurpleUtilFetchUrlData *url_data, gpointer user_data,
301 const gchar *themedata, size_t len, const gchar *error_message)
302 {
303 FILE *f;
304 gchar *path;
305
306 if ((error_message != NULL) || (len == 0))
307 return;
308
309 f = purple_mkstemp(&path, TRUE);
310 fwrite(themedata, len, 1, f);
311 fclose(f);
312
313 icon_choose_cb(path, user_data);
314
315 g_unlink(path);
316 g_free(path);
317 }
318
319
320 static gboolean
321 statusbox_uri_handler(const char *proto, const char *cmd, GHashTable *params, void *data)
322 {
323 const char *src;
324
325 if (g_ascii_strcasecmp(proto, "aim"))
326 return FALSE;
327
328 if (g_ascii_strcasecmp(cmd, "buddyicon"))
329 return FALSE;
330
331 src = g_hash_table_lookup(params, "account");
332 if (src == NULL)
333 return FALSE;
334
335 purple_util_fetch_url(src, TRUE, NULL, FALSE, statusbox_got_url, data);
336 return TRUE;
337 }
338
339 static gboolean
340 icon_box_enter_cb(GtkWidget *widget, GdkEventCrossing *event, PidginStatusBox *box)
341 {
342 gdk_window_set_cursor(widget->window, box->hand_cursor);
343 gtk_image_set_from_pixbuf(GTK_IMAGE(box->icon), box->buddy_icon_hover);
344 return FALSE;
345 }
346
347 static gboolean
348 icon_box_leave_cb(GtkWidget *widget, GdkEventCrossing *event, PidginStatusBox *box)
349 {
350 gdk_window_set_cursor(widget->window, box->arrow_cursor);
351 gtk_image_set_from_pixbuf(GTK_IMAGE(box->icon), box->buddy_icon) ;
352 return FALSE;
353 }
354
355
356 static const GtkTargetEntry dnd_targets[] = {
357 {"text/plain", 0, 0},
358 {"text/uri-list", 0, 1},
359 {"STRING", 0, 2}
360 };
361
362 static void
363 setup_icon_box(PidginStatusBox *status_box)
364 {
365 if (status_box->icon_box != NULL)
366 return;
367
368 status_box->icon = gtk_image_new();
369 status_box->icon_box = gtk_event_box_new();
370 gtk_widget_set_parent(status_box->icon_box, GTK_WIDGET(status_box));
371 gtk_widget_show(status_box->icon_box);
372
373 if (status_box->account &&
374 !purple_account_get_bool(status_box->account, "use-global-buddyicon", TRUE))
375 {
376 char *string = purple_buddy_icons_get_full_path(purple_account_get_buddy_icon(status_box->account));
377 pidgin_status_box_set_buddy_icon(status_box, string);
378 g_free(string);
379 }
380 else
381 {
382 pidgin_status_box_set_buddy_icon(status_box, purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon"));
383 }
384
385 status_box->hand_cursor = gdk_cursor_new (GDK_HAND2);
386 status_box->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR);
387
388 /* Set up DND */
389 gtk_drag_dest_set(status_box->icon_box,
390 GTK_DEST_DEFAULT_MOTION |
391 GTK_DEST_DEFAULT_DROP,
392 dnd_targets,
393 sizeof(dnd_targets) / sizeof(GtkTargetEntry),
394 GDK_ACTION_COPY);
395
396 g_signal_connect(G_OBJECT(status_box->icon_box), "drag_data_received", G_CALLBACK(icon_box_dnd_cb), status_box);
397 g_signal_connect(G_OBJECT(status_box->icon_box), "enter-notify-event", G_CALLBACK(icon_box_enter_cb), status_box);
398 g_signal_connect(G_OBJECT(status_box->icon_box), "leave-notify-event", G_CALLBACK(icon_box_leave_cb), status_box);
399 g_signal_connect(G_OBJECT(status_box->icon_box), "button-press-event", G_CALLBACK(icon_box_press_cb), status_box);
400
401 gtk_container_add(GTK_CONTAINER(status_box->icon_box), status_box->icon);
402 gtk_widget_show(status_box->icon);
403 }
404
405 static void
406 destroy_icon_box(PidginStatusBox *statusbox)
407 {
408 if (statusbox->icon_box == NULL)
409 return;
410
411 gtk_widget_destroy(statusbox->icon_box);
412 gdk_cursor_unref(statusbox->hand_cursor);
413 gdk_cursor_unref(statusbox->arrow_cursor);
414
415 g_object_unref(G_OBJECT(statusbox->buddy_icon));
416 g_object_unref(G_OBJECT(statusbox->buddy_icon_hover));
417
418 if (statusbox->buddy_icon_sel)
419 gtk_widget_destroy(statusbox->buddy_icon_sel);
420
421 if (statusbox->icon_box_menu)
422 gtk_widget_destroy(statusbox->icon_box_menu);
423
424 g_free(statusbox->buddy_icon_path);
425
426 statusbox->icon = NULL;
427 statusbox->icon_box = NULL;
428 statusbox->icon_box_menu = NULL;
429 statusbox->buddy_icon_path = NULL;
430 statusbox->buddy_icon = NULL;
431 statusbox->buddy_icon_hover = NULL;
432 statusbox->hand_cursor = NULL;
433 statusbox->arrow_cursor = NULL;
434 }
435
436 static void
437 pidgin_status_box_set_property(GObject *object, guint param_id,
438 const GValue *value, GParamSpec *pspec)
439 {
440 PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(object);
441
442 switch (param_id) {
443 case PROP_ICON_SEL:
444 if (g_value_get_boolean(value)) {
445 if (statusbox->account) {
446 PurplePlugin *plug = purple_plugins_find_with_id(purple_account_get_protocol_id(statusbox->account));
447 if (plug) {
448 PurplePluginProtocolInfo *prplinfo = PURPLE_PLUGIN_PROTOCOL_INFO(plug);
449 if (prplinfo && prplinfo->icon_spec.format != NULL)
450 setup_icon_box(statusbox);
451 }
452 } else {
453 setup_icon_box(statusbox);
454 }
455 } else {
456 destroy_icon_box(statusbox);
457 }
458 break;
459 case PROP_ACCOUNT:
460 statusbox->account = g_value_get_pointer(value);
461
462 pidgin_status_box_regenerate(statusbox);
463
464 break;
465 default:
466 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
467 break;
468 }
469 }
470
471 static void
472 pidgin_status_box_finalize(GObject *obj)
473 {
474 PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(obj);
475
476 purple_signals_disconnect_by_handle(statusbox);
477 purple_prefs_disconnect_by_handle(statusbox);
478
479 gdk_cursor_unref(statusbox->hand_cursor);
480 gdk_cursor_unref(statusbox->arrow_cursor);
481
482 g_object_unref(G_OBJECT(statusbox->buddy_icon));
483 g_object_unref(G_OBJECT(statusbox->buddy_icon_hover));
484
485 if (statusbox->buddy_icon_sel)
486 gtk_widget_destroy(statusbox->buddy_icon_sel);
487
488 g_free(statusbox->buddy_icon_path);
489
490 G_OBJECT_CLASS(parent_class)->finalize(obj);
491 }
492
493 static GType
494 pidgin_status_box_child_type (GtkContainer *container)
495 {
496 return GTK_TYPE_WIDGET;
497 }
498
499 static void
500 pidgin_status_box_class_init (PidginStatusBoxClass *klass)
501 {
502 GObjectClass *object_class;
503 GtkWidgetClass *widget_class;
504 GtkContainerClass *container_class = (GtkContainerClass*)klass;
505
506 parent_class = g_type_class_peek_parent(klass);
507
508 widget_class = (GtkWidgetClass*)klass;
509 widget_class->size_request = pidgin_status_box_size_request;
510 widget_class->size_allocate = pidgin_status_box_size_allocate;
511 widget_class->expose_event = pidgin_status_box_expose_event;
512
513 container_class->child_type = pidgin_status_box_child_type;
514 container_class->forall = pidgin_status_box_forall;
515 container_class->remove = NULL;
516
517 object_class = (GObjectClass *)klass;
518
519 object_class->finalize = pidgin_status_box_finalize;
520
521 object_class->get_property = pidgin_status_box_get_property;
522 object_class->set_property = pidgin_status_box_set_property;
523
524 g_object_class_install_property(object_class,
525 PROP_ACCOUNT,
526 g_param_spec_pointer("account",
527 "Account",
528 "The account, or NULL for all accounts",
529 G_PARAM_READWRITE
530 )
531 );
532 g_object_class_install_property(object_class,
533 PROP_ICON_SEL,
534 g_param_spec_boolean("iconsel",
535 "Icon Selector",
536 "Whether the icon selector should be displayed or not.",
537 FALSE,
538 G_PARAM_READWRITE
539 )
540 );
541 }
542
543 /**
544 * This updates the text displayed on the status box so that it shows
545 * the current status. This is the only function in this file that
546 * should modify status_box->store
547 */
548 static void
549 pidgin_status_box_refresh(PidginStatusBox *status_box)
550 {
551 GtkIconSize icon_size;
552 GtkStyle *style;
553 char aa_color[8];
554 PurpleSavedStatus *saved_status;
555 char *primary, *secondary, *text;
556 GdkPixbuf *pixbuf;
557 GtkTreePath *path;
558 gboolean account_status = FALSE;
559 PurpleAccount *acct = (status_box->token_status_account) ? status_box->token_status_account : status_box->account;
560
561 icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL);
562
563 style = gtk_widget_get_style(GTK_WIDGET(status_box));
564 snprintf(aa_color, sizeof(aa_color), "#%02x%02x%02x",
565 style->text_aa[GTK_STATE_NORMAL].red >> 8,
566 style->text_aa[GTK_STATE_NORMAL].green >> 8,
567 style->text_aa[GTK_STATE_NORMAL].blue >> 8);
568
569 saved_status = purple_savedstatus_get_current();
570
571 if (status_box->account || (status_box->token_status_account
572 && purple_savedstatus_is_transient(saved_status)))
573 account_status = TRUE;
574
575 /* Primary */
576 if (status_box->typing != 0)
577 {
578 GtkTreeIter iter;
579 PidginStatusBoxItemType type;
580 gpointer data;
581
582 /* Primary (get the status selected in the dropdown) */
583 path = gtk_tree_row_reference_get_path(status_box->active_row);
584 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
585 return;
586 gtk_tree_path_free(path);
587
588 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
589 TYPE_COLUMN, &type,
590 DATA_COLUMN, &data,
591 -1);
592 if (type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE)
593 primary = g_strdup(purple_primitive_get_name_from_type(GPOINTER_TO_INT(data)));
594 else
595 /* This should never happen, but just in case... */
596 primary = g_strdup("New status");
597 }
598 else if (account_status)
599 primary = g_strdup(purple_status_get_name(purple_account_get_active_status(acct)));
600 else if (purple_savedstatus_is_transient(saved_status))
601 primary = g_strdup(purple_primitive_get_name_from_type(purple_savedstatus_get_type(saved_status)));
602 else
603 primary = g_markup_escape_text(purple_savedstatus_get_title(saved_status), -1);
604
605 /* Secondary */
606 if (status_box->typing != 0)
607 secondary = g_strdup(_("Typing"));
608 else if (status_box->connecting)
609 secondary = g_strdup(_("Connecting"));
610 else if (!status_box->network_available)
611 secondary = g_strdup(_("Waiting for network connection"));
612 else if (purple_savedstatus_is_transient(saved_status))
613 secondary = NULL;
614 else
615 {
616 const char *message;
617 char *tmp;
618 message = purple_savedstatus_get_message(saved_status);
619 if (message != NULL)
620 {
621 tmp = purple_markup_strip_html(message);
622 purple_util_chrreplace(tmp, '\n', ' ');
623 secondary = g_markup_escape_text(tmp, -1);
624 g_free(tmp);
625 }
626 else
627 secondary = NULL;
628 }
629
630 /* Pixbuf */
631 if (status_box->typing != 0)
632 pixbuf = status_box->typing_pixbufs[status_box->typing_index];
633 else if (status_box->connecting)
634 pixbuf = status_box->connecting_pixbufs[status_box->connecting_index];
635 else
636 {
637 PurpleStatusType *status_type;
638 PurpleStatusPrimitive prim;
639 GtkIconSize icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL);
640 if (account_status) {
641 status_type = purple_status_get_type(purple_account_get_active_status(acct));
642 prim = purple_status_type_get_primitive(status_type);
643 } else {
644 prim = purple_savedstatus_get_type(saved_status);
645 }
646
647 if (prim == PURPLE_STATUS_UNAVAILABLE)
648 pixbuf = gtk_widget_render_icon (GTK_WIDGET(status_box), PIDGIN_STOCK_STATUS_BUSY,
649 icon_size, "PidginStatusBox");
650 else if (prim == PURPLE_STATUS_AWAY)
651 pixbuf = gtk_widget_render_icon (GTK_WIDGET(status_box), PIDGIN_STOCK_STATUS_AWAY,
652 icon_size, "PidginStatusBox");
653 else if (prim == PURPLE_STATUS_EXTENDED_AWAY)
654 pixbuf = gtk_widget_render_icon (GTK_WIDGET(status_box), PIDGIN_STOCK_STATUS_XA,
655 icon_size, "PidginStatusBox");
656 else if (prim == PURPLE_STATUS_OFFLINE)
657 pixbuf = gtk_widget_render_icon (GTK_WIDGET(status_box), PIDGIN_STOCK_STATUS_OFFLINE,
658 icon_size, "PidginStatusBox");
659 else
660 pixbuf = gtk_widget_render_icon (GTK_WIDGET(status_box), PIDGIN_STOCK_STATUS_AVAILABLE,
661 icon_size, "PidginStatusBox");
662 #if 0
663 if (account_status)
664 pixbuf = pidgin_create_prpl_icon_with_status(acct,
665 purple_status_get_type(purple_account_get_active_status(acct)),
666 0.5);
667 else
668 pixbuf = pidgin_create_purple_icon_with_status(
669 purple_savedstatus_get_type(saved_status),
670 0.5);
671
672 if (!purple_savedstatus_is_transient(saved_status))
673 {
674 GdkPixbuf *emblem;
675
676 /* Overlay a disk in the bottom left corner */
677 emblem = gtk_widget_render_icon(GTK_WIDGET(status_box->vbox),
678 GTK_STOCK_SAVE, icon_size, "PidginStatusBox");
679 if (emblem != NULL)
680 {
681 int width, height;
682 width = gdk_pixbuf_get_width(pixbuf) / 2;
683 height = gdk_pixbuf_get_height(pixbuf) / 2;
684 gdk_pixbuf_composite(emblem, pixbuf, 0, height,
685 width, height, 0, height,
686 0.5, 0.5, GDK_INTERP_BILINEAR, 255);
687 g_object_unref(G_OBJECT(emblem));
688 }
689 }
690 #endif
691
692 }
693
694 if (status_box->account != NULL) {
695 text = g_strdup_printf("%s - <span size=\"smaller\" color=\"%s\">%s</span>",
696 purple_account_get_username(status_box->account),
697 aa_color, secondary ? secondary : primary);
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);
703 }
704 g_free(primary);
705 g_free(secondary);
706
707 /*
708 * Only two columns are used in this list store (does it
709 * really need to be a list store?)
710 */
711 gtk_list_store_set(status_box->store, &(status_box->iter),
712 ICON_COLUMN, pixbuf,
713 TEXT_COLUMN, text,
714 -1);
715 if ((status_box->typing == 0) && (!status_box->connecting))
716 g_object_unref(pixbuf);
717 g_free(text);
718
719 /* Make sure to activate the only row in the tree view */
720 path = gtk_tree_path_new_from_string("0");
721 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(status_box->cell_view), path);
722 gtk_tree_path_free(path);
723
724 update_size(status_box);
725 }
726
727 static PurpleStatusType *
728 find_status_type_by_index(const PurpleAccount *account, gint active)
729 {
730 const GList *l = purple_account_get_status_types(account);
731 gint i;
732
733 for (i = 0; l; l = l->next) {
734 PurpleStatusType *status_type = l->data;
735 if (!purple_status_type_is_user_settable(status_type))
736 continue;
737
738 if (active == i)
739 return status_type;
740 i++;
741 }
742
743 return NULL;
744 }
745
746 /**
747 * This updates the GtkTreeView so that it correctly shows the state
748 * we are currently using. It is used when the current state is
749 * updated from somewhere other than the GtkStatusBox (from a plugin,
750 * or when signing on with the "-n" option, for example). It is
751 * also used when the user selects the "New..." option.
752 *
753 * Maybe we could accomplish this by triggering off the mouse and
754 * keyboard signals instead of the changed signal?
755 */
756 static void
757 status_menu_refresh_iter(PidginStatusBox *status_box)
758 {
759 PurpleSavedStatus *saved_status;
760 PurpleStatusPrimitive primitive;
761 gint index;
762 const char *message;
763 GtkTreePath *path = NULL;
764
765 /* this function is inappropriate for ones with accounts */
766 if (status_box->account)
767 return;
768
769 saved_status = purple_savedstatus_get_current();
770
771 /*
772 * Suppress the "changed" signal because the status
773 * was changed programmatically.
774 */
775 gtk_widget_set_sensitive(GTK_WIDGET(status_box), FALSE);
776
777 /*
778 * If there is a token-account, then select the primitive from the
779 * dropdown using a loop. Otherwise select from the default list.
780 */
781 primitive = purple_savedstatus_get_type(saved_status);
782 if (!status_box->token_status_account && purple_savedstatus_is_transient(saved_status) &&
783 ((primitive == PURPLE_STATUS_AVAILABLE) || (primitive == PURPLE_STATUS_AWAY) ||
784 (primitive == PURPLE_STATUS_INVISIBLE) || (primitive == PURPLE_STATUS_OFFLINE)) &&
785 (!purple_savedstatus_has_substatuses(saved_status)))
786 {
787 index = get_statusbox_index(status_box, saved_status);
788 path = gtk_tree_path_new_from_indices(index, -1);
789 }
790 else
791 {
792 GtkTreeIter iter;
793 PidginStatusBoxItemType type;
794 gpointer data;
795
796 /* If this saved status is in the list store, then set it as the active item */
797 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(status_box->dropdown_store), &iter))
798 {
799 do
800 {
801 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
802 TYPE_COLUMN, &type,
803 DATA_COLUMN, &data,
804 -1);
805
806 /* This is a special case because Primitives for the token_status_account are actually
807 * saved statuses with substatuses for the enabled accounts */
808 if (status_box->token_status_account && purple_savedstatus_is_transient(saved_status)
809 && type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE && primitive == GPOINTER_TO_INT(data))
810 {
811 char *name;
812 const char *acct_status_name = purple_status_get_name(
813 purple_account_get_active_status(status_box->token_status_account));
814
815 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
816 TEXT_COLUMN, &name, -1);
817
818 if (!purple_savedstatus_has_substatuses(saved_status)
819 || !strcmp(name, acct_status_name))
820 {
821 /* Found! */
822 path = gtk_tree_model_get_path(GTK_TREE_MODEL(status_box->dropdown_store), &iter);
823 g_free(name);
824 break;
825 }
826 g_free(name);
827
828 } else if ((type == PIDGIN_STATUS_BOX_TYPE_POPULAR) &&
829 (GPOINTER_TO_INT(data) == purple_savedstatus_get_creation_time(saved_status)))
830 {
831 /* Found! */
832 path = gtk_tree_model_get_path(GTK_TREE_MODEL(status_box->dropdown_store), &iter);
833 break;
834 }
835 } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(status_box->dropdown_store), &iter));
836 }
837 }
838
839 if (status_box->active_row)
840 gtk_tree_row_reference_free(status_box->active_row);
841 if (path) { /* path should never be NULL */
842 status_box->active_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box->dropdown_store), path);
843 gtk_tree_path_free(path);
844 } else
845 status_box->active_row = NULL;
846
847 message = purple_savedstatus_get_message(saved_status);
848 if (!purple_savedstatus_is_transient(saved_status) || !message || !*message)
849 {
850 status_box->imhtml_visible = FALSE;
851 gtk_widget_hide_all(status_box->vbox);
852 }
853 else
854 {
855 status_box->imhtml_visible = TRUE;
856 gtk_widget_show_all(status_box->vbox);
857
858 /*
859 * Suppress the "changed" signal because the status
860 * was changed programmatically.
861 */
862 gtk_widget_set_sensitive(GTK_WIDGET(status_box->imhtml), FALSE);
863
864 gtk_imhtml_clear(GTK_IMHTML(status_box->imhtml));
865 gtk_imhtml_clear_formatting(GTK_IMHTML(status_box->imhtml));
866 gtk_imhtml_append_text(GTK_IMHTML(status_box->imhtml), message, 0);
867 gtk_widget_set_sensitive(GTK_WIDGET(status_box->imhtml), TRUE);
868 }
869
870 update_size(status_box);
871
872 /* Stop suppressing the "changed" signal. */
873 gtk_widget_set_sensitive(GTK_WIDGET(status_box), TRUE);
874 }
875
876 static void
877 add_popular_statuses(PidginStatusBox *statusbox)
878 {
879 GtkIconSize icon_size;
880 GList *list, *cur;
881 GdkPixbuf *pixbuf;
882
883 list = purple_savedstatuses_get_popular(6);
884 if (list == NULL)
885 /* Odd... oh well, nothing we can do about it. */
886 return;
887
888 icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL);
889
890 pidgin_status_box_add_separator(statusbox);
891
892 for (cur = list; cur != NULL; cur = cur->next)
893 {
894 PurpleSavedStatus *saved = cur->data;
895 const gchar *message;
896 gchar *stripped = NULL;
897 PurpleStatusPrimitive prim;
898
899 /* Get an appropriate status icon */
900 prim = purple_savedstatus_get_type(saved);
901
902 if (prim == PURPLE_STATUS_UNAVAILABLE)
903 pixbuf = gtk_widget_render_icon (GTK_WIDGET(statusbox),
904 PIDGIN_STOCK_STATUS_BUSY, icon_size, "PidginStatusBox");
905 else if (prim == PURPLE_STATUS_AWAY)
906 pixbuf = gtk_widget_render_icon (GTK_WIDGET(statusbox),
907 PIDGIN_STOCK_STATUS_AWAY, icon_size, "PidginStatusBox");
908 else if (prim == PURPLE_STATUS_EXTENDED_AWAY)
909 pixbuf = gtk_widget_render_icon (GTK_WIDGET(statusbox),
910 PIDGIN_STOCK_STATUS_XA, icon_size, "PidginStatusBox");
911 else if (prim == PURPLE_STATUS_OFFLINE)
912 pixbuf = gtk_widget_render_icon (GTK_WIDGET(statusbox),
913 PIDGIN_STOCK_STATUS_OFFLINE, icon_size, "PidginStatusBox");
914 else
915 pixbuf = gtk_widget_render_icon (GTK_WIDGET(statusbox),
916 PIDGIN_STOCK_STATUS_AVAILABLE, icon_size, "PidginStatusBox");
917
918 if (purple_savedstatus_is_transient(saved))
919 {
920 /*
921 * Transient statuses do not have a title, so the savedstatus
922 * API returns the message when purple_savedstatus_get_title() is
923 * called, so we don't need to get the message a second time.
924 */
925 }
926 else
927 {
928 message = purple_savedstatus_get_message(saved);
929 if (message != NULL)
930 {
931 stripped = purple_markup_strip_html(message);
932 purple_util_chrreplace(stripped, '\n', ' ');
933 }
934 #if 0
935 /* Overlay a disk in the bottom left corner */
936 emblem = gtk_widget_render_icon(GTK_WIDGET(statusbox->vbox),
937 GTK_STOCK_SAVE, icon_size, "PidginStatusBox");
938 if (emblem != NULL)
939 {
940 width = gdk_pixbuf_get_width(pixbuf) / 2;
941 height = gdk_pixbuf_get_height(pixbuf) / 2;
942 gdk_pixbuf_composite(emblem, pixbuf, 0, height,
943 width, height, 0, height,
944 0.5, 0.5, GDK_INTERP_BILINEAR, 255);
945 g_object_unref(G_OBJECT(emblem));
946 }
947 #endif
948 }
949
950 pidgin_status_box_add(statusbox, PIDGIN_STATUS_BOX_TYPE_POPULAR,
951 pixbuf, purple_savedstatus_get_title(saved), stripped,
952 GINT_TO_POINTER(purple_savedstatus_get_creation_time(saved)));
953 g_free(stripped);
954 if (pixbuf != NULL)
955 g_object_unref(G_OBJECT(pixbuf));
956 }
957
958 g_list_free(list);
959 }
960
961 /* This returns NULL if the active accounts don't have identical
962 * statuses and a token account if they do */
963 static PurpleAccount* check_active_accounts_for_identical_statuses()
964 {
965 PurpleAccount *acct = NULL, *acct2;
966 GList *tmp, *tmp2, *active_accts = purple_accounts_get_all_active();
967 const GList *s, *s1, *s2;
968
969 for (tmp = active_accts; tmp; tmp = tmp->next) {
970 acct = tmp->data;
971 s = purple_account_get_status_types(acct);
972 for (tmp2 = tmp->next; tmp2; tmp2 = tmp2->next) {
973 acct2 = tmp2->data;
974
975 /* Only actually look at the statuses if the accounts use the same prpl */
976 if (strcmp(purple_account_get_protocol_id(acct), purple_account_get_protocol_id(acct2))) {
977 acct = NULL;
978 break;
979 }
980
981 s2 = purple_account_get_status_types(acct2);
982
983 s1 = s;
984 while (s1 && s2) {
985 PurpleStatusType *st1 = s1->data, *st2 = s2->data;
986 /* TODO: Are these enough to consider the statuses identical? */
987 if (purple_status_type_get_primitive(st1) != purple_status_type_get_primitive(st2)
988 || strcmp(purple_status_type_get_id(st1), purple_status_type_get_id(st2))
989 || strcmp(purple_status_type_get_name(st1), purple_status_type_get_name(st2))) {
990 acct = NULL;
991 break;
992 }
993
994 s1 = s1->next;
995 s2 = s2->next;
996 }
997
998 if (s1 != s2) {/* Will both be NULL if matched */
999 acct = NULL;
1000 break;
1001 }
1002 }
1003 if (!acct)
1004 break;
1005 }
1006 g_list_free(active_accts);
1007
1008 return acct;
1009 }
1010
1011 static void
1012 add_account_statuses(PidginStatusBox *status_box, PurpleAccount *account)
1013 {
1014 /* Per-account */
1015 const GList *l;
1016 GdkPixbuf *pixbuf;
1017
1018 for (l = purple_account_get_status_types(account); l != NULL; l = l->next)
1019 {
1020 PurpleStatusType *status_type = (PurpleStatusType *)l->data;
1021 PurpleStatusPrimitive prim;
1022 GtkIconSize icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL);
1023
1024 if (!purple_status_type_is_user_settable(status_type))
1025 continue;
1026
1027 prim = purple_status_type_get_primitive(status_type);
1028
1029 if (prim == PURPLE_STATUS_UNAVAILABLE)
1030 pixbuf = gtk_widget_render_icon (GTK_WIDGET(status_box), PIDGIN_STOCK_STATUS_BUSY,
1031 icon_size, "PidginStatusBox");
1032 else if (prim == PURPLE_STATUS_AWAY)
1033 pixbuf = gtk_widget_render_icon (GTK_WIDGET(status_box), PIDGIN_STOCK_STATUS_AWAY,
1034 icon_size, "PidginStatusBox");
1035 else if (prim == PURPLE_STATUS_EXTENDED_AWAY)
1036 pixbuf = gtk_widget_render_icon (GTK_WIDGET(status_box), PIDGIN_STOCK_STATUS_XA,
1037 icon_size, "PidginStatusBox");
1038 else if (prim == PURPLE_STATUS_OFFLINE)
1039 pixbuf = gtk_widget_render_icon (GTK_WIDGET(status_box), PIDGIN_STOCK_STATUS_OFFLINE,
1040 icon_size, "PidginStatusBox");
1041 else
1042 pixbuf = gtk_widget_render_icon (GTK_WIDGET(status_box), PIDGIN_STOCK_STATUS_AVAILABLE,
1043 icon_size, "PidginStatusBox");
1044
1045 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box),
1046 PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, pixbuf,
1047 purple_status_type_get_name(status_type),
1048 NULL,
1049 GINT_TO_POINTER(purple_status_type_get_primitive(status_type)));
1050 if (pixbuf != NULL)
1051 g_object_unref(pixbuf);
1052 }
1053 }
1054
1055 static void
1056 pidgin_status_box_regenerate(PidginStatusBox *status_box)
1057 {
1058 GdkPixbuf *pixbuf, *pixbuf2, *pixbuf3, *pixbuf4;
1059 GtkIconSize icon_size;
1060
1061 icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL);
1062
1063 /* Unset the model while clearing it */
1064 gtk_tree_view_set_model(GTK_TREE_VIEW(status_box->tree_view), NULL);
1065 gtk_list_store_clear(status_box->dropdown_store);
1066 /* Don't set the model until the new statuses have been added to the box.
1067 * What is presumably a bug in Gtk < 2.4 causes things to get all confused
1068 * if we do this here. */
1069 /* gtk_combo_box_set_model(GTK_COMBO_BOX(status_box), GTK_TREE_MODEL(status_box->dropdown_store)); */
1070
1071 if (status_box->account == NULL)
1072 {
1073 pixbuf = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_STATUS_AVAILABLE,
1074 icon_size, "PidginStatusBox");
1075 /* Do all the currently enabled accounts have the same statuses?
1076 * If so, display them instead of our global list.
1077 */
1078 if (status_box->token_status_account) {
1079 add_account_statuses(status_box, status_box->token_status_account);
1080 } else {
1081 /* Global */
1082 pixbuf2 = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_STATUS_AWAY,
1083 icon_size, "PidginStatusBox");
1084 pixbuf3 = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_STATUS_OFFLINE,
1085 icon_size, "PidginStatusBox");
1086 pixbuf4 = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_STATUS_AVAILABLE_I,
1087 icon_size, "PidginStatusBox");
1088
1089 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, pixbuf, _("Available"), NULL, GINT_TO_POINTER(PURPLE_STATUS_AVAILABLE));
1090 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, pixbuf2, _("Away"), NULL, GINT_TO_POINTER(PURPLE_STATUS_AWAY));
1091 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, pixbuf4, _("Invisible"), NULL, GINT_TO_POINTER(PURPLE_STATUS_INVISIBLE));
1092 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, pixbuf3, _("Offline"), NULL, GINT_TO_POINTER(PURPLE_STATUS_OFFLINE));
1093
1094 if (pixbuf2) g_object_unref(G_OBJECT(pixbuf2));
1095 if (pixbuf3) g_object_unref(G_OBJECT(pixbuf3));
1096 if (pixbuf4) g_object_unref(G_OBJECT(pixbuf4));
1097 }
1098
1099 add_popular_statuses(status_box);
1100
1101 pidgin_status_box_add_separator(PIDGIN_STATUS_BOX(status_box));
1102 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_CUSTOM, pixbuf, _("New..."), NULL, NULL);
1103 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_SAVED, pixbuf, _("Saved..."), NULL, NULL);
1104 if (pixbuf) g_object_unref(G_OBJECT(pixbuf));
1105
1106 status_menu_refresh_iter(status_box);
1107 pidgin_status_box_refresh(status_box);
1108
1109 } else {
1110 add_account_statuses(status_box, status_box->account);
1111 update_to_reflect_account_status(status_box, status_box->account,
1112 purple_account_get_active_status(status_box->account));
1113 }
1114 gtk_tree_view_set_model(GTK_TREE_VIEW(status_box->tree_view), GTK_TREE_MODEL(status_box->dropdown_store));
1115 gtk_tree_view_set_search_column(GTK_TREE_VIEW(status_box->tree_view), TEXT_COLUMN);
1116 }
1117
1118 static gboolean combo_box_scroll_event_cb(GtkWidget *w, GdkEventScroll *event, GtkIMHtml *imhtml)
1119 {
1120 pidgin_status_box_popup(PIDGIN_STATUS_BOX(w));
1121 return TRUE;
1122 }
1123
1124 static gboolean imhtml_scroll_event_cb(GtkWidget *w, GdkEventScroll *event, GtkIMHtml *imhtml)
1125 {
1126 if (event->direction == GDK_SCROLL_UP)
1127 gtk_imhtml_page_up(imhtml);
1128 else if (event->direction == GDK_SCROLL_DOWN)
1129 gtk_imhtml_page_down(imhtml);
1130 return TRUE;
1131 }
1132
1133 static int imhtml_remove_focus(GtkWidget *w, GdkEventKey *event, PidginStatusBox *status_box)
1134 {
1135 if (event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab)
1136 {
1137 /* If last inserted character is a tab, then remove the focus from here */
1138 GtkWidget *top = gtk_widget_get_toplevel(w);
1139 g_signal_emit_by_name(G_OBJECT(top), "move_focus",
1140 (event->state & GDK_SHIFT_MASK) ?
1141 GTK_DIR_TAB_BACKWARD: GTK_DIR_TAB_FORWARD);
1142 return TRUE;
1143 }
1144 if (!status_box->typing != 0)
1145 return FALSE;
1146
1147 /* Reset the status if Escape was pressed */
1148 if (event->keyval == GDK_Escape)
1149 {
1150 g_source_remove(status_box->typing);
1151 status_box->typing = 0;
1152 if (status_box->account != NULL)
1153 update_to_reflect_account_status(status_box, status_box->account,
1154 purple_account_get_active_status(status_box->account));
1155 else {
1156 status_menu_refresh_iter(status_box);
1157 pidgin_status_box_refresh(status_box);
1158 }
1159 return TRUE;
1160 }
1161
1162 pidgin_status_box_pulse_typing(status_box);
1163 g_source_remove(status_box->typing);
1164 status_box->typing = g_timeout_add(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
1165
1166 return FALSE;
1167 }
1168
1169 #if GTK_CHECK_VERSION(2,6,0)
1170 static gboolean
1171 dropdown_store_row_separator_func(GtkTreeModel *model,
1172 GtkTreeIter *iter, gpointer data)
1173 {
1174 PidginStatusBoxItemType type;
1175
1176 gtk_tree_model_get(model, iter, TYPE_COLUMN, &type, -1);
1177
1178 if (type == PIDGIN_STATUS_BOX_TYPE_SEPARATOR)
1179 return TRUE;
1180
1181 return FALSE;
1182 }
1183 #endif
1184
1185 static void
1186 cache_pixbufs(PidginStatusBox *status_box)
1187 {
1188 GtkIconSize icon_size;
1189
1190 g_object_set(G_OBJECT(status_box->icon_rend), "xpad", 3, NULL);
1191 icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL);
1192
1193 if (status_box->connecting_pixbufs[0] != NULL)
1194 gdk_pixbuf_unref(status_box->connecting_pixbufs[0]);
1195 if (status_box->connecting_pixbufs[1] != NULL)
1196 gdk_pixbuf_unref(status_box->connecting_pixbufs[1]);
1197 if (status_box->connecting_pixbufs[2] != NULL)
1198 gdk_pixbuf_unref(status_box->connecting_pixbufs[2]);
1199 if (status_box->connecting_pixbufs[3] != NULL)
1200 gdk_pixbuf_unref(status_box->connecting_pixbufs[3]);
1201
1202 status_box->connecting_index = 0;
1203 status_box->connecting_pixbufs[0] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_CONNECT0,
1204 icon_size, "PidginStatusBox");
1205 status_box->connecting_pixbufs[1] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_CONNECT1,
1206 icon_size, "PidginStatusBox");
1207 status_box->connecting_pixbufs[2] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_CONNECT2,
1208 icon_size, "PidginStatusBox");
1209 status_box->connecting_pixbufs[3] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_CONNECT3,
1210 icon_size, "PidginStatusBox");
1211 status_box->connecting_pixbufs[4] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_CONNECT4,
1212 icon_size, "PidginStatusBox");
1213 status_box->connecting_pixbufs[5] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_CONNECT5,
1214 icon_size, "PidginStatusBox");
1215 status_box->connecting_pixbufs[6] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_CONNECT6,
1216 icon_size, "PidginStatusBox");
1217 status_box->connecting_pixbufs[7] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_CONNECT7,
1218 icon_size, "PidginStatusBox");
1219 status_box->connecting_pixbufs[8] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_CONNECT8,
1220 icon_size, "PidginStatusBox");
1221
1222 if (status_box->typing_pixbufs[0] != NULL)
1223 gdk_pixbuf_unref(status_box->typing_pixbufs[0]);
1224 if (status_box->typing_pixbufs[1] != NULL)
1225 gdk_pixbuf_unref(status_box->typing_pixbufs[1]);
1226 if (status_box->typing_pixbufs[2] != NULL)
1227 gdk_pixbuf_unref(status_box->typing_pixbufs[2]);
1228 if (status_box->typing_pixbufs[3] != NULL)
1229 gdk_pixbuf_unref(status_box->typing_pixbufs[3]);
1230
1231 status_box->typing_index = 0;
1232 status_box->typing_pixbufs[0] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_TYPING0,
1233 icon_size, "PidginStatusBox");
1234 status_box->typing_pixbufs[1] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_TYPING1,
1235 icon_size, "PidginStatusBox");
1236 status_box->typing_pixbufs[2] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_TYPING2,
1237 icon_size, "PidginStatusBox");
1238 status_box->typing_pixbufs[3] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_TYPING3,
1239 icon_size, "PidginStatusBox");
1240 status_box->typing_pixbufs[4] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_TYPING4,
1241 icon_size, "PidginStatusBox");
1242 }
1243
1244 static void account_enabled_cb(PurpleAccount *acct, PidginStatusBox *status_box) {
1245 PurpleAccount *initial_token_acct = status_box->token_status_account;
1246
1247 status_box->token_status_account = check_active_accounts_for_identical_statuses();
1248
1249 /* Regenerate the list if it has changed */
1250 if (initial_token_acct != status_box->token_status_account) {
1251 pidgin_status_box_regenerate(status_box);
1252 }
1253
1254 }
1255
1256 static void
1257 current_savedstatus_changed_cb(PurpleSavedStatus *now, PurpleSavedStatus *old, PidginStatusBox *status_box)
1258 {
1259 /* Make sure our current status is added to the list of popular statuses */
1260 pidgin_status_box_regenerate(status_box);
1261 }
1262
1263 static void
1264 spellcheck_prefs_cb(const char *name, PurplePrefType type,
1265 gconstpointer value, gpointer data)
1266 {
1267 #ifdef USE_GTKSPELL
1268 PidginStatusBox *status_box = (PidginStatusBox *)data;
1269
1270 if (value)
1271 pidgin_setup_gtkspell(GTK_TEXT_VIEW(status_box->imhtml));
1272 else
1273 {
1274 GtkSpell *spell;
1275 spell = gtkspell_get_from_text_view(GTK_TEXT_VIEW(status_box->imhtml));
1276 gtkspell_detach(spell);
1277 }
1278 #endif
1279 }
1280
1281 #if 0
1282 static gboolean button_released_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
1283 {
1284
1285 if (event->button != 1)
1286 return FALSE;
1287 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box->toggle_button), FALSE);
1288 if (!box->imhtml_visible)
1289 g_signal_emit_by_name(G_OBJECT(box), "changed", NULL, NULL);
1290 return TRUE;
1291 }
1292
1293 static gboolean button_pressed_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
1294 {
1295 if (event->button != 1)
1296 return FALSE;
1297 gtk_combo_box_popup(GTK_COMBO_BOX(box));
1298 // Disabled until button_released_cb works
1299 // gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box->toggle_button), TRUE);
1300 return TRUE;
1301 }
1302 #endif
1303
1304 static void
1305 pidgin_status_box_list_position (PidginStatusBox *status_box, int *x, int *y, int *width, int *height)
1306 {
1307 #if GTK_CHECK_VERSION(2,2,0)
1308 GdkScreen *screen;
1309 gint monitor_num;
1310 GdkRectangle monitor;
1311 #endif
1312 GtkRequisition popup_req;
1313 GtkPolicyType hpolicy, vpolicy;
1314
1315 gdk_window_get_origin (GTK_WIDGET(status_box)->window, x, y);
1316
1317 *x += GTK_WIDGET(status_box)->allocation.x;
1318 *y += GTK_WIDGET(status_box)->allocation.y;
1319
1320 *width = GTK_WIDGET(status_box)->allocation.width;
1321
1322 hpolicy = vpolicy = GTK_POLICY_NEVER;
1323 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (status_box->scrolled_window),
1324 hpolicy, vpolicy);
1325 gtk_widget_size_request (status_box->popup_frame, &popup_req);
1326
1327 if (popup_req.width > *width)
1328 {
1329 hpolicy = GTK_POLICY_ALWAYS;
1330 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (status_box->scrolled_window),
1331 hpolicy, vpolicy);
1332 gtk_widget_size_request (status_box->popup_frame, &popup_req);
1333 }
1334
1335 *height = popup_req.height;
1336
1337 #if GTK_CHECK_VERSION(2,2,0)
1338 screen = gtk_widget_get_screen (GTK_WIDGET (status_box));
1339 monitor_num = gdk_screen_get_monitor_at_window (screen,
1340 GTK_WIDGET (status_box)->window);
1341 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
1342
1343 if (*x < monitor.x)
1344 *x = monitor.x;
1345 else if (*x + *width > monitor.x + monitor.width)
1346 *x = monitor.x + monitor.width - *width;
1347
1348 if (*y + GTK_WIDGET(status_box)->allocation.height + *height <= monitor.y + monitor.height)
1349 *y += GTK_WIDGET(status_box)->allocation.height;
1350 else if (*y - *height >= monitor.y)
1351 *y -= *height;
1352 else if (monitor.y + monitor.height - (*y + GTK_WIDGET(status_box)->allocation.height) > *y - monitor.y)
1353 {
1354 *y += GTK_WIDGET(status_box)->allocation.height;
1355 *height = monitor.y + monitor.height - *y;
1356 }
1357 else
1358 {
1359 *height = *y - monitor.y;
1360 *y = monitor.y;
1361 }
1362
1363 if (popup_req.height > *height)
1364 {
1365 vpolicy = GTK_POLICY_ALWAYS;
1366
1367 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (status_box->scrolled_window),
1368 hpolicy, vpolicy);
1369 }
1370 #endif
1371 }
1372
1373 static gboolean
1374 popup_grab_on_window (GdkWindow *window,
1375 guint32 activate_time,
1376 gboolean grab_keyboard)
1377 {
1378 if ((gdk_pointer_grab (window, TRUE,
1379 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1380 GDK_POINTER_MOTION_MASK,
1381 NULL, NULL, activate_time) == 0))
1382 {
1383 if (!grab_keyboard ||
1384 gdk_keyboard_grab (window, TRUE,
1385 activate_time) == 0)
1386 return TRUE;
1387 else
1388 {
1389 #if GTK_CHECK_VERSION(2,2,0)
1390 gdk_display_pointer_ungrab (gdk_drawable_get_display (window),
1391 activate_time);
1392 #else
1393 gdk_pointer_ungrab(activate_time);
1394 gdk_keyboard_ungrab(activate_time);
1395 #endif
1396 return FALSE;
1397 }
1398 }
1399
1400 return FALSE;
1401 }
1402
1403
1404 static void
1405 pidgin_status_box_popup(PidginStatusBox *box)
1406 {
1407 int width, height, x, y;
1408 pidgin_status_box_list_position (box, &x, &y, &width, &height);
1409
1410 gtk_widget_set_size_request (box->popup_window, width, height);
1411 gtk_window_move (GTK_WINDOW (box->popup_window), x, y);
1412 gtk_widget_show(box->popup_window);
1413 gtk_widget_grab_focus (box->tree_view);
1414 if (!popup_grab_on_window (box->popup_window->window,
1415 GDK_CURRENT_TIME, TRUE)) {
1416 gtk_widget_hide (box->popup_window);
1417 return;
1418 }
1419 gtk_grab_add (box->popup_window);
1420 box->popup_in_progress = TRUE;
1421 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (box->toggle_button),
1422 TRUE);
1423
1424 if (box->active_row) {
1425 GtkTreePath *path = gtk_tree_row_reference_get_path(box->active_row);
1426 gtk_tree_view_set_cursor(GTK_TREE_VIEW(box->tree_view), path, NULL, FALSE);
1427 gtk_tree_path_free(path);
1428 }
1429 }
1430
1431 static void
1432 pidgin_status_box_popdown(PidginStatusBox *box) {
1433 gtk_widget_hide(box->popup_window);
1434 box->popup_in_progress = FALSE;
1435 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (box->toggle_button),
1436 FALSE);
1437 gtk_grab_remove (box->popup_window);
1438 }
1439
1440
1441 static void
1442 toggled_cb(GtkWidget *widget, PidginStatusBox *box)
1443 {
1444 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
1445 if (!box->popup_in_progress)
1446 pidgin_status_box_popup (box);
1447 } else {
1448 pidgin_status_box_popdown(box);
1449 }
1450 }
1451
1452 static void
1453 buddy_icon_set_cb(const char *filename, PidginStatusBox *box)
1454 {
1455
1456 if (box->account) {
1457 PurplePlugin *plug = purple_find_prpl(purple_account_get_protocol_id(box->account));
1458 if (plug) {
1459 PurplePluginProtocolInfo *prplinfo = PURPLE_PLUGIN_PROTOCOL_INFO(plug);
1460 if (prplinfo && prplinfo->icon_spec.format) {
1461 char *icon = NULL;
1462 if (filename)
1463 icon = pidgin_convert_buddy_icon(plug, filename);
1464 purple_account_set_bool(box->account, "use-global-buddyicon", (filename != NULL));
1465 purple_account_set_ui_string(box->account, PIDGIN_UI, "non-global-buddyicon-cached-path", icon);
1466 purple_account_set_buddy_icon_path(box->account, filename);
1467 purple_account_set_buddy_icon(box->account, icon);
1468 g_free(icon);
1469 }
1470 }
1471 } else {
1472 GList *accounts;
1473 for (accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) {
1474 PurpleAccount *account = accounts->data;
1475 PurplePlugin *plug = purple_find_prpl(purple_account_get_protocol_id(account));
1476 if (plug) {
1477 PurplePluginProtocolInfo *prplinfo = PURPLE_PLUGIN_PROTOCOL_INFO(plug);
1478 if (prplinfo != NULL &&
1479 purple_account_get_bool(account, "use-global-buddyicon", TRUE) &&
1480 prplinfo->icon_spec.format) {
1481 char *icon = NULL;
1482 if (filename)
1483 icon = pidgin_convert_buddy_icon(plug, filename);
1484 purple_account_set_buddy_icon_path(account, filename);
1485 purple_account_set_buddy_icon(account, icon);
1486 g_free(icon);
1487 }
1488 }
1489 }
1490 }
1491 pidgin_status_box_set_buddy_icon(box, filename);
1492 }
1493
1494 static void
1495 remove_buddy_icon_cb(GtkWidget *w, PidginStatusBox *box)
1496 {
1497 if (box->account == NULL)
1498 /* The pref-connect callback does the actual work */
1499 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon", NULL);
1500 else
1501 buddy_icon_set_cb(NULL, box);
1502
1503 gtk_widget_destroy(box->icon_box_menu);
1504 box->icon_box_menu = NULL;
1505 }
1506
1507 static void
1508 icon_choose_cb(const char *filename, gpointer data)
1509 {
1510 PidginStatusBox *box = data;
1511 if (filename) {
1512 if (box->account == NULL)
1513 /* The pref-connect callback does the actual work */
1514 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon", filename);
1515 else
1516 buddy_icon_set_cb(filename, box);
1517 }
1518
1519 box->buddy_icon_sel = NULL;
1520 }
1521
1522 static void
1523 update_buddyicon_cb(const char *name, PurplePrefType type,
1524 gconstpointer value, gpointer data)
1525 {
1526 buddy_icon_set_cb(value, (PidginStatusBox*) data);
1527 }
1528
1529 static void
1530 treeview_activate_current_selection(PidginStatusBox *status_box, GtkTreePath *path)
1531 {
1532 if (status_box->active_row)
1533 gtk_tree_row_reference_free(status_box->active_row);
1534
1535 status_box->active_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box->dropdown_store), path);
1536
1537 pidgin_status_box_popdown (status_box);
1538 pidgin_status_box_changed(status_box);
1539 }
1540
1541 static gboolean
1542 treeview_button_release_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *status_box)
1543 {
1544 GtkTreePath *path = NULL;
1545 int ret;
1546 GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);
1547
1548 if (ewidget != status_box->tree_view) {
1549 if (ewidget == status_box->toggle_button &&
1550 status_box->popup_in_progress &&
1551 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (status_box->toggle_button))) {
1552 pidgin_status_box_popdown (status_box);
1553 return TRUE;
1554 }
1555
1556 /* released outside treeview */
1557 if (ewidget != status_box->toggle_button)
1558 {
1559 pidgin_status_box_popdown (status_box);
1560 return TRUE;
1561 }
1562
1563 return FALSE;
1564 }
1565
1566 ret = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (status_box->tree_view),
1567 event->x, event->y,
1568 &path,
1569 NULL, NULL, NULL);
1570
1571 if (!ret)
1572 return TRUE; /* clicked outside window? */
1573
1574 treeview_activate_current_selection(status_box, path);
1575 gtk_tree_path_free (path);
1576
1577 return TRUE;
1578 }
1579
1580 static gboolean
1581 treeview_key_press_event(GtkWidget *widget,
1582 GdkEventKey *event, PidginStatusBox *box)
1583 {
1584 if (box->popup_in_progress) {
1585 if (event->keyval == GDK_Escape) {
1586 pidgin_status_box_popdown(box);
1587 return TRUE;
1588 } else if (event->keyval == GDK_Return) {
1589 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(box->tree_view));
1590 GtkTreeIter iter;
1591 GtkTreePath *path;
1592
1593 if (gtk_tree_selection_get_selected(sel, NULL, &iter)) {
1594 path = gtk_tree_model_get_path(GTK_TREE_MODEL(box->dropdown_store), &iter);
1595 treeview_activate_current_selection(box, path);
1596 gtk_tree_path_free (path);
1597 return TRUE;
1598 }
1599 }
1600 }
1601 return FALSE;
1602 }
1603
1604 static void
1605 pidgin_status_box_init (PidginStatusBox *status_box)
1606 {
1607 GtkCellRenderer *text_rend;
1608 GtkCellRenderer *icon_rend;
1609 GtkTextBuffer *buffer;
1610 GtkWidget *toplevel;
1611 GtkTreeSelection *sel;
1612
1613 GTK_WIDGET_SET_FLAGS (status_box, GTK_NO_WINDOW);
1614 status_box->imhtml_visible = FALSE;
1615 status_box->network_available = purple_network_is_available();
1616 status_box->connecting = FALSE;
1617 status_box->typing = 0;
1618 status_box->toggle_button = gtk_toggle_button_new();
1619 status_box->hbox = gtk_hbox_new(FALSE, 6);
1620 status_box->cell_view = gtk_cell_view_new();
1621 status_box->vsep = gtk_vseparator_new();
1622 status_box->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
1623
1624 status_box->store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_INT, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
1625 status_box->dropdown_store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_INT, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
1626 ;
1627 gtk_cell_view_set_model(GTK_CELL_VIEW(status_box->cell_view), GTK_TREE_MODEL(status_box->store));
1628 gtk_list_store_append(status_box->store, &(status_box->iter));
1629
1630 gtk_container_add(GTK_CONTAINER(status_box->toggle_button), status_box->hbox);
1631 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->cell_view, TRUE, TRUE, 0);
1632 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->vsep, FALSE, FALSE, 0);
1633 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->arrow, FALSE, FALSE, 0);
1634 gtk_widget_show_all(status_box->toggle_button);
1635 #if GTK_CHECK_VERSION(2,4,0)
1636 gtk_button_set_focus_on_click(GTK_BUTTON(status_box->toggle_button), FALSE);
1637 #endif
1638
1639 text_rend = gtk_cell_renderer_text_new();
1640 icon_rend = gtk_cell_renderer_pixbuf_new();
1641
1642 status_box->popup_window = gtk_window_new (GTK_WINDOW_POPUP);
1643
1644 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (status_box));
1645 if (GTK_IS_WINDOW (toplevel)) {
1646 gtk_window_set_transient_for (GTK_WINDOW (status_box->popup_window),
1647 GTK_WINDOW (toplevel));
1648 }
1649
1650 gtk_window_set_resizable (GTK_WINDOW (status_box->popup_window), FALSE);
1651 #if GTK_CHECK_VERSION(2,2,0)
1652 gtk_window_set_screen (GTK_WINDOW (status_box->popup_window),
1653 gtk_widget_get_screen (GTK_WIDGET (status_box)));
1654 #endif
1655 status_box->popup_frame = gtk_frame_new (NULL);
1656 gtk_frame_set_shadow_type (GTK_FRAME (status_box->popup_frame),
1657 GTK_SHADOW_ETCHED_IN);
1658 gtk_container_add (GTK_CONTAINER (status_box->popup_window),
1659 status_box->popup_frame);
1660
1661 gtk_widget_show (status_box->popup_frame);
1662
1663 status_box->scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1664
1665 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (status_box->scrolled_window),
1666 GTK_POLICY_NEVER,
1667 GTK_POLICY_NEVER);
1668 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (status_box->scrolled_window),
1669 GTK_SHADOW_NONE);
1670
1671 gtk_widget_show (status_box->scrolled_window);
1672
1673 gtk_container_add (GTK_CONTAINER (status_box->popup_frame),
1674 status_box->scrolled_window);
1675
1676 status_box->tree_view = gtk_tree_view_new ();
1677 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (status_box->tree_view));
1678 gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE);
1679 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (status_box->tree_view),
1680 FALSE);
1681 #if GTK_CHECK_VERSION(2,6,0)
1682 gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (status_box->tree_view),
1683 TRUE);
1684 #endif
1685 gtk_tree_view_set_model (GTK_TREE_VIEW (status_box->tree_view),
1686 GTK_TREE_MODEL(status_box->dropdown_store));
1687 status_box->column = gtk_tree_view_column_new ();
1688 gtk_tree_view_append_column (GTK_TREE_VIEW (status_box->tree_view),
1689 status_box->column);
1690 gtk_tree_view_column_pack_start(status_box->column, icon_rend, FALSE);
1691 gtk_tree_view_column_pack_start(status_box->column, text_rend, TRUE);
1692 gtk_tree_view_column_set_attributes(status_box->column, icon_rend, "pixbuf", ICON_COLUMN, NULL);
1693 gtk_tree_view_column_set_attributes(status_box->column, text_rend, "markup", TEXT_COLUMN, NULL);
1694 gtk_container_add(GTK_CONTAINER(status_box->scrolled_window), status_box->tree_view);
1695 gtk_widget_show(status_box->tree_view);
1696 gtk_tree_view_set_search_column(GTK_TREE_VIEW(status_box->tree_view), TEXT_COLUMN);
1697 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(status_box->tree_view),
1698 pidgin_tree_view_search_equal_func, NULL, NULL);
1699
1700 #if GTK_CHECK_VERSION(2, 6, 0)
1701 g_object_set(text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1702 #endif
1703
1704 status_box->icon_rend = gtk_cell_renderer_pixbuf_new();
1705 status_box->text_rend = gtk_cell_renderer_text_new();
1706 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), status_box->icon_rend, FALSE);
1707 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), status_box->text_rend, TRUE);
1708 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), status_box->icon_rend, "pixbuf", ICON_COLUMN, NULL);
1709 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), status_box->text_rend, "markup", TEXT_COLUMN, NULL);
1710 #if GTK_CHECK_VERSION(2, 6, 0)
1711 g_object_set(status_box->text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1712 #endif
1713
1714 status_box->vbox = gtk_vbox_new(0, FALSE);
1715 status_box->sw = pidgin_create_imhtml(FALSE, &status_box->imhtml, NULL, NULL);
1716 gtk_imhtml_set_editable(GTK_IMHTML(status_box->imhtml), TRUE);
1717
1718 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(status_box->imhtml));
1719 #if 0
1720 g_signal_connect(G_OBJECT(status_box->toggle_button), "button-press-event",
1721 G_CALLBACK(button_pressed_cb), status_box);
1722 g_signal_connect(G_OBJECT(status_box->toggle_button), "button-release-event",
1723 G_CALLBACK(button_released_cb), status_box);
1724 #endif
1725 g_signal_connect(G_OBJECT(status_box->toggle_button), "toggled",
1726 G_CALLBACK(toggled_cb), status_box);
1727 g_signal_connect(G_OBJECT(buffer), "changed", G_CALLBACK(imhtml_changed_cb), status_box);
1728 g_signal_connect(G_OBJECT(status_box->imhtml), "format_function_toggle",
1729 G_CALLBACK(imhtml_format_changed_cb), status_box);
1730 g_signal_connect(G_OBJECT(status_box->imhtml), "key_press_event",
1731 G_CALLBACK(imhtml_remove_focus), status_box);
1732 g_signal_connect_swapped(G_OBJECT(status_box->imhtml), "message_send", G_CALLBACK(remove_typing_cb), status_box);
1733 gtk_imhtml_set_editable(GTK_IMHTML(status_box->imhtml), TRUE);
1734 #ifdef USE_GTKSPELL
1735 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck"))
1736 pidgin_setup_gtkspell(GTK_TEXT_VIEW(status_box->imhtml));
1737 #endif
1738 gtk_widget_set_parent(status_box->vbox, GTK_WIDGET(status_box));
1739 gtk_widget_show_all(status_box->vbox);
1740
1741 gtk_widget_set_parent(status_box->toggle_button, GTK_WIDGET(status_box));
1742
1743 gtk_box_pack_start(GTK_BOX(status_box->vbox), status_box->sw, TRUE, TRUE, 0);
1744
1745 g_signal_connect(G_OBJECT(status_box), "scroll_event", G_CALLBACK(combo_box_scroll_event_cb), NULL);
1746 g_signal_connect(G_OBJECT(status_box->imhtml), "scroll_event",
1747 G_CALLBACK(imhtml_scroll_event_cb), status_box->imhtml);
1748 g_signal_connect(G_OBJECT(status_box->popup_window), "button_release_event", G_CALLBACK(treeview_button_release_cb), status_box);
1749 g_signal_connect(G_OBJECT(status_box->popup_window), "key_press_event", G_CALLBACK(treeview_key_press_event), status_box);
1750
1751 #if GTK_CHECK_VERSION(2,6,0)
1752 gtk_tree_view_set_row_separator_func(GTK_TREE_VIEW(status_box->tree_view), dropdown_store_row_separator_func, NULL, NULL);
1753 #endif
1754
1755 status_box->token_status_account = check_active_accounts_for_identical_statuses();
1756
1757 cache_pixbufs(status_box);
1758 pidgin_status_box_regenerate(status_box);
1759
1760 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed",
1761 status_box,
1762 PURPLE_CALLBACK(current_savedstatus_changed_cb),
1763 status_box);
1764 purple_signal_connect(purple_accounts_get_handle(), "account-enabled", status_box,
1765 PURPLE_CALLBACK(account_enabled_cb),
1766 status_box);
1767 purple_signal_connect(purple_accounts_get_handle(), "account-disabled", status_box,
1768 PURPLE_CALLBACK(account_enabled_cb),
1769 status_box);
1770 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed", status_box,
1771 PURPLE_CALLBACK(account_status_changed_cb),
1772 status_box);
1773
1774 purple_prefs_connect_callback(status_box, PIDGIN_PREFS_ROOT "/conversations/spellcheck",
1775 spellcheck_prefs_cb, status_box);
1776 purple_prefs_connect_callback(status_box, PIDGIN_PREFS_ROOT "/accounts/buddyicon",
1777 update_buddyicon_cb, status_box);
1778 purple_signal_connect(purple_get_core(), "uri-handler", status_box,
1779 PURPLE_CALLBACK(statusbox_uri_handler), status_box);
1780
1781 }
1782
1783 static void
1784 pidgin_status_box_size_request(GtkWidget *widget,
1785 GtkRequisition *requisition)
1786 {
1787 GtkRequisition box_req;
1788 gint border_width = GTK_CONTAINER (widget)->border_width;
1789
1790 gtk_widget_size_request(PIDGIN_STATUS_BOX(widget)->toggle_button, requisition);
1791
1792 /* Make this icon the same size as other buddy icons in the list; unless it already wants to be bigger */
1793 requisition->height = MAX(requisition->height, 34);
1794 requisition->height += border_width * 2;
1795
1796 /* If the gtkimhtml is visible, then add some additional padding */
1797 gtk_widget_size_request(PIDGIN_STATUS_BOX(widget)->vbox, &box_req);
1798 if (box_req.height > 1)
1799 requisition->height += box_req.height + border_width * 2;
1800
1801 requisition->width = 1;
1802 }
1803
1804 /* From gnome-panel */
1805 static void
1806 do_colorshift (GdkPixbuf *dest, GdkPixbuf *src, int shift)
1807 {
1808 gint i, j;
1809 gint width, height, has_alpha, srcrowstride, destrowstride;
1810 guchar *target_pixels;
1811 guchar *original_pixels;
1812 guchar *pixsrc;
1813 guchar *pixdest;
1814 int val;
1815 guchar r,g,b;
1816
1817 has_alpha = gdk_pixbuf_get_has_alpha (src);
1818 width = gdk_pixbuf_get_width (src);
1819 height = gdk_pixbuf_get_height (src);
1820 srcrowstride = gdk_pixbuf_get_rowstride (src);
1821 destrowstride = gdk_pixbuf_get_rowstride (dest);
1822 target_pixels = gdk_pixbuf_get_pixels (dest);
1823 original_pixels = gdk_pixbuf_get_pixels (src);
1824
1825 for (i = 0; i < height; i++) {
1826 pixdest = target_pixels + i*destrowstride;
1827 pixsrc = original_pixels + i*srcrowstride;
1828 for (j = 0; j < width; j++) {
1829 r = *(pixsrc++);
1830 g = *(pixsrc++);
1831 b = *(pixsrc++);
1832 val = r + shift;
1833 *(pixdest++) = CLAMP(val, 0, 255);
1834 val = g + shift;
1835 *(pixdest++) = CLAMP(val, 0, 255);
1836 val = b + shift;
1837 *(pixdest++) = CLAMP(val, 0, 255);
1838 if (has_alpha)
1839 *(pixdest++) = *(pixsrc++);
1840 }
1841 }
1842 }
1843
1844 static void
1845 pidgin_status_box_size_allocate(GtkWidget *widget,
1846 GtkAllocation *allocation)
1847 {
1848 PidginStatusBox *status_box = PIDGIN_STATUS_BOX(widget);
1849 GtkRequisition req = {0,0};
1850 GtkAllocation parent_alc, box_alc, icon_alc;
1851 gint border_width = GTK_CONTAINER (widget)->border_width;
1852
1853 gtk_widget_size_request(status_box->toggle_button, &req);
1854 /* Make this icon the same size as other buddy icons in the list; unless it already wants to be bigger */
1855
1856 req.height = MAX(req.height, 34);
1857 req.height += border_width * 2;
1858
1859 box_alc = *allocation;
1860
1861 box_alc.width -= (border_width * 2);
1862 box_alc.height = MAX(1, ((allocation->height - req.height) - (border_width*2)));
1863 box_alc.x += border_width;
1864 box_alc.y += req.height + border_width;
1865 gtk_widget_size_allocate(status_box->vbox, &box_alc);
1866
1867 parent_alc = *allocation;
1868 parent_alc.height = MAX(1,req.height - (border_width *2));
1869 parent_alc.width -= (border_width * 2);
1870 parent_alc.x += border_width;
1871 parent_alc.y += border_width;
1872
1873 if (status_box->icon_box)
1874 {
1875 GtkTextDirection dir = gtk_widget_get_direction(widget);
1876 parent_alc.width -= (parent_alc.height + border_width);
1877 icon_alc = parent_alc;
1878 icon_alc.height = MAX(1, icon_alc.height) - 2;
1879 icon_alc.width = icon_alc.height;
1880 if (dir == GTK_TEXT_DIR_RTL) {
1881 icon_alc.x = parent_alc.x;
1882 parent_alc.x += icon_alc.width + border_width;
1883 } else {
1884 icon_alc.x = allocation->width - (icon_alc.width + border_width + 1);
1885 }
1886 icon_alc.y += 1;
1887
1888 if (status_box->icon_size != icon_alc.height)
1889 {
1890 status_box->icon_size = icon_alc.height;
1891 pidgin_status_box_redisplay_buddy_icon(status_box);
1892 }
1893 gtk_widget_size_allocate(status_box->icon_box, &icon_alc);
1894 }
1895 gtk_widget_size_allocate(status_box->toggle_button, &parent_alc);
1896 widget->allocation = *allocation;
1897 }
1898
1899 static gboolean
1900 pidgin_status_box_expose_event(GtkWidget *widget,
1901 GdkEventExpose *event)
1902 {
1903 PidginStatusBox *status_box = PIDGIN_STATUS_BOX(widget);
1904 gtk_container_propagate_expose(GTK_CONTAINER(widget), status_box->vbox, event);
1905 gtk_container_propagate_expose(GTK_CONTAINER(widget), status_box->toggle_button, event);
1906 if (status_box->icon_box && status_box->icon_opaque) {
1907 gtk_paint_box(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL,
1908 status_box->icon_box, "button", status_box->icon_box->allocation.x-1, status_box->icon_box->allocation.y-1,
1909 34, 34);
1910 }
1911 return FALSE;
1912 }
1913
1914 static void
1915 pidgin_status_box_forall(GtkContainer *container,
1916 gboolean include_internals,
1917 GtkCallback callback,
1918 gpointer callback_data)
1919 {
1920 PidginStatusBox *status_box = PIDGIN_STATUS_BOX (container);
1921
1922 if (include_internals)
1923 {
1924 (* callback) (status_box->vbox, callback_data);
1925 (* callback) (status_box->toggle_button, callback_data);
1926 (* callback) (status_box->arrow, callback_data);
1927 if (status_box->icon_box)
1928 (* callback) (status_box->icon_box, callback_data);
1929 }
1930 }
1931
1932 GtkWidget *
1933 pidgin_status_box_new()
1934 {
1935 return g_object_new(PIDGIN_TYPE_STATUS_BOX, "account", NULL,
1936 "iconsel", TRUE, NULL);
1937 }
1938
1939 GtkWidget *
1940 pidgin_status_box_new_with_account(PurpleAccount *account)
1941 {
1942 return g_object_new(PIDGIN_TYPE_STATUS_BOX, "account", account,
1943 "iconsel", TRUE, NULL);
1944 }
1945
1946 /**
1947 * Add a row to the dropdown menu.
1948 *
1949 * @param status_box The status box itself.
1950 * @param type A PidginStatusBoxItemType.
1951 * @param pixbuf The icon to associate with this row in the menu.
1952 * @param title The title of this item. For the primitive entries,
1953 * this is something like "Available" or "Away." For
1954 * the saved statuses, this is something like
1955 * "My favorite away message!" This should be
1956 * plaintext (non-markedup) (this function escapes it).
1957 * @param desc The secondary text for this item. This will be
1958 * placed on the row below the title, in a dimmer
1959 * font (generally gray). This text should be plaintext
1960 * (non-markedup) (this function escapes it).
1961 * @param data Data to be associated with this row in the dropdown
1962 * menu. For primitives this is the value of the
1963 * PurpleStatusPrimitive. For saved statuses this is the
1964 * creation timestamp.
1965 */
1966 void
1967 pidgin_status_box_add(PidginStatusBox *status_box, PidginStatusBoxItemType type, GdkPixbuf *pixbuf, const char *title, const char *desc, gpointer data)
1968 {
1969 GtkTreeIter iter;
1970 char *text;
1971
1972 if (desc == NULL)
1973 {
1974 text = g_markup_escape_text(title, -1);
1975 }
1976 else
1977 {
1978 GtkStyle *style;
1979 char aa_color[8];
1980 gchar *escaped_title, *escaped_desc;
1981
1982 style = gtk_widget_get_style(GTK_WIDGET(status_box));
1983 snprintf(aa_color, sizeof(aa_color), "#%02x%02x%02x",
1984 style->text_aa[GTK_STATE_NORMAL].red >> 8,
1985 style->text_aa[GTK_STATE_NORMAL].green >> 8,
1986 style->text_aa[GTK_STATE_NORMAL].blue >> 8);
1987
1988 escaped_title = g_markup_escape_text(title, -1);
1989 escaped_desc = g_markup_escape_text(desc, -1);
1990 text = g_strdup_printf("%s - <span color=\"%s\" size=\"smaller\">%s</span>",
1991 escaped_title,
1992 aa_color, escaped_desc);
1993 g_free(escaped_title);
1994 g_free(escaped_desc);
1995 }
1996
1997 gtk_list_store_append(status_box->dropdown_store, &iter);
1998 gtk_list_store_set(status_box->dropdown_store, &iter,
1999 TYPE_COLUMN, type,
2000 ICON_COLUMN, pixbuf,
2001 TEXT_COLUMN, text,
2002 TITLE_COLUMN, title,
2003 DESC_COLUMN, desc,
2004 DATA_COLUMN, data,
2005 -1);
2006 g_free(text);
2007 }
2008
2009 void
2010 pidgin_status_box_add_separator(PidginStatusBox *status_box)
2011 {
2012 /* Don't do anything unless GTK actually supports
2013 * gtk_combo_box_set_row_separator_func */
2014 #if GTK_CHECK_VERSION(2,6,0)
2015 GtkTreeIter iter;
2016
2017 gtk_list_store_append(status_box->dropdown_store, &iter);
2018 gtk_list_store_set(status_box->dropdown_store, &iter,
2019 TYPE_COLUMN, PIDGIN_STATUS_BOX_TYPE_SEPARATOR,
2020 -1);
2021 #endif
2022 }
2023
2024 void
2025 pidgin_status_box_set_network_available(PidginStatusBox *status_box, gboolean available)
2026 {
2027 if (!status_box)
2028 return;
2029 status_box->network_available = available;
2030 pidgin_status_box_refresh(status_box);
2031 }
2032
2033 void
2034 pidgin_status_box_set_connecting(PidginStatusBox *status_box, gboolean connecting)
2035 {
2036 if (!status_box)
2037 return;
2038 status_box->connecting = connecting;
2039 pidgin_status_box_refresh(status_box);
2040 }
2041
2042 static void
2043 pidgin_status_box_redisplay_buddy_icon(PidginStatusBox *status_box)
2044 {
2045
2046 /* This is sometimes called before the box is shown, and we will not have a size */
2047 if (status_box->icon_size <= 0)
2048 return;
2049
2050 if (status_box->buddy_icon)
2051 g_object_unref(status_box->buddy_icon);
2052 if (status_box->buddy_icon_hover)
2053 g_object_unref(status_box->buddy_icon_hover);
2054 status_box->buddy_icon = NULL;
2055 status_box->buddy_icon_hover = NULL;
2056
2057 if ((status_box->buddy_icon_path != NULL) &&
2058 (*status_box->buddy_icon_path != '\0'))
2059 status_box->buddy_icon = gdk_pixbuf_new_from_file_at_scale(status_box->buddy_icon_path,
2060 status_box->icon_size, status_box->icon_size, FALSE, NULL);
2061
2062 if (status_box->buddy_icon == NULL)
2063 {
2064 /* Show a placeholder icon */
2065 gchar *filename;
2066 filename = g_build_filename(DATADIR, "pixmaps",
2067 "pidgin", "insert-image.png", NULL);
2068 status_box->buddy_icon = gdk_pixbuf_new_from_file(filename, NULL);
2069 g_free(filename);
2070 }
2071
2072 if (status_box->buddy_icon != NULL) {
2073 status_box->icon_opaque = pidgin_gdk_pixbuf_is_opaque(status_box->buddy_icon);
2074 gtk_image_set_from_pixbuf(GTK_IMAGE(status_box->icon), status_box->buddy_icon);
2075 status_box->buddy_icon_hover = gdk_pixbuf_copy(status_box->buddy_icon);
2076 do_colorshift(status_box->buddy_icon_hover, status_box->buddy_icon_hover, 32);
2077 gtk_widget_queue_resize(GTK_WIDGET(status_box));
2078 }
2079 }
2080
2081 void
2082 pidgin_status_box_set_buddy_icon(PidginStatusBox *status_box, const char *filename)
2083 {
2084 g_free(status_box->buddy_icon_path);
2085 status_box->buddy_icon_path = g_strdup(filename);
2086
2087 pidgin_status_box_redisplay_buddy_icon(status_box);
2088 }
2089
2090 const char*
2091 pidgin_status_box_get_buddy_icon(PidginStatusBox *box)
2092 {
2093 return box->buddy_icon_path;
2094 }
2095
2096 void
2097 pidgin_status_box_pulse_connecting(PidginStatusBox *status_box)
2098 {
2099 if (!status_box)
2100 return;
2101 if (status_box->connecting_index == 8)
2102 status_box->connecting_index = 0;
2103 else
2104 status_box->connecting_index++;
2105 pidgin_status_box_refresh(status_box);
2106 }
2107
2108 static void
2109 pidgin_status_box_pulse_typing(PidginStatusBox *status_box)
2110 {
2111 if (status_box->typing_index == 4)
2112 status_box->typing_index = 0;
2113 else
2114 status_box->typing_index++;
2115 pidgin_status_box_refresh(status_box);
2116 }
2117
2118 static gboolean
2119 message_changed(const char *one, const char *two)
2120 {
2121 if (one == NULL && two == NULL)
2122 return FALSE;
2123
2124 if (one == NULL || two == NULL)
2125 return TRUE;
2126
2127 return (g_utf8_collate(one, two) != 0);
2128 }
2129
2130 static void
2131 activate_currently_selected_status(PidginStatusBox *status_box)
2132 {
2133 PidginStatusBoxItemType type;
2134 gpointer data;
2135 gchar *title;
2136 GtkTreeIter iter;
2137 GtkTreePath *path;
2138 char *message;
2139 PurpleSavedStatus *saved_status = NULL;
2140 gboolean changed = TRUE;
2141
2142 path = gtk_tree_row_reference_get_path(status_box->active_row);
2143 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
2144 return;
2145 gtk_tree_path_free(path);
2146
2147 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
2148 TYPE_COLUMN, &type,
2149 DATA_COLUMN, &data,
2150 -1);
2151
2152 /*
2153 * If the currently selected status is "New..." or
2154 * "Saved..." or a popular status then do nothing.
2155 * Popular statuses are
2156 * activated elsewhere, and we update the status_box
2157 * accordingly by connecting to the savedstatus-changed
2158 * signal and then calling status_menu_refresh_iter()
2159 */
2160 if (type != PIDGIN_STATUS_BOX_TYPE_PRIMITIVE)
2161 return;
2162
2163 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
2164 TITLE_COLUMN, &title, -1);
2165
2166 message = pidgin_status_box_get_message(status_box);
2167 if (!message || !*message)
2168 {
2169 gtk_widget_hide_all(status_box->vbox);
2170 status_box->imhtml_visible = FALSE;
2171 if (message != NULL)
2172 {
2173 g_free(message);
2174 message = NULL;
2175 }
2176 }
2177
2178 if (status_box->account == NULL) {
2179 PurpleStatusType *acct_status_type = NULL;
2180 PurpleStatusPrimitive primitive = GPOINTER_TO_INT(data);
2181 /* Global */
2182 /* Save the newly selected status to prefs.xml and status.xml */
2183
2184 /* Has the status really been changed? */
2185 if (status_box->token_status_account) {
2186 gint active;
2187 PurpleStatus *status;
2188 const char *id = NULL;
2189 GtkTreePath *path = gtk_tree_row_reference_get_path(status_box->active_row);
2190 active = gtk_tree_path_get_indices(path)[0];
2191
2192 gtk_tree_path_free(path);
2193
2194 status = purple_account_get_active_status(status_box->token_status_account);
2195
2196 acct_status_type = find_status_type_by_index(status_box->token_status_account, active);
2197 id = purple_status_type_get_id(acct_status_type);
2198
2199 if (strncmp(id, purple_status_get_id(status), strlen(id)) == 0)
2200 {
2201 /* Selected status and previous status is the same */
2202 if (!message_changed(message, purple_status_get_attr_string(status, "message")))
2203 {
2204 PurpleSavedStatus *ss = purple_savedstatus_get_current();
2205 /* Make sure that statusbox displays the correct thing.
2206 * It can get messed up if the previous selection was a
2207 * saved status that wasn't supported by this account */
2208 if ((purple_savedstatus_get_type(ss) == primitive)
2209 && purple_savedstatus_is_transient(ss)
2210 && purple_savedstatus_has_substatuses(ss))
2211 changed = FALSE;
2212 }
2213 }
2214 } else {
2215 saved_status = purple_savedstatus_get_current();
2216 if (purple_savedstatus_get_type(saved_status) == primitive &&
2217 !purple_savedstatus_has_substatuses(saved_status))
2218 {
2219 if (!message_changed(purple_savedstatus_get_message(saved_status), message))
2220 changed = FALSE;
2221 }
2222 }
2223
2224 if (changed)
2225 {
2226 /* Manually find the appropriate transient acct */
2227 if (status_box->token_status_account) {
2228 const GList *iter = purple_savedstatuses_get_all();
2229 GList *tmp, *active_accts = purple_accounts_get_all_active();
2230
2231 for (; iter != NULL; iter = iter->next) {
2232 PurpleSavedStatus *ss = iter->data;
2233 const char *ss_msg = purple_savedstatus_get_message(ss);
2234 if ((purple_savedstatus_get_type(ss) == primitive) && purple_savedstatus_is_transient(ss) &&
2235 purple_savedstatus_has_substatuses(ss) && /* Must have substatuses */
2236 !message_changed(ss_msg, message))
2237 {
2238 gboolean found = FALSE;
2239 /* The currently enabled accounts must have substatuses for all the active accts */
2240 for(tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2241 PurpleAccount *acct = tmp->data;
2242 PurpleSavedStatusSub *sub = purple_savedstatus_get_substatus(ss, acct);
2243 if (sub) {
2244 const PurpleStatusType *sub_type = purple_savedstatus_substatus_get_type(sub);
2245 if (!strcmp(purple_status_type_get_id(sub_type),
2246 purple_status_type_get_id(acct_status_type)))
2247 found = TRUE;
2248 }
2249 }
2250 if (!found)
2251 continue;
2252 saved_status = ss;
2253 break;
2254 }
2255 }
2256
2257 g_list_free(active_accts);
2258
2259 } else {
2260 /* If we've used this type+message before, lookup the transient status */
2261 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2262 }
2263
2264 /* If this type+message is unique then create a new transient saved status */
2265 if (saved_status == NULL)
2266 {
2267 saved_status = purple_savedstatus_new(NULL, primitive);
2268 purple_savedstatus_set_message(saved_status, message);
2269 if (status_box->token_status_account) {
2270 GList *tmp, *active_accts = purple_accounts_get_all_active();
2271 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2272 purple_savedstatus_set_substatus(saved_status,
2273 (PurpleAccount*) tmp->data, acct_status_type, message);
2274 }
2275 g_list_free(active_accts);
2276 }
2277 }
2278
2279 /* Set the status for each account */
2280 purple_savedstatus_activate(saved_status);
2281 }
2282 } else {
2283 /* Per-account */
2284 gint active;
2285 PurpleStatusType *status_type;
2286 PurpleStatus *status;
2287 const char *id = NULL;
2288
2289 status = purple_account_get_active_status(status_box->account);
2290
2291 active = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(status_box), "active"));
2292
2293 status_type = find_status_type_by_index(status_box->account, active);
2294 id = purple_status_type_get_id(status_type);
2295
2296 if (strncmp(id, purple_status_get_id(status), strlen(id)) == 0)
2297 {
2298 /* Selected status and previous status is the same */
2299 if (!message_changed(message, purple_status_get_attr_string(status, "message")))
2300 changed = FALSE;
2301 }
2302
2303 if (changed)
2304 {
2305 if (message)
2306 purple_account_set_status(status_box->account, id,
2307 TRUE, "message", message, NULL);
2308 else
2309 purple_account_set_status(status_box->account, id,
2310 TRUE, NULL);
2311
2312 saved_status = purple_savedstatus_get_current();
2313 if (purple_savedstatus_is_transient(saved_status))
2314 purple_savedstatus_set_substatus(saved_status, status_box->account,
2315 status_type, message);
2316 }
2317 }
2318
2319 g_free(title);
2320 g_free(message);
2321 }
2322
2323 static void update_size(PidginStatusBox *status_box)
2324 {
2325 GtkTextBuffer *buffer;
2326 GtkTextIter iter;
2327 int wrapped_lines;
2328 int lines;
2329 GdkRectangle oneline;
2330 int height;
2331 int pad_top, pad_inside, pad_bottom;
2332
2333 if (!status_box->imhtml_visible)
2334 {
2335 if (status_box->vbox != NULL)
2336 gtk_widget_set_size_request(status_box->vbox, -1, -1);
2337 return;
2338 }
2339
2340 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(status_box->imhtml));
2341
2342 wrapped_lines = 1;
2343 gtk_text_buffer_get_start_iter(buffer, &iter);
2344 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(status_box->imhtml), &iter, &oneline);
2345 while (gtk_text_view_forward_display_line(GTK_TEXT_VIEW(status_box->imhtml), &iter))
2346 wrapped_lines++;
2347
2348 lines = gtk_text_buffer_get_line_count(buffer);
2349
2350 /* Show a maximum of 4 lines */
2351 lines = MIN(lines, 4);
2352 wrapped_lines = MIN(wrapped_lines, 4);
2353
2354 pad_top = gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(status_box->imhtml));
2355 pad_bottom = gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(status_box->imhtml));
2356 pad_inside = gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(status_box->imhtml));
2357
2358 height = (oneline.height + pad_top + pad_bottom) * lines;
2359 height += (oneline.height + pad_inside) * (wrapped_lines - lines);
2360
2361 gtk_widget_set_size_request(status_box->vbox, -1, height + PIDGIN_HIG_BOX_SPACE);
2362 }
2363
2364 static void remove_typing_cb(PidginStatusBox *status_box)
2365 {
2366 if (status_box->typing == 0)
2367 {
2368 /* Nothing has changed, so we don't need to do anything */
2369 status_menu_refresh_iter(status_box);
2370 return;
2371 }
2372
2373 g_source_remove(status_box->typing);
2374 status_box->typing = 0;
2375
2376 activate_currently_selected_status(status_box);
2377 pidgin_status_box_refresh(status_box);
2378 }
2379
2380 static void pidgin_status_box_changed(PidginStatusBox *status_box)
2381 {
2382 GtkTreePath *path = gtk_tree_row_reference_get_path(status_box->active_row);
2383 GtkTreeIter iter;
2384 PidginStatusBoxItemType type;
2385 gpointer data;
2386 GList *accounts = NULL, *node;
2387 int active;
2388
2389
2390 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box->dropdown_store), &iter, path))
2391 return;
2392 active = gtk_tree_path_get_indices(path)[0];
2393 gtk_tree_path_free(path);
2394 g_object_set_data(G_OBJECT(status_box), "active", GINT_TO_POINTER(active));
2395
2396 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
2397 TYPE_COLUMN, &type,
2398 DATA_COLUMN, &data,
2399 -1);
2400 if (status_box->typing != 0)
2401 g_source_remove(status_box->typing);
2402 status_box->typing = 0;
2403
2404 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box)))
2405 {
2406 if (type == PIDGIN_STATUS_BOX_TYPE_POPULAR)
2407 {
2408 PurpleSavedStatus *saved;
2409 saved = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
2410 g_return_if_fail(saved != NULL);
2411 purple_savedstatus_activate(saved);
2412 return;
2413 }
2414
2415 if (type == PIDGIN_STATUS_BOX_TYPE_CUSTOM)
2416 {
2417 PurpleSavedStatus *saved_status;
2418 saved_status = purple_savedstatus_get_current();
2419 if (purple_savedstatus_get_type(saved_status) == PURPLE_STATUS_AVAILABLE)
2420 saved_status = purple_savedstatus_new(NULL, PURPLE_STATUS_AWAY);
2421 pidgin_status_editor_show(FALSE,
2422 purple_savedstatus_is_transient(saved_status)
2423 ? saved_status : NULL);
2424 status_menu_refresh_iter(status_box);
2425 return;
2426 }
2427
2428 if (type == PIDGIN_STATUS_BOX_TYPE_SAVED)
2429 {
2430 pidgin_status_window_show();
2431 status_menu_refresh_iter(status_box);
2432 return;
2433 }
2434 }
2435
2436 /*
2437 * Show the message box whenever the primitive allows for a
2438 * message attribute on any protocol that is enabled,
2439 * or our protocol, if we have account set
2440 */
2441 if (status_box->account)
2442 accounts = g_list_prepend(accounts, status_box->account);
2443 else
2444 accounts = purple_accounts_get_all_active();
2445 status_box->imhtml_visible = FALSE;
2446 for (node = accounts; node != NULL; node = node->next)
2447 {
2448 PurpleAccount *account;
2449 PurpleStatusType *status_type;
2450
2451 account = node->data;
2452 status_type = purple_account_get_status_type_with_primitive(account, GPOINTER_TO_INT(data));
2453 if ((status_type != NULL) &&
2454 (purple_status_type_get_attr(status_type, "message") != NULL))
2455 {
2456 status_box->imhtml_visible = TRUE;
2457 break;
2458 }
2459 }
2460 g_list_free(accounts);
2461
2462 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box)))
2463 {
2464 if (status_box->imhtml_visible)
2465 {
2466 gtk_widget_show_all(status_box->vbox);
2467 status_box->typing = g_timeout_add(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
2468 gtk_widget_grab_focus(status_box->imhtml);
2469 gtk_imhtml_clear(GTK_IMHTML(status_box->imhtml));
2470 }
2471 else
2472 {
2473 gtk_widget_hide_all(status_box->vbox);
2474 activate_currently_selected_status(status_box); /* This is where we actually set the status */
2475 return;
2476 }
2477 }
2478 pidgin_status_box_refresh(status_box);
2479 }
2480
2481 static gint
2482 get_statusbox_index(PidginStatusBox *box, PurpleSavedStatus *saved_status)
2483 {
2484 gint index;
2485
2486 switch (purple_savedstatus_get_type(saved_status))
2487 {
2488 case PURPLE_STATUS_AVAILABLE:
2489 index = 0;
2490 break;
2491 case PURPLE_STATUS_AWAY:
2492 index = 1;
2493 break;
2494 case PURPLE_STATUS_INVISIBLE:
2495 index = 2;
2496 break;
2497 case PURPLE_STATUS_OFFLINE:
2498 index = 3;
2499 break;
2500 default:
2501 index = -1;
2502 break;
2503 }
2504
2505 return index;
2506 }
2507
2508 static void imhtml_changed_cb(GtkTextBuffer *buffer, void *data)
2509 {
2510 PidginStatusBox *status_box = (PidginStatusBox*)data;
2511 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box)))
2512 {
2513 if (status_box->typing != 0) {
2514 pidgin_status_box_pulse_typing(status_box);
2515 g_source_remove(status_box->typing);
2516 }
2517 status_box->typing = g_timeout_add(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
2518 }
2519 pidgin_status_box_refresh(status_box);
2520 }
2521
2522 static void imhtml_format_changed_cb(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons, void *data)
2523 {
2524 imhtml_changed_cb(NULL, data);
2525 }
2526
2527 char *pidgin_status_box_get_message(PidginStatusBox *status_box)
2528 {
2529 if (status_box->imhtml_visible)
2530 return gtk_imhtml_get_markup(GTK_IMHTML(status_box->imhtml));
2531 else
2532 return NULL;
2533 }

mercurial