comparison pidgin/gtkstatusbox.c @ 15374:5fe8042783c1

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