comparison gtk/gtkstatusbox.c @ 14191:009db0b357b5

This is a hand-crafted commit to migrate across subversion revisions 16854:16861, due to some vagaries of the way the original renames were done. Witness that monotone can do in one revision what svn had to spread across several.
author Ethan Blanton <elb@pidgin.im>
date Sat, 16 Dec 2006 04:59:55 +0000
parents
children 360c016459d0
comparison
equal deleted inserted replaced
14190:366be2ce35a7 14191:009db0b357b5
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 "savedstatuses.h"
47 #include "status.h"
48 #include "debug.h"
49
50 #include "gtkgaim.h"
51 #include "gtksavedstatuses.h"
52 #include "gaimstock.h"
53 #include "gtkstatusbox.h"
54 #include "gtkutils.h"
55
56 #ifdef USE_GTKSPELL
57 # include <gtkspell/gtkspell.h>
58 # ifdef _WIN32
59 # include "wspell.h"
60 # endif
61 #endif
62
63 #define TYPING_TIMEOUT 4000
64
65 static void imhtml_changed_cb(GtkTextBuffer *buffer, void *data);
66 static void imhtml_format_changed_cb(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons, void *data);
67 static void remove_typing_cb(GtkGaimStatusBox *box);
68 static void update_size (GtkGaimStatusBox *box);
69 static gint get_statusbox_index(GtkGaimStatusBox *box, GaimSavedStatus *saved_status);
70
71 static void gtk_gaim_status_box_pulse_typing(GtkGaimStatusBox *status_box);
72 static void gtk_gaim_status_box_refresh(GtkGaimStatusBox *status_box);
73 static void gtk_gaim_status_box_regenerate(GtkGaimStatusBox *status_box);
74 static void gtk_gaim_status_box_changed(GtkComboBox *box);
75 static void gtk_gaim_status_box_size_request (GtkWidget *widget, GtkRequisition *requisition);
76 static void gtk_gaim_status_box_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
77 static gboolean gtk_gaim_status_box_expose_event (GtkWidget *widget, GdkEventExpose *event);
78 static void gtk_gaim_status_box_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data);
79
80 static void do_colorshift (GdkPixbuf *dest, GdkPixbuf *src, int shift);
81 static void icon_choose_cb(const char *filename, gpointer data);
82
83 static void (*combo_box_size_request)(GtkWidget *widget, GtkRequisition *requisition);
84 static void (*combo_box_size_allocate)(GtkWidget *widget, GtkAllocation *allocation);
85 static void (*combo_box_forall) (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data);
86
87 enum {
88 /** A GtkGaimStatusBoxItemType */
89 TYPE_COLUMN,
90
91 /**
92 * This is a GdkPixbuf (the other columns are strings).
93 * This column is visible.
94 */
95 ICON_COLUMN,
96
97 /** The text displayed on the status box. This column is visible. */
98 TEXT_COLUMN,
99
100 /** The plain-English title of this item */
101 TITLE_COLUMN,
102
103 /** A plain-English description of this item */
104 DESC_COLUMN,
105
106 /*
107 * This value depends on TYPE_COLUMN. For POPULAR types,
108 * this is the creation time. For PRIMITIVE types,
109 * this is the GaimStatusPrimitive.
110 */
111 DATA_COLUMN,
112
113 NUM_COLUMNS
114 };
115
116 enum {
117 PROP_0,
118 PROP_ACCOUNT,
119 PROP_ICON_SEL,
120 };
121
122 GtkComboBoxClass *parent_class = NULL;
123
124 static void gtk_gaim_status_box_class_init (GtkGaimStatusBoxClass *klass);
125 static void gtk_gaim_status_box_init (GtkGaimStatusBox *status_box);
126
127 GType
128 gtk_gaim_status_box_get_type (void)
129 {
130 static GType status_box_type = 0;
131
132 if (!status_box_type)
133 {
134 static const GTypeInfo status_box_info =
135 {
136 sizeof (GtkGaimStatusBoxClass),
137 NULL, /* base_init */
138 NULL, /* base_finalize */
139 (GClassInitFunc) gtk_gaim_status_box_class_init,
140 NULL, /* class_finalize */
141 NULL, /* class_data */
142 sizeof (GtkGaimStatusBox),
143 0,
144 (GInstanceInitFunc) gtk_gaim_status_box_init,
145 NULL /* value_table */
146 };
147
148 status_box_type = g_type_register_static(GTK_TYPE_COMBO_BOX,
149 "GtkGaimStatusBox",
150 &status_box_info,
151 0);
152 }
153
154 return status_box_type;
155 }
156
157 static void
158 gtk_gaim_status_box_get_property(GObject *object, guint param_id,
159 GValue *value, GParamSpec *psec)
160 {
161 GtkGaimStatusBox *statusbox = GTK_GAIM_STATUS_BOX(object);
162
163 switch (param_id) {
164 case PROP_ACCOUNT:
165 g_value_set_pointer(value, statusbox->account);
166 break;
167 case PROP_ICON_SEL:
168 g_value_set_boolean(value, statusbox->icon_box != NULL);
169 break;
170 default:
171 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, psec);
172 break;
173 }
174 }
175
176 static void
177 update_to_reflect_account_status(GtkGaimStatusBox *status_box, GaimAccount *account, GaimStatus *newstatus)
178 {
179 const GList *l;
180 int status_no = -1;
181 const GaimStatusType *statustype = NULL;
182 const char *message;
183
184 statustype = gaim_status_type_find_with_id((GList *)gaim_account_get_status_types(account),
185 (char *)gaim_status_type_get_id(gaim_status_get_type(newstatus)));
186
187 for (l = gaim_account_get_status_types(account); l != NULL; l = l->next) {
188 GaimStatusType *status_type = (GaimStatusType *)l->data;
189
190 if (!gaim_status_type_is_user_settable(status_type))
191 continue;
192 status_no++;
193 if (statustype == status_type)
194 break;
195 }
196
197 if (status_no != -1) {
198 gtk_widget_set_sensitive(GTK_WIDGET(status_box), FALSE);
199 gtk_combo_box_set_active(GTK_COMBO_BOX(status_box), status_no);
200
201 message = gaim_status_get_attr_string(newstatus, "message");
202
203 if (!message || !*message)
204 {
205 gtk_widget_hide_all(status_box->vbox);
206 status_box->imhtml_visible = FALSE;
207 }
208 else
209 {
210 gtk_widget_show_all(status_box->vbox);
211 status_box->imhtml_visible = TRUE;
212 gtk_imhtml_clear(GTK_IMHTML(status_box->imhtml));
213 gtk_imhtml_clear_formatting(GTK_IMHTML(status_box->imhtml));
214 gtk_imhtml_append_text(GTK_IMHTML(status_box->imhtml), message, 0);
215 }
216 gtk_widget_set_sensitive(GTK_WIDGET(status_box), TRUE);
217 gtk_gaim_status_box_refresh(status_box);
218 }
219 }
220
221 static void
222 account_status_changed_cb(GaimAccount *account, GaimStatus *oldstatus, GaimStatus *newstatus, GtkGaimStatusBox *status_box)
223 {
224 if (status_box->account == account)
225 update_to_reflect_account_status(status_box, account, newstatus);
226 }
227
228 static gboolean
229 icon_box_press_cb(GtkWidget *widget, GdkEventButton *event, GtkGaimStatusBox *box)
230 {
231 if (box->buddy_icon_sel) {
232 gtk_window_present(GTK_WINDOW(box->buddy_icon_sel));
233 return FALSE;
234 }
235
236 box->buddy_icon_sel = gaim_gtk_buddy_icon_chooser_new(NULL, icon_choose_cb, box);
237 gtk_widget_show_all(box->buddy_icon_sel);
238 return FALSE;
239 }
240
241 static gboolean
242 icon_box_enter_cb(GtkWidget *widget, GdkEventCrossing *event, GtkGaimStatusBox *box)
243 {
244 gdk_window_set_cursor(widget->window, box->hand_cursor);
245 gtk_image_set_from_pixbuf(GTK_IMAGE(box->icon), box->buddy_icon_hover);
246 return FALSE;
247 }
248
249 static gboolean
250 icon_box_leave_cb(GtkWidget *widget, GdkEventCrossing *event, GtkGaimStatusBox *box)
251 {
252 gdk_window_set_cursor(widget->window, box->arrow_cursor);
253 gtk_image_set_from_pixbuf(GTK_IMAGE(box->icon), box->buddy_icon) ;
254 return FALSE;
255 }
256
257 static void
258 setup_icon_box(GtkGaimStatusBox *status_box)
259 {
260 if (status_box->icon_box != NULL)
261 return;
262
263 if (status_box->account &&
264 !gaim_account_get_ui_bool(status_box->account, GAIM_GTK_UI, "use-global-buddyicon", TRUE))
265 {
266 char *string = gaim_buddy_icons_get_full_path(gaim_account_get_buddy_icon(status_box->account));
267 gtk_gaim_status_box_set_buddy_icon(status_box, string);
268 g_free(string);
269 }
270 else
271 {
272 gtk_gaim_status_box_set_buddy_icon(status_box, gaim_prefs_get_string("/gaim/gtk/accounts/buddyicon"));
273 }
274 status_box->icon = gtk_image_new_from_pixbuf(status_box->buddy_icon);
275 status_box->icon_box = gtk_event_box_new();
276 status_box->hand_cursor = gdk_cursor_new (GDK_HAND2);
277 status_box->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR);
278
279 g_signal_connect(G_OBJECT(status_box->icon_box), "enter-notify-event", G_CALLBACK(icon_box_enter_cb), status_box);
280 g_signal_connect(G_OBJECT(status_box->icon_box), "leave-notify-event", G_CALLBACK(icon_box_leave_cb), status_box);
281 g_signal_connect(G_OBJECT(status_box->icon_box), "button-press-event", G_CALLBACK(icon_box_press_cb), status_box);
282
283 gtk_container_add(GTK_CONTAINER(status_box->icon_box), status_box->icon);
284
285 gtk_widget_show_all(status_box->icon_box);
286 gtk_widget_set_parent(status_box->icon_box, GTK_WIDGET(status_box));
287 }
288
289 static void
290 destroy_icon_box(GtkGaimStatusBox *statusbox)
291 {
292 if (statusbox->icon_box == NULL)
293 return;
294
295 gtk_widget_destroy(statusbox->icon_box);
296 gdk_cursor_unref(statusbox->hand_cursor);
297 gdk_cursor_unref(statusbox->arrow_cursor);
298
299 g_object_unref(G_OBJECT(statusbox->buddy_icon));
300 g_object_unref(G_OBJECT(statusbox->buddy_icon_hover));
301
302 if (statusbox->buddy_icon_sel)
303 gtk_widget_destroy(statusbox->buddy_icon_sel);
304
305 g_free(statusbox->buddy_icon_path);
306
307 statusbox->icon_box = NULL;
308 statusbox->buddy_icon_path = NULL;
309 statusbox->buddy_icon = NULL;
310 statusbox->buddy_icon_hover = NULL;
311 statusbox->hand_cursor = NULL;
312 statusbox->arrow_cursor = NULL;
313 }
314
315 static void
316 gtk_gaim_status_box_set_property(GObject *object, guint param_id,
317 const GValue *value, GParamSpec *pspec)
318 {
319 GtkGaimStatusBox *statusbox = GTK_GAIM_STATUS_BOX(object);
320
321 switch (param_id) {
322 case PROP_ICON_SEL:
323 if (g_value_get_boolean(value)) {
324 if (statusbox->account) {
325 GaimPlugin *plug = gaim_plugins_find_with_id(gaim_account_get_protocol_id(statusbox->account));
326 if (plug) {
327 GaimPluginProtocolInfo *prplinfo = GAIM_PLUGIN_PROTOCOL_INFO(plug);
328 if (prplinfo && prplinfo->icon_spec.format != NULL)
329 setup_icon_box(statusbox);
330 }
331 } else {
332 setup_icon_box(statusbox);
333 }
334 } else {
335 destroy_icon_box(statusbox);
336 }
337 break;
338 case PROP_ACCOUNT:
339 statusbox->account = g_value_get_pointer(value);
340
341 if (statusbox->status_changed_signal) {
342 gaim_signal_disconnect(gaim_accounts_get_handle(), "account-status-changed",
343 statusbox, GAIM_CALLBACK(account_status_changed_cb));
344 statusbox->status_changed_signal = 0;
345 }
346
347 if (statusbox->account) {
348 statusbox->status_changed_signal = gaim_signal_connect(gaim_accounts_get_handle(), "account-status-changed",
349 statusbox, GAIM_CALLBACK(account_status_changed_cb),
350 statusbox);
351 }
352 gtk_gaim_status_box_regenerate(statusbox);
353
354 break;
355 default:
356 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
357 break;
358 }
359 }
360
361 static void
362 gtk_gaim_status_box_finalize(GObject *obj)
363 {
364 GtkGaimStatusBox *statusbox = GTK_GAIM_STATUS_BOX(obj);
365
366 if (statusbox->status_changed_signal) {
367 gaim_signal_disconnect(gaim_accounts_get_handle(), "account-status-changed",
368 statusbox, GAIM_CALLBACK(account_status_changed_cb));
369 statusbox->status_changed_signal = 0;
370 }
371 gaim_signals_disconnect_by_handle(statusbox);
372 gaim_prefs_disconnect_by_handle(statusbox);
373
374 gdk_cursor_unref(statusbox->hand_cursor);
375 gdk_cursor_unref(statusbox->arrow_cursor);
376
377 g_object_unref(G_OBJECT(statusbox->buddy_icon));
378 g_object_unref(G_OBJECT(statusbox->buddy_icon_hover));
379
380 if (statusbox->buddy_icon_sel)
381 gtk_widget_destroy(statusbox->buddy_icon_sel);
382
383 g_free(statusbox->buddy_icon_path);
384
385 G_OBJECT_CLASS(parent_class)->finalize(obj);
386 }
387
388 static void
389 gtk_gaim_status_box_class_init (GtkGaimStatusBoxClass *klass)
390 {
391 GObjectClass *object_class;
392 GtkComboBoxClass *combo_class;
393 GtkWidgetClass *widget_class;
394 GtkContainerClass *container_class = (GtkContainerClass*)klass;
395
396 parent_class = g_type_class_peek_parent(klass);
397
398 combo_class = (GtkComboBoxClass*)klass;
399 combo_class->changed = gtk_gaim_status_box_changed;
400
401 widget_class = (GtkWidgetClass*)klass;
402 combo_box_size_request = widget_class->size_request;
403 widget_class->size_request = gtk_gaim_status_box_size_request;
404 combo_box_size_allocate = widget_class->size_allocate;
405 widget_class->size_allocate = gtk_gaim_status_box_size_allocate;
406 widget_class->expose_event = gtk_gaim_status_box_expose_event;
407
408 combo_box_forall = container_class->forall;
409 container_class->forall = gtk_gaim_status_box_forall;
410 container_class->remove = NULL;
411
412 object_class = (GObjectClass *)klass;
413
414 object_class->finalize = gtk_gaim_status_box_finalize;
415
416 object_class->get_property = gtk_gaim_status_box_get_property;
417 object_class->set_property = gtk_gaim_status_box_set_property;
418
419 g_object_class_install_property(object_class,
420 PROP_ACCOUNT,
421 g_param_spec_pointer("account",
422 "Account",
423 "The account, or NULL for all accounts",
424 G_PARAM_READWRITE
425 )
426 );
427 g_object_class_install_property(object_class,
428 PROP_ICON_SEL,
429 g_param_spec_boolean("iconsel",
430 "Icon Selector",
431 "Whether the icon selector should be displayed or not.",
432 FALSE,
433 G_PARAM_READWRITE
434 )
435 );
436 }
437
438 /**
439 * This updates the text displayed on the status box so that it shows
440 * the current status. This is the only function in this file that
441 * should modify status_box->store
442 */
443 static void
444 gtk_gaim_status_box_refresh(GtkGaimStatusBox *status_box)
445 {
446 gboolean show_buddy_icons;
447 GtkIconSize icon_size;
448 GtkStyle *style;
449 char aa_color[8];
450 GaimSavedStatus *saved_status;
451 char *primary, *secondary, *text;
452 GdkPixbuf *pixbuf;
453 GtkTreePath *path;
454
455 show_buddy_icons = gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons");
456 if (show_buddy_icons)
457 icon_size = gtk_icon_size_from_name(GAIM_ICON_SIZE_STATUS);
458 else
459 icon_size = gtk_icon_size_from_name(GAIM_ICON_SIZE_STATUS_SMALL);
460
461 style = gtk_widget_get_style(GTK_WIDGET(status_box));
462 snprintf(aa_color, sizeof(aa_color), "#%02x%02x%02x",
463 style->text_aa[GTK_STATE_NORMAL].red >> 8,
464 style->text_aa[GTK_STATE_NORMAL].green >> 8,
465 style->text_aa[GTK_STATE_NORMAL].blue >> 8);
466
467 saved_status = gaim_savedstatus_get_current();
468
469 /* Primary */
470 if (status_box->typing != 0)
471 {
472 GtkTreeIter iter;
473 GtkGaimStatusBoxItemType type;
474 gpointer data;
475
476 /* Primary (get the status selected in the dropdown) */
477 gtk_combo_box_get_active_iter(GTK_COMBO_BOX(status_box), &iter);
478 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
479 TYPE_COLUMN, &type,
480 DATA_COLUMN, &data,
481 -1);
482 if (type == GTK_GAIM_STATUS_BOX_TYPE_PRIMITIVE)
483 primary = g_strdup(gaim_primitive_get_name_from_type(GPOINTER_TO_INT(data)));
484 else
485 /* This should never happen, but just in case... */
486 primary = g_strdup("New status");
487 }
488 else if (status_box->account != NULL)
489 {
490 primary = g_strdup(gaim_status_get_name(gaim_account_get_active_status(status_box->account)));
491 }
492 else if (gaim_savedstatus_is_transient(saved_status))
493 primary = g_strdup(gaim_primitive_get_name_from_type(gaim_savedstatus_get_type(saved_status)));
494 else
495 primary = g_markup_escape_text(gaim_savedstatus_get_title(saved_status), -1);
496
497 /* Secondary */
498 if (status_box->typing != 0)
499 secondary = g_strdup(_("Typing"));
500 else if (status_box->connecting)
501 secondary = g_strdup(_("Connecting"));
502 else if (gaim_savedstatus_is_transient(saved_status))
503 secondary = NULL;
504 else
505 {
506 const char *message;
507 char *tmp;
508 message = gaim_savedstatus_get_message(saved_status);
509 if (message != NULL)
510 {
511 tmp = gaim_markup_strip_html(message);
512 gaim_util_chrreplace(tmp, '\n', ' ');
513 secondary = g_markup_escape_text(tmp, -1);
514 g_free(tmp);
515 }
516 else
517 secondary = NULL;
518 }
519
520 /* Pixbuf */
521 if (status_box->typing != 0)
522 pixbuf = status_box->typing_pixbufs[status_box->typing_index];
523 else if (status_box->connecting)
524 pixbuf = status_box->connecting_pixbufs[status_box->connecting_index];
525 else
526 {
527 if (status_box->account != NULL)
528 pixbuf = gaim_gtk_create_prpl_icon_with_status(status_box->account,
529 gaim_status_get_type(gaim_account_get_active_status(status_box->account)),
530 show_buddy_icons ? 1.0 : 0.5);
531 else
532 pixbuf = gaim_gtk_create_gaim_icon_with_status(
533 gaim_savedstatus_get_type(saved_status),
534 show_buddy_icons ? 1.0 : 0.5);
535
536 if (!gaim_savedstatus_is_transient(saved_status))
537 {
538 GdkPixbuf *emblem;
539
540 /* Overlay a disk in the bottom left corner */
541 emblem = gtk_widget_render_icon(GTK_WIDGET(status_box->vbox),
542 GTK_STOCK_SAVE, icon_size, "GtkGaimStatusBox");
543 if (emblem != NULL)
544 {
545 int width, height;
546 width = gdk_pixbuf_get_width(pixbuf) / 2;
547 height = gdk_pixbuf_get_height(pixbuf) / 2;
548 gdk_pixbuf_composite(emblem, pixbuf, 0, height,
549 width, height, 0, height,
550 0.5, 0.5, GDK_INTERP_BILINEAR, 255);
551 g_object_unref(G_OBJECT(emblem));
552 }
553 }
554 }
555
556 if (status_box->account != NULL) {
557 text = g_strdup_printf("%s%s<span size=\"smaller\" color=\"%s\">%s</span>",
558 gaim_account_get_username(status_box->account),
559 show_buddy_icons ? "\n" : " - ", aa_color,
560 secondary ? secondary : primary);
561 } else if (secondary != NULL) {
562 char *separator;
563 separator = show_buddy_icons ? "\n" : " - ";
564 text = g_strdup_printf("%s<span size=\"smaller\" color=\"%s\">%s%s</span>",
565 primary, aa_color, separator, secondary);
566 } else {
567 text = g_strdup(primary);
568 }
569 g_free(primary);
570 g_free(secondary);
571
572 /*
573 * Only two columns are used in this list store (does it
574 * really need to be a list store?)
575 */
576 gtk_list_store_set(status_box->store, &(status_box->iter),
577 ICON_COLUMN, pixbuf,
578 TEXT_COLUMN, text,
579 -1);
580 if ((status_box->typing == 0) && (!status_box->connecting))
581 g_object_unref(pixbuf);
582 g_free(text);
583
584 /* Make sure to activate the only row in the tree view */
585 path = gtk_tree_path_new_from_string("0");
586 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(status_box->cell_view), path);
587 gtk_tree_path_free(path);
588
589 update_size(status_box);
590 }
591
592 /**
593 * This updates the GtkTreeView so that it correctly shows the state
594 * we are currently using. It is used when the current state is
595 * updated from somewhere other than the GtkStatusBox (from a plugin,
596 * or when signing on with the "-n" option, for example). It is
597 * also used when the user selects the "New..." option.
598 *
599 * Maybe we could accomplish this by triggering off the mouse and
600 * keyboard signals instead of the changed signal?
601 */
602 static void
603 status_menu_refresh_iter(GtkGaimStatusBox *status_box)
604 {
605 GaimSavedStatus *saved_status;
606 GaimStatusPrimitive primitive;
607 gint index;
608 const char *message;
609
610 /* this function is inappropriate for ones with accounts */
611 if (status_box->account)
612 return;
613
614 saved_status = gaim_savedstatus_get_current();
615
616 /*
617 * Suppress the "changed" signal because the status
618 * was changed programmatically.
619 */
620 gtk_widget_set_sensitive(GTK_WIDGET(status_box), FALSE);
621
622 /*
623 * If the saved_status is transient, is Available, Away, Invisible
624 * or Offline, and it does not have an substatuses, then select
625 * the primitive in the dropdown menu. Otherwise select the
626 * popular status in the dropdown menu.
627 */
628 primitive = gaim_savedstatus_get_type(saved_status);
629 if (gaim_savedstatus_is_transient(saved_status) &&
630 ((primitive == GAIM_STATUS_AVAILABLE) || (primitive == GAIM_STATUS_AWAY) ||
631 (primitive == GAIM_STATUS_INVISIBLE) || (primitive == GAIM_STATUS_OFFLINE)) &&
632 (!gaim_savedstatus_has_substatuses(saved_status)))
633 {
634 index = get_statusbox_index(status_box, saved_status);
635 gtk_combo_box_set_active(GTK_COMBO_BOX(status_box), index);
636 }
637 else
638 {
639 GtkTreeIter iter;
640 GtkGaimStatusBoxItemType type;
641 gpointer data;
642
643 /* Unset the active item */
644 gtk_combo_box_set_active(GTK_COMBO_BOX(status_box), -1);
645
646 /* If this saved status is in the list store, then set it as the active item */
647 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(status_box->dropdown_store), &iter))
648 {
649 do
650 {
651 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
652 TYPE_COLUMN, &type,
653 DATA_COLUMN, &data,
654 -1);
655 if ((type == GTK_GAIM_STATUS_BOX_TYPE_POPULAR) &&
656 (GPOINTER_TO_INT(data) == gaim_savedstatus_get_creation_time(saved_status)))
657 {
658 /* Found! */
659 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(status_box), &iter);
660 break;
661 }
662 }
663 while (gtk_tree_model_iter_next(GTK_TREE_MODEL(status_box->dropdown_store), &iter));
664 }
665 }
666
667 message = gaim_savedstatus_get_message(saved_status);
668 if (!gaim_savedstatus_is_transient(saved_status) || !message || !*message)
669 {
670 status_box->imhtml_visible = FALSE;
671 gtk_widget_hide_all(status_box->vbox);
672 }
673 else
674 {
675 status_box->imhtml_visible = TRUE;
676 gtk_widget_show_all(status_box->vbox);
677
678 /*
679 * Suppress the "changed" signal because the status
680 * was changed programmatically.
681 */
682 gtk_widget_set_sensitive(GTK_WIDGET(status_box->imhtml), FALSE);
683
684 gtk_imhtml_clear(GTK_IMHTML(status_box->imhtml));
685 gtk_imhtml_clear_formatting(GTK_IMHTML(status_box->imhtml));
686 gtk_imhtml_append_text(GTK_IMHTML(status_box->imhtml), message, 0);
687 gtk_widget_set_sensitive(GTK_WIDGET(status_box->imhtml), TRUE);
688 }
689
690 update_size(status_box);
691
692 /* Stop suppressing the "changed" signal. */
693 gtk_widget_set_sensitive(GTK_WIDGET(status_box), TRUE);
694 }
695
696 static void
697 add_popular_statuses(GtkGaimStatusBox *statusbox)
698 {
699 gboolean show_buddy_icons;
700 GtkIconSize icon_size;
701 GList *list, *cur;
702 GdkPixbuf *pixbuf, *emblem;
703 int width, height;
704
705 list = gaim_savedstatuses_get_popular(6);
706 if (list == NULL)
707 /* Odd... oh well, nothing we can do about it. */
708 return;
709
710 show_buddy_icons = gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons");
711 if (show_buddy_icons)
712 icon_size = gtk_icon_size_from_name(GAIM_ICON_SIZE_STATUS);
713 else
714 icon_size = gtk_icon_size_from_name(GAIM_ICON_SIZE_STATUS_SMALL);
715
716 gtk_gaim_status_box_add_separator(statusbox);
717
718 for (cur = list; cur != NULL; cur = cur->next)
719 {
720 GaimSavedStatus *saved = cur->data;
721 const gchar *message;
722 gchar *stripped = NULL;
723
724 /* Get an appropriate status icon */
725 pixbuf = gaim_gtk_create_gaim_icon_with_status(
726 gaim_savedstatus_get_type(saved),
727 show_buddy_icons ? 1.0 : 0.5);
728
729 if (gaim_savedstatus_is_transient(saved))
730 {
731 /*
732 * Transient statuses do not have a title, so the savedstatus
733 * API returns the message when gaim_savedstatus_get_title() is
734 * called, so we don't need to get the message a second time.
735 */
736 }
737 else
738 {
739 message = gaim_savedstatus_get_message(saved);
740 if (message != NULL)
741 {
742 stripped = gaim_markup_strip_html(message);
743 gaim_util_chrreplace(stripped, '\n', ' ');
744 }
745
746 /* Overlay a disk in the bottom left corner */
747 emblem = gtk_widget_render_icon(GTK_WIDGET(statusbox->vbox),
748 GTK_STOCK_SAVE, icon_size, "GtkGaimStatusBox");
749 if (emblem != NULL)
750 {
751 width = gdk_pixbuf_get_width(pixbuf) / 2;
752 height = gdk_pixbuf_get_height(pixbuf) / 2;
753 gdk_pixbuf_composite(emblem, pixbuf, 0, height,
754 width, height, 0, height,
755 0.5, 0.5, GDK_INTERP_BILINEAR, 255);
756 g_object_unref(G_OBJECT(emblem));
757 }
758 }
759
760 gtk_gaim_status_box_add(statusbox, GTK_GAIM_STATUS_BOX_TYPE_POPULAR,
761 pixbuf, gaim_savedstatus_get_title(saved), stripped,
762 GINT_TO_POINTER(gaim_savedstatus_get_creation_time(saved)));
763 g_free(stripped);
764 if (pixbuf != NULL)
765 g_object_unref(G_OBJECT(pixbuf));
766 }
767
768 g_list_free(list);
769 }
770
771 static void
772 gtk_gaim_status_box_regenerate(GtkGaimStatusBox *status_box)
773 {
774 gboolean show_buddy_icons;
775 GaimAccount *account;
776 GdkPixbuf *pixbuf, *pixbuf2, *pixbuf3, *pixbuf4, *tmp;
777 GtkIconSize icon_size;
778
779 show_buddy_icons = gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons");
780 if (show_buddy_icons)
781 icon_size = gtk_icon_size_from_name(GAIM_ICON_SIZE_STATUS);
782 else
783 icon_size = gtk_icon_size_from_name(GAIM_ICON_SIZE_STATUS_SMALL);
784
785 /* Unset the model while clearing it */
786 gtk_combo_box_set_model(GTK_COMBO_BOX(status_box), NULL);
787 gtk_list_store_clear(status_box->dropdown_store);
788 /* Don't set the model until the new statuses have been added to the box.
789 * What is presumably a bug in Gtk < 2.4 causes things to get all confused
790 * if we do this here. */
791 /* gtk_combo_box_set_model(GTK_COMBO_BOX(status_box), GTK_TREE_MODEL(status_box->dropdown_store)); */
792
793 account = GTK_GAIM_STATUS_BOX(status_box)->account;
794 if (account == NULL)
795 {
796 /* Global */
797 pixbuf = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), GAIM_STOCK_STATUS_ONLINE,
798 icon_size, "GtkGaimStatusBox");
799 pixbuf2 = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), GAIM_STOCK_STATUS_AWAY,
800 icon_size, "GtkGaimStatusBox");
801 pixbuf3 = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), GAIM_STOCK_STATUS_OFFLINE,
802 icon_size, "GtkGaimStatusBox");
803 pixbuf4 = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), GAIM_STOCK_STATUS_INVISIBLE,
804 icon_size, "GtkGaimStatusBox");
805
806 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));
807 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));
808 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));
809 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));
810
811 add_popular_statuses(status_box);
812
813 gtk_gaim_status_box_add_separator(GTK_GAIM_STATUS_BOX(status_box));
814 gtk_gaim_status_box_add(GTK_GAIM_STATUS_BOX(status_box), GTK_GAIM_STATUS_BOX_TYPE_CUSTOM, pixbuf, _("New..."), NULL, NULL);
815 gtk_gaim_status_box_add(GTK_GAIM_STATUS_BOX(status_box), GTK_GAIM_STATUS_BOX_TYPE_SAVED, pixbuf, _("Saved..."), NULL, NULL);
816
817 gtk_combo_box_set_model(GTK_COMBO_BOX(status_box), GTK_TREE_MODEL(status_box->dropdown_store));
818 status_menu_refresh_iter(status_box);
819
820 } else {
821 /* Per-account */
822 const GList *l;
823
824 for (l = gaim_account_get_status_types(account); l != NULL; l = l->next)
825 {
826 GaimStatusType *status_type = (GaimStatusType *)l->data;
827
828 if (!gaim_status_type_is_user_settable(status_type))
829 continue;
830
831 tmp = gaim_gtk_create_prpl_icon_with_status(account, status_type,
832 show_buddy_icons ? 1.0 : 0.5);
833 gtk_gaim_status_box_add(GTK_GAIM_STATUS_BOX(status_box),
834 GTK_GAIM_STATUS_BOX_TYPE_PRIMITIVE, tmp,
835 gaim_status_type_get_name(status_type),
836 NULL,
837 GINT_TO_POINTER(gaim_status_type_get_primitive(status_type)));
838 if (tmp != NULL)
839 g_object_unref(tmp);
840 }
841 gtk_combo_box_set_model(GTK_COMBO_BOX(status_box), GTK_TREE_MODEL(status_box->dropdown_store));
842 update_to_reflect_account_status(status_box, account, gaim_account_get_active_status(account));
843 }
844 }
845
846 static gboolean combo_box_scroll_event_cb(GtkWidget *w, GdkEventScroll *event, GtkIMHtml *imhtml)
847 {
848 gtk_combo_box_popup(GTK_COMBO_BOX(w));
849 return TRUE;
850 }
851
852 static gboolean imhtml_scroll_event_cb(GtkWidget *w, GdkEventScroll *event, GtkIMHtml *imhtml)
853 {
854 if (event->direction == GDK_SCROLL_UP)
855 gtk_imhtml_page_up(imhtml);
856 else if (event->direction == GDK_SCROLL_DOWN)
857 gtk_imhtml_page_down(imhtml);
858 return TRUE;
859 }
860
861 static int imhtml_remove_focus(GtkWidget *w, GdkEventKey *event, GtkGaimStatusBox *status_box)
862 {
863 if (event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab)
864 {
865 /* If last inserted character is a tab, then remove the focus from here */
866 GtkWidget *top = gtk_widget_get_toplevel(w);
867 g_signal_emit_by_name(G_OBJECT(top), "move_focus",
868 (event->state & GDK_SHIFT_MASK) ?
869 GTK_DIR_TAB_BACKWARD: GTK_DIR_TAB_FORWARD);
870 return TRUE;
871 }
872 if (!status_box->typing != 0)
873 return FALSE;
874
875 /* Reset the status if Escape was pressed */
876 if (event->keyval == GDK_Escape)
877 {
878 g_source_remove(status_box->typing);
879 status_box->typing = 0;
880 if (status_box->account != NULL)
881 update_to_reflect_account_status(status_box, status_box->account,
882 gaim_account_get_active_status(status_box->account));
883 else
884 status_menu_refresh_iter(status_box);
885 return TRUE;
886 }
887
888 gtk_gaim_status_box_pulse_typing(status_box);
889 g_source_remove(status_box->typing);
890 status_box->typing = g_timeout_add(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
891
892 return FALSE;
893 }
894
895 #if GTK_CHECK_VERSION(2,6,0)
896 static gboolean
897 dropdown_store_row_separator_func(GtkTreeModel *model,
898 GtkTreeIter *iter, gpointer data)
899 {
900 GtkGaimStatusBoxItemType type;
901
902 gtk_tree_model_get(model, iter, TYPE_COLUMN, &type, -1);
903
904 if (type == GTK_GAIM_STATUS_BOX_TYPE_SEPARATOR)
905 return TRUE;
906
907 return FALSE;
908 }
909 #endif
910
911 static void
912 cache_pixbufs(GtkGaimStatusBox *status_box)
913 {
914 GtkIconSize icon_size;
915
916 if (gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons"))
917 {
918 g_object_set(G_OBJECT(status_box->icon_rend), "xpad", 6, NULL);
919 icon_size = gtk_icon_size_from_name(GAIM_ICON_SIZE_STATUS_TWO_LINE);
920 } else {
921 g_object_set(G_OBJECT(status_box->icon_rend), "xpad", 3, NULL);
922 icon_size = gtk_icon_size_from_name(GAIM_ICON_SIZE_STATUS_SMALL_TWO_LINE);
923 }
924
925 if (status_box->connecting_pixbufs[0] != NULL)
926 gdk_pixbuf_unref(status_box->connecting_pixbufs[0]);
927 if (status_box->connecting_pixbufs[1] != NULL)
928 gdk_pixbuf_unref(status_box->connecting_pixbufs[1]);
929 if (status_box->connecting_pixbufs[2] != NULL)
930 gdk_pixbuf_unref(status_box->connecting_pixbufs[2]);
931 if (status_box->connecting_pixbufs[3] != NULL)
932 gdk_pixbuf_unref(status_box->connecting_pixbufs[3]);
933
934 status_box->connecting_index = 0;
935 status_box->connecting_pixbufs[0] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), GAIM_STOCK_STATUS_CONNECT0,
936 icon_size, "GtkGaimStatusBox");
937 status_box->connecting_pixbufs[1] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), GAIM_STOCK_STATUS_CONNECT1,
938 icon_size, "GtkGaimStatusBox");
939 status_box->connecting_pixbufs[2] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), GAIM_STOCK_STATUS_CONNECT2,
940 icon_size, "GtkGaimStatusBox");
941 status_box->connecting_pixbufs[3] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), GAIM_STOCK_STATUS_CONNECT3,
942 icon_size, "GtkGaimStatusBox");
943
944 if (status_box->typing_pixbufs[0] != NULL)
945 gdk_pixbuf_unref(status_box->typing_pixbufs[0]);
946 if (status_box->typing_pixbufs[1] != NULL)
947 gdk_pixbuf_unref(status_box->typing_pixbufs[1]);
948 if (status_box->typing_pixbufs[2] != NULL)
949 gdk_pixbuf_unref(status_box->typing_pixbufs[2]);
950 if (status_box->typing_pixbufs[3] != NULL)
951 gdk_pixbuf_unref(status_box->typing_pixbufs[3]);
952
953 status_box->typing_index = 0;
954 status_box->typing_pixbufs[0] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), GAIM_STOCK_STATUS_TYPING0,
955 icon_size, "GtkGaimStatusBox");
956 status_box->typing_pixbufs[1] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), GAIM_STOCK_STATUS_TYPING1,
957 icon_size, "GtkGaimStatusBox");
958 status_box->typing_pixbufs[2] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), GAIM_STOCK_STATUS_TYPING2,
959 icon_size, "GtkGaimStatusBox");
960 status_box->typing_pixbufs[3] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), GAIM_STOCK_STATUS_TYPING3,
961 icon_size, "GtkGaimStatusBox");
962 }
963
964 static void
965 current_savedstatus_changed_cb(GaimSavedStatus *now, GaimSavedStatus *old, gpointer data)
966 {
967 GtkGaimStatusBox *status_box = data;
968
969 /* Make sure our current status is added to the list of popular statuses */
970 gtk_gaim_status_box_regenerate(status_box);
971
972 if (status_box->account != NULL)
973 update_to_reflect_account_status(status_box, status_box->account,
974 gaim_account_get_active_status(status_box->account));
975 else
976 status_menu_refresh_iter(status_box);
977
978 gtk_gaim_status_box_refresh(status_box);
979 }
980
981 static void
982 buddy_list_details_pref_changed_cb(const char *name, GaimPrefType type,
983 gconstpointer val, gpointer data)
984 {
985 GtkGaimStatusBox *status_box = (GtkGaimStatusBox *)data;
986
987 cache_pixbufs(status_box);
988 gtk_gaim_status_box_regenerate(status_box);
989 gtk_gaim_status_box_refresh(status_box);
990 }
991
992 static void
993 spellcheck_prefs_cb(const char *name, GaimPrefType type,
994 gconstpointer value, gpointer data)
995 {
996 #ifdef USE_GTKSPELL
997 GtkGaimStatusBox *status_box = (GtkGaimStatusBox *)data;
998
999 if (value)
1000 gaim_gtk_setup_gtkspell(GTK_TEXT_VIEW(status_box->imhtml));
1001 else
1002 {
1003 GtkSpell *spell;
1004 spell = gtkspell_get_from_text_view(GTK_TEXT_VIEW(status_box->imhtml));
1005 gtkspell_detach(spell);
1006 }
1007 #endif
1008 }
1009
1010 #if 0
1011 static gboolean button_released_cb(GtkWidget *widget, GdkEventButton *event, GtkGaimStatusBox *box)
1012 {
1013
1014 if (event->button != 1)
1015 return FALSE;
1016 gtk_combo_box_popdown(GTK_COMBO_BOX(box));
1017 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box->toggle_button), FALSE);
1018 if (!box->imhtml_visible)
1019 g_signal_emit_by_name(G_OBJECT(box), "changed", NULL, NULL);
1020 return TRUE;
1021 }
1022
1023 static gboolean button_pressed_cb(GtkWidget *widget, GdkEventButton *event, GtkGaimStatusBox *box)
1024 {
1025 if (event->button != 1)
1026 return FALSE;
1027 gtk_combo_box_popup(GTK_COMBO_BOX(box));
1028 // Disabled until button_released_cb works
1029 // gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box->toggle_button), TRUE);
1030 return TRUE;
1031 }
1032 #endif
1033
1034 static void
1035 toggled_cb(GtkWidget *widget, GtkGaimStatusBox *box)
1036 {
1037 gtk_combo_box_popup(GTK_COMBO_BOX(box));
1038 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box->toggle_button), FALSE);
1039 }
1040
1041 static void
1042 icon_choose_cb(const char *filename, gpointer data)
1043 {
1044 GtkGaimStatusBox *box;
1045
1046 box = data;
1047
1048 if (filename) {
1049 GList *accounts;
1050
1051 if (box->account) {
1052 GaimPlugin *plug = gaim_find_prpl(gaim_account_get_protocol_id(box->account));
1053 if (plug) {
1054 GaimPluginProtocolInfo *prplinfo = GAIM_PLUGIN_PROTOCOL_INFO(plug);
1055 if (prplinfo && prplinfo->icon_spec.format) {
1056 char *icon = gaim_gtk_convert_buddy_icon(plug, filename);
1057 gaim_account_set_buddy_icon(box->account, icon);
1058 g_free(icon);
1059 gaim_account_set_ui_bool(box->account, GAIM_GTK_UI, "use-global-buddyicon", FALSE);
1060 gaim_account_set_ui_string(box->account, GAIM_GTK_UI, "non-global-buddyicon", icon);
1061 }
1062 }
1063 } else {
1064 for (accounts = gaim_accounts_get_all(); accounts != NULL; accounts = accounts->next) {
1065 GaimAccount *account = accounts->data;
1066 GaimPlugin *plug = gaim_find_prpl(gaim_account_get_protocol_id(account));
1067 if (plug) {
1068 GaimPluginProtocolInfo *prplinfo = GAIM_PLUGIN_PROTOCOL_INFO(plug);
1069 if (prplinfo != NULL &&
1070 gaim_account_get_ui_bool(account, GAIM_GTK_UI, "use-global-buddyicon", TRUE) &&
1071 prplinfo->icon_spec.format) {
1072 char *icon = gaim_gtk_convert_buddy_icon(plug, filename);
1073 gaim_account_set_buddy_icon(account, icon);
1074 g_free(icon);
1075 }
1076 }
1077 }
1078 }
1079 gtk_gaim_status_box_set_buddy_icon(box, filename);
1080 }
1081
1082 box->buddy_icon_sel = NULL;
1083 }
1084
1085 static void
1086 gtk_gaim_status_box_init (GtkGaimStatusBox *status_box)
1087 {
1088 GtkCellRenderer *text_rend;
1089 GtkCellRenderer *icon_rend;
1090 GtkTextBuffer *buffer;
1091
1092 status_box->imhtml_visible = FALSE;
1093 status_box->connecting = FALSE;
1094 status_box->typing = 0;
1095 status_box->toggle_button = gtk_toggle_button_new();
1096 status_box->hbox = gtk_hbox_new(FALSE, 6);
1097 status_box->cell_view = gtk_cell_view_new();
1098 status_box->vsep = gtk_vseparator_new();
1099 status_box->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
1100
1101 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);
1102 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);
1103 gtk_combo_box_set_model(GTK_COMBO_BOX(status_box), GTK_TREE_MODEL(status_box->dropdown_store));
1104 gtk_cell_view_set_model(GTK_CELL_VIEW(status_box->cell_view), GTK_TREE_MODEL(status_box->store));
1105 gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(status_box), 0);
1106 gtk_list_store_append(status_box->store, &(status_box->iter));
1107
1108 gtk_container_add(GTK_CONTAINER(status_box->toggle_button), status_box->hbox);
1109 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->cell_view, TRUE, TRUE, 0);
1110 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->vsep, FALSE, FALSE, 0);
1111 gtk_box_pack_start(GTK_BOX(status_box->hbox), status_box->arrow, FALSE, FALSE, 0);
1112 gtk_widget_show_all(status_box->toggle_button);
1113 #if GTK_CHECK_VERSION(2,4,0)
1114 gtk_button_set_focus_on_click(GTK_BUTTON(status_box->toggle_button), FALSE);
1115 #endif
1116
1117 text_rend = gtk_cell_renderer_text_new();
1118 icon_rend = gtk_cell_renderer_pixbuf_new();
1119 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box), icon_rend, FALSE);
1120 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box), text_rend, TRUE);
1121 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box), icon_rend, "pixbuf", ICON_COLUMN, NULL);
1122 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box), text_rend, "markup", TEXT_COLUMN, NULL);
1123 #if GTK_CHECK_VERSION(2, 6, 0)
1124 g_object_set(text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1125 #endif
1126
1127 status_box->icon_rend = gtk_cell_renderer_pixbuf_new();
1128 status_box->text_rend = gtk_cell_renderer_text_new();
1129 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), status_box->icon_rend, FALSE);
1130 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box->cell_view), status_box->text_rend, TRUE);
1131 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), status_box->icon_rend, "pixbuf", ICON_COLUMN, NULL);
1132 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box->cell_view), status_box->text_rend, "markup", TEXT_COLUMN, NULL);
1133 #if GTK_CHECK_VERSION(2, 6, 0)
1134 g_object_set(status_box->text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1135 #endif
1136
1137 status_box->vbox = gtk_vbox_new(0, FALSE);
1138 status_box->sw = gaim_gtk_create_imhtml(FALSE, &status_box->imhtml, NULL, NULL);
1139 gtk_imhtml_set_editable(GTK_IMHTML(status_box->imhtml), TRUE);
1140
1141 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(status_box->imhtml));
1142 #if 0
1143 g_signal_connect(G_OBJECT(status_box->toggle_button), "button-press-event",
1144 G_CALLBACK(button_pressed_cb), status_box);
1145 g_signal_connect(G_OBJECT(status_box->toggle_button), "button-release-event",
1146 G_CALLBACK(button_released_cb), status_box);
1147 #endif
1148 g_signal_connect(G_OBJECT(status_box->toggle_button), "toggled",
1149 G_CALLBACK(toggled_cb), status_box);
1150 g_signal_connect(G_OBJECT(buffer), "changed", G_CALLBACK(imhtml_changed_cb), status_box);
1151 g_signal_connect(G_OBJECT(status_box->imhtml), "format_function_toggle",
1152 G_CALLBACK(imhtml_format_changed_cb), status_box);
1153 g_signal_connect(G_OBJECT(status_box->imhtml), "key_press_event",
1154 G_CALLBACK(imhtml_remove_focus), status_box);
1155 g_signal_connect_swapped(G_OBJECT(status_box->imhtml), "message_send", G_CALLBACK(remove_typing_cb), status_box);
1156 gtk_imhtml_set_editable(GTK_IMHTML(status_box->imhtml), TRUE);
1157 #ifdef USE_GTKSPELL
1158 if (gaim_prefs_get_bool("/gaim/gtk/conversations/spellcheck"))
1159 gaim_gtk_setup_gtkspell(GTK_TEXT_VIEW(status_box->imhtml));
1160 #endif
1161 gtk_widget_set_parent(status_box->vbox, GTK_WIDGET(status_box));
1162 gtk_widget_set_parent(status_box->toggle_button, GTK_WIDGET(status_box));
1163 GTK_BIN(status_box)->child = status_box->toggle_button;
1164
1165 gtk_box_pack_start(GTK_BOX(status_box->vbox), status_box->sw, TRUE, TRUE, 0);
1166
1167 g_signal_connect(G_OBJECT(status_box), "scroll_event", G_CALLBACK(combo_box_scroll_event_cb), NULL);
1168 g_signal_connect(G_OBJECT(status_box->imhtml), "scroll_event",
1169 G_CALLBACK(imhtml_scroll_event_cb), status_box->imhtml);
1170
1171 #if GTK_CHECK_VERSION(2,6,0)
1172 gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(status_box), dropdown_store_row_separator_func, NULL, NULL);
1173 #endif
1174
1175 cache_pixbufs(status_box);
1176 gtk_gaim_status_box_regenerate(status_box);
1177 gtk_gaim_status_box_refresh(status_box);
1178
1179 gaim_signal_connect(gaim_savedstatuses_get_handle(), "savedstatus-changed",
1180 status_box,
1181 GAIM_CALLBACK(current_savedstatus_changed_cb),
1182 status_box);
1183 gaim_prefs_connect_callback(status_box, "/gaim/gtk/blist/show_buddy_icons",
1184 buddy_list_details_pref_changed_cb, status_box);
1185 gaim_prefs_connect_callback(status_box, "/gaim/gtk/conversations/spellcheck",
1186 spellcheck_prefs_cb, status_box);
1187 }
1188
1189 static void
1190 gtk_gaim_status_box_size_request(GtkWidget *widget,
1191 GtkRequisition *requisition)
1192 {
1193 GtkRequisition box_req;
1194 combo_box_size_request(widget, requisition);
1195 requisition->height += 3;
1196
1197 /* If the gtkimhtml is visible, then add some additional padding */
1198 gtk_widget_size_request(GTK_GAIM_STATUS_BOX(widget)->vbox, &box_req);
1199 if (box_req.height > 1)
1200 requisition->height += box_req.height + 3;
1201
1202 requisition->width = 1;
1203
1204
1205 }
1206
1207 /* From gnome-panel */
1208 static void
1209 do_colorshift (GdkPixbuf *dest, GdkPixbuf *src, int shift)
1210 {
1211 gint i, j;
1212 gint width, height, has_alpha, srcrowstride, destrowstride;
1213 guchar *target_pixels;
1214 guchar *original_pixels;
1215 guchar *pixsrc;
1216 guchar *pixdest;
1217 int val;
1218 guchar r,g,b;
1219
1220 has_alpha = gdk_pixbuf_get_has_alpha (src);
1221 width = gdk_pixbuf_get_width (src);
1222 height = gdk_pixbuf_get_height (src);
1223 srcrowstride = gdk_pixbuf_get_rowstride (src);
1224 destrowstride = gdk_pixbuf_get_rowstride (dest);
1225 target_pixels = gdk_pixbuf_get_pixels (dest);
1226 original_pixels = gdk_pixbuf_get_pixels (src);
1227
1228 for (i = 0; i < height; i++) {
1229 pixdest = target_pixels + i*destrowstride;
1230 pixsrc = original_pixels + i*srcrowstride;
1231 for (j = 0; j < width; j++) {
1232 r = *(pixsrc++);
1233 g = *(pixsrc++);
1234 b = *(pixsrc++);
1235 val = r + shift;
1236 *(pixdest++) = CLAMP(val, 0, 255);
1237 val = g + shift;
1238 *(pixdest++) = CLAMP(val, 0, 255);
1239 val = b + shift;
1240 *(pixdest++) = CLAMP(val, 0, 255);
1241 if (has_alpha)
1242 *(pixdest++) = *(pixsrc++);
1243 }
1244 }
1245 }
1246
1247 static void
1248 gtk_gaim_status_box_size_allocate(GtkWidget *widget,
1249 GtkAllocation *allocation)
1250 {
1251 GtkGaimStatusBox *status_box = GTK_GAIM_STATUS_BOX(widget);
1252 GtkRequisition req = {0,0};
1253 GtkAllocation parent_alc, box_alc, icon_alc;
1254 GdkPixbuf *scaled;
1255
1256 combo_box_size_request(widget, &req);
1257
1258 box_alc = *allocation;
1259 box_alc.height = MAX(1, (allocation->height - req.height - 6));
1260 box_alc.y += req.height + 6;
1261 gtk_widget_size_allocate((GTK_GAIM_STATUS_BOX(widget))->vbox, &box_alc);
1262
1263 parent_alc = *allocation;
1264 parent_alc.height = MAX(1,req.height);
1265 parent_alc.y += 3;
1266
1267 if (status_box->icon_box)
1268 {
1269 parent_alc.width -= (parent_alc.height + 3);
1270 icon_alc = *allocation;
1271 icon_alc.height = MAX(1,req.height);
1272 icon_alc.width = icon_alc.height;
1273 icon_alc.x = allocation->width - icon_alc.width;
1274 icon_alc.y += 3;
1275
1276 if (status_box->icon_size != icon_alc.height)
1277 {
1278 if (status_box->buddy_icon_hover)
1279 g_object_unref(status_box->buddy_icon_hover);
1280 if ((status_box->buddy_icon_path != NULL) &&
1281 (*status_box->buddy_icon_path != '\0'))
1282 {
1283 scaled = gdk_pixbuf_new_from_file_at_scale(status_box->buddy_icon_path,
1284 icon_alc.height, icon_alc.width, FALSE, NULL);
1285 if (scaled != NULL)
1286 {
1287 status_box->buddy_icon_hover = gdk_pixbuf_copy(scaled);
1288 do_colorshift(status_box->buddy_icon_hover, status_box->buddy_icon_hover, 30);
1289 if (status_box->buddy_icon)
1290 g_object_unref(status_box->buddy_icon);
1291 status_box->buddy_icon = scaled;
1292 gtk_image_set_from_pixbuf(GTK_IMAGE(status_box->icon), status_box->buddy_icon);
1293 }
1294 }
1295 status_box->icon_size = icon_alc.height;
1296 }
1297 gtk_widget_size_allocate(status_box->icon_box, &icon_alc);
1298 }
1299
1300 combo_box_size_allocate(widget, &parent_alc);
1301 gtk_widget_size_allocate(status_box->toggle_button, &parent_alc);
1302 widget->allocation = *allocation;
1303 }
1304
1305 static gboolean
1306 gtk_gaim_status_box_expose_event(GtkWidget *widget,
1307 GdkEventExpose *event)
1308 {
1309 GtkGaimStatusBox *status_box = GTK_GAIM_STATUS_BOX(widget);
1310 gtk_container_propagate_expose(GTK_CONTAINER(widget), status_box->vbox, event);
1311 gtk_container_propagate_expose(GTK_CONTAINER(widget), status_box->toggle_button, event);
1312 if (status_box->icon_box)
1313 gtk_container_propagate_expose(GTK_CONTAINER(widget), status_box->icon_box, event);
1314 return FALSE;
1315 }
1316
1317 static void
1318 gtk_gaim_status_box_forall(GtkContainer *container,
1319 gboolean include_internals,
1320 GtkCallback callback,
1321 gpointer callback_data)
1322 {
1323 GtkGaimStatusBox *status_box = GTK_GAIM_STATUS_BOX (container);
1324
1325 if (include_internals)
1326 {
1327 (* callback) (status_box->vbox, callback_data);
1328 (* callback) (status_box->toggle_button, callback_data);
1329 (* callback) (status_box->arrow, callback_data);
1330 if (status_box->icon_box)
1331 (* callback) (status_box->icon_box, callback_data);
1332 }
1333
1334 combo_box_forall(container, include_internals, callback, callback_data);
1335 }
1336
1337 GtkWidget *
1338 gtk_gaim_status_box_new()
1339 {
1340 return g_object_new(GTK_GAIM_TYPE_STATUS_BOX, "account", NULL,
1341 "iconsel", TRUE, NULL);
1342 }
1343
1344 GtkWidget *
1345 gtk_gaim_status_box_new_with_account(GaimAccount *account)
1346 {
1347 return g_object_new(GTK_GAIM_TYPE_STATUS_BOX, "account", account,
1348 "iconsel", TRUE, NULL);
1349 }
1350
1351 /**
1352 * Add a row to the dropdown menu.
1353 *
1354 * @param status_box The status box itself.
1355 * @param type A GtkGaimStatusBoxItemType.
1356 * @param pixbuf The icon to associate with this row in the menu.
1357 * @param title The title of this item. For the primitive entries,
1358 * this is something like "Available" or "Away." For
1359 * the saved statuses, this is something like
1360 * "My favorite away message!" This should be
1361 * plaintext (non-markedup) (this function escapes it).
1362 * @param desc The secondary text for this item. This will be
1363 * placed on the row below the title, in a dimmer
1364 * font (generally gray). This text should be plaintext
1365 * (non-markedup) (this function escapes it).
1366 * @param data Data to be associated with this row in the dropdown
1367 * menu. For primitives this is the value of the
1368 * GaimStatusPrimitive. For saved statuses this is the
1369 * creation timestamp.
1370 */
1371 void
1372 gtk_gaim_status_box_add(GtkGaimStatusBox *status_box, GtkGaimStatusBoxItemType type, GdkPixbuf *pixbuf, const char *title, const char *desc, gpointer data)
1373 {
1374 GtkTreeIter iter;
1375 char *text;
1376
1377 if (desc == NULL)
1378 {
1379 text = g_markup_escape_text(title, -1);
1380 }
1381 else
1382 {
1383 gboolean show_buddy_icons;
1384 GtkStyle *style;
1385 char aa_color[8];
1386 gchar *escaped_title, *escaped_desc;
1387
1388 show_buddy_icons = gaim_prefs_get_bool("/gaim/gtk/blist/show_buddy_icons");
1389 style = gtk_widget_get_style(GTK_WIDGET(status_box));
1390 snprintf(aa_color, sizeof(aa_color), "#%02x%02x%02x",
1391 style->text_aa[GTK_STATE_NORMAL].red >> 8,
1392 style->text_aa[GTK_STATE_NORMAL].green >> 8,
1393 style->text_aa[GTK_STATE_NORMAL].blue >> 8);
1394
1395 escaped_title = g_markup_escape_text(title, -1);
1396 escaped_desc = g_markup_escape_text(desc, -1);
1397 text = g_strdup_printf("%s%s<span color=\"%s\" size=\"smaller\">%s</span>",
1398 escaped_title,
1399 show_buddy_icons ? "\n" : " - ",
1400 aa_color, escaped_desc);
1401 g_free(escaped_title);
1402 g_free(escaped_desc);
1403 }
1404
1405 gtk_list_store_append(status_box->dropdown_store, &iter);
1406 gtk_list_store_set(status_box->dropdown_store, &iter,
1407 TYPE_COLUMN, type,
1408 ICON_COLUMN, pixbuf,
1409 TEXT_COLUMN, text,
1410 TITLE_COLUMN, title,
1411 DESC_COLUMN, desc,
1412 DATA_COLUMN, data,
1413 -1);
1414 g_free(text);
1415 }
1416
1417 void
1418 gtk_gaim_status_box_add_separator(GtkGaimStatusBox *status_box)
1419 {
1420 /* Don't do anything unless GTK actually supports
1421 * gtk_combo_box_set_row_separator_func */
1422 #if GTK_CHECK_VERSION(2,6,0)
1423 GtkTreeIter iter;
1424
1425 gtk_list_store_append(status_box->dropdown_store, &iter);
1426 gtk_list_store_set(status_box->dropdown_store, &iter,
1427 TYPE_COLUMN, GTK_GAIM_STATUS_BOX_TYPE_SEPARATOR,
1428 -1);
1429 #endif
1430 }
1431
1432 void
1433 gtk_gaim_status_box_set_connecting(GtkGaimStatusBox *status_box, gboolean connecting)
1434 {
1435 if (!status_box)
1436 return;
1437 status_box->connecting = connecting;
1438 gtk_gaim_status_box_refresh(status_box);
1439 }
1440
1441 void
1442 gtk_gaim_status_box_set_buddy_icon(GtkGaimStatusBox *box, const char *filename)
1443 {
1444 GdkPixbuf *scaled;
1445 g_free(box->buddy_icon_path);
1446 box->buddy_icon_path = g_strdup(filename);
1447
1448 if ((filename != NULL) && (*filename != '\0'))
1449 {
1450 if (box->buddy_icon != NULL)
1451 g_object_unref(box->buddy_icon);
1452
1453 /* This will get called before the box is shown and will not have a size */
1454 if (box->icon_size > 0) {
1455 scaled = gdk_pixbuf_new_from_file_at_scale(filename,
1456 box->icon_size, box->icon_size, FALSE, NULL);
1457 if (scaled != NULL)
1458 {
1459 box->buddy_icon_hover = gdk_pixbuf_copy(scaled);
1460 do_colorshift(box->buddy_icon_hover, box->buddy_icon_hover, 30);
1461 box->buddy_icon = scaled;
1462 gtk_image_set_from_pixbuf(GTK_IMAGE(box->icon), box->buddy_icon);
1463 }
1464 }
1465 }
1466
1467 if (box->account == NULL)
1468 gaim_prefs_set_string("/gaim/gtk/accounts/buddyicon", filename);
1469 }
1470
1471 const char*
1472 gtk_gaim_status_box_get_buddy_icon(GtkGaimStatusBox *box)
1473 {
1474 return box->buddy_icon_path;
1475 }
1476
1477 void
1478 gtk_gaim_status_box_pulse_connecting(GtkGaimStatusBox *status_box)
1479 {
1480 if (!status_box)
1481 return;
1482 if (status_box->connecting_index == 3)
1483 status_box->connecting_index = 0;
1484 else
1485 status_box->connecting_index++;
1486 gtk_gaim_status_box_refresh(status_box);
1487 }
1488
1489 static void
1490 gtk_gaim_status_box_pulse_typing(GtkGaimStatusBox *status_box)
1491 {
1492 if (status_box->typing_index == 3)
1493 status_box->typing_index = 0;
1494 else
1495 status_box->typing_index++;
1496 gtk_gaim_status_box_refresh(status_box);
1497 }
1498
1499 static GaimStatusType *
1500 find_status_type_by_index(const GaimAccount *account, gint active)
1501 {
1502 const GList *l = gaim_account_get_status_types(account);
1503 gint i;
1504
1505 for (i = 0; l; l = l->next) {
1506 GaimStatusType *status_type = l->data;
1507 if (!gaim_status_type_is_user_settable(status_type))
1508 continue;
1509
1510 if (active == i)
1511 return status_type;
1512 i++;
1513 }
1514
1515 return NULL;
1516 }
1517
1518 static gboolean
1519 message_changed(const char *one, const char *two)
1520 {
1521 if (one == NULL && two == NULL)
1522 return FALSE;
1523
1524 if (one == NULL || two == NULL)
1525 return TRUE;
1526
1527 return (g_utf8_collate(one, two) != 0);
1528 }
1529
1530 static void
1531 activate_currently_selected_status(GtkGaimStatusBox *status_box)
1532 {
1533 GtkGaimStatusBoxItemType type;
1534 gpointer data;
1535 gchar *title;
1536 GtkTreeIter iter;
1537 char *message;
1538 GaimSavedStatus *saved_status;
1539 gboolean changed = TRUE;
1540
1541 if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(status_box), &iter))
1542 return;
1543
1544 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
1545 TYPE_COLUMN, &type,
1546 DATA_COLUMN, &data,
1547 -1);
1548
1549 /*
1550 * If the currently selected status is "New..." or
1551 * "Saved..." or a popular status then do nothing.
1552 * Popular statuses are
1553 * activated elsewhere, and we update the status_box
1554 * accordingly by connecting to the savedstatus-changed
1555 * signal and then calling status_menu_refresh_iter()
1556 */
1557 if (type != GTK_GAIM_STATUS_BOX_TYPE_PRIMITIVE)
1558 return;
1559
1560 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
1561 TITLE_COLUMN, &title, -1);
1562
1563 message = gtk_gaim_status_box_get_message(status_box);
1564 if (!message || !*message)
1565 {
1566 gtk_widget_hide_all(status_box->vbox);
1567 status_box->imhtml_visible = FALSE;
1568 if (message != NULL)
1569 {
1570 g_free(message);
1571 message = NULL;
1572 }
1573 }
1574
1575 if (status_box->account == NULL) {
1576 /* Global */
1577 /* Save the newly selected status to prefs.xml and status.xml */
1578
1579 /* Has the status really been changed? */
1580 saved_status = gaim_savedstatus_get_current();
1581 if (gaim_savedstatus_get_type(saved_status) == GPOINTER_TO_INT(data) &&
1582 !gaim_savedstatus_has_substatuses(saved_status))
1583 {
1584 if (!message_changed(gaim_savedstatus_get_message(saved_status), message))
1585 changed = FALSE;
1586 }
1587
1588 if (changed)
1589 {
1590 /* If we've used this type+message before, lookup the transient status */
1591 saved_status = gaim_savedstatus_find_transient_by_type_and_message(
1592 GPOINTER_TO_INT(data), message);
1593
1594 /* If this type+message is unique then create a new transient saved status */
1595 if (saved_status == NULL)
1596 {
1597 saved_status = gaim_savedstatus_new(NULL, GPOINTER_TO_INT(data));
1598 gaim_savedstatus_set_message(saved_status, message);
1599 }
1600
1601 /* Set the status for each account */
1602 gaim_savedstatus_activate(saved_status);
1603 }
1604 } else {
1605 /* Per-account */
1606 gint active;
1607 GaimStatusType *status_type;
1608 GaimStatus *status;
1609 const char *id = NULL;
1610
1611 status = gaim_account_get_active_status(status_box->account);
1612
1613 g_object_get(G_OBJECT(status_box), "active", &active, NULL);
1614
1615 status_type = find_status_type_by_index(status_box->account, active);
1616 id = gaim_status_type_get_id(status_type);
1617
1618 if (strncmp(id, gaim_status_get_id(status), strlen(id)) == 0)
1619 {
1620 /* Selected status and previous status is the same */
1621 if (!message_changed(message, gaim_status_get_attr_string(status, "message")))
1622 changed = FALSE;
1623 }
1624
1625 if (changed)
1626 {
1627 if (message)
1628 gaim_account_set_status(status_box->account, id,
1629 TRUE, "message", message, NULL);
1630 else
1631 gaim_account_set_status(status_box->account, id,
1632 TRUE, NULL);
1633 }
1634 }
1635
1636 g_free(title);
1637 g_free(message);
1638 }
1639
1640 static void update_size(GtkGaimStatusBox *status_box)
1641 {
1642 GtkTextBuffer *buffer;
1643 GtkTextIter iter;
1644 int wrapped_lines;
1645 int lines;
1646 GdkRectangle oneline;
1647 int height;
1648 int pad_top, pad_inside, pad_bottom;
1649
1650 if (!status_box->imhtml_visible)
1651 {
1652 if (status_box->vbox != NULL)
1653 gtk_widget_set_size_request(status_box->vbox, -1, -1);
1654 return;
1655 }
1656
1657 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(status_box->imhtml));
1658
1659 wrapped_lines = 1;
1660 gtk_text_buffer_get_start_iter(buffer, &iter);
1661 while (gtk_text_view_forward_display_line(GTK_TEXT_VIEW(status_box->imhtml), &iter))
1662 wrapped_lines++;
1663
1664 lines = gtk_text_buffer_get_line_count(buffer);
1665
1666 /* Show a maximum of 4 lines */
1667 lines = MIN(lines, 4);
1668 wrapped_lines = MIN(wrapped_lines, 4);
1669
1670 gtk_text_buffer_get_start_iter(buffer, &iter);
1671 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(status_box->imhtml), &iter, &oneline);
1672
1673 pad_top = gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(status_box->imhtml));
1674 pad_bottom = gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(status_box->imhtml));
1675 pad_inside = gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(status_box->imhtml));
1676
1677 height = (oneline.height + pad_top + pad_bottom) * lines;
1678 height += (oneline.height + pad_inside) * (wrapped_lines - lines);
1679
1680 gtk_widget_set_size_request(status_box->vbox, -1, height + GAIM_HIG_BOX_SPACE);
1681 }
1682
1683 static void remove_typing_cb(GtkGaimStatusBox *status_box)
1684 {
1685 if (status_box->typing == 0)
1686 {
1687 /* Nothing has changed, so we don't need to do anything */
1688 status_menu_refresh_iter(status_box);
1689 return;
1690 }
1691
1692 g_source_remove(status_box->typing);
1693 status_box->typing = 0;
1694
1695 activate_currently_selected_status(status_box);
1696 gtk_gaim_status_box_refresh(status_box);
1697 }
1698
1699 static void gtk_gaim_status_box_changed(GtkComboBox *box)
1700 {
1701 GtkGaimStatusBox *status_box;
1702 GtkTreeIter iter;
1703 GtkGaimStatusBoxItemType type;
1704 gpointer data;
1705 GList *accounts = NULL, *node;
1706
1707 status_box = GTK_GAIM_STATUS_BOX(box);
1708
1709 if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(status_box), &iter))
1710 return;
1711 gtk_tree_model_get(GTK_TREE_MODEL(status_box->dropdown_store), &iter,
1712 TYPE_COLUMN, &type,
1713 DATA_COLUMN, &data,
1714 -1);
1715 if (status_box->typing != 0)
1716 g_source_remove(status_box->typing);
1717 status_box->typing = 0;
1718
1719 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box)))
1720 {
1721 if (type == GTK_GAIM_STATUS_BOX_TYPE_POPULAR)
1722 {
1723 GaimSavedStatus *saved;
1724 saved = gaim_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
1725 g_return_if_fail(saved != NULL);
1726 gaim_savedstatus_activate(saved);
1727 return;
1728 }
1729
1730 if (type == GTK_GAIM_STATUS_BOX_TYPE_CUSTOM)
1731 {
1732 GaimSavedStatus *saved_status;
1733 saved_status = gaim_savedstatus_get_current();
1734 gaim_gtk_status_editor_show(FALSE,
1735 gaim_savedstatus_is_transient(saved_status)
1736 ? saved_status : NULL);
1737 status_menu_refresh_iter(status_box);
1738 return;
1739 }
1740
1741 if (type == GTK_GAIM_STATUS_BOX_TYPE_SAVED)
1742 {
1743 gaim_gtk_status_window_show();
1744 status_menu_refresh_iter(status_box);
1745 return;
1746 }
1747 }
1748
1749 /*
1750 * Show the message box whenever the primitive allows for a
1751 * message attribute on any protocol that is enabled,
1752 * or our protocol, if we have account set
1753 */
1754 if (status_box->account)
1755 accounts = g_list_prepend(accounts, status_box->account);
1756 else
1757 accounts = gaim_accounts_get_all_active();
1758 status_box->imhtml_visible = FALSE;
1759 for (node = accounts; node != NULL; node = node->next)
1760 {
1761 GaimAccount *account;
1762 GaimStatusType *status_type;
1763
1764 account = node->data;
1765 status_type = gaim_account_get_status_type_with_primitive(account, GPOINTER_TO_INT(data));
1766 if ((status_type != NULL) &&
1767 (gaim_status_type_get_attr(status_type, "message") != NULL))
1768 {
1769 status_box->imhtml_visible = TRUE;
1770 break;
1771 }
1772 }
1773 g_list_free(accounts);
1774
1775 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box)))
1776 {
1777 if (status_box->imhtml_visible)
1778 {
1779 gtk_widget_show_all(status_box->vbox);
1780 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box))) {
1781 status_box->typing = g_timeout_add(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
1782 }
1783 gtk_widget_grab_focus(status_box->imhtml);
1784 gtk_imhtml_clear(GTK_IMHTML(status_box->imhtml));
1785 }
1786 else
1787 {
1788 gtk_widget_hide_all(status_box->vbox);
1789 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box)))
1790 activate_currently_selected_status(status_box); /* This is where we actually set the status */
1791 }
1792 }
1793 gtk_gaim_status_box_refresh(status_box);
1794 }
1795
1796 static gint
1797 get_statusbox_index(GtkGaimStatusBox *box, GaimSavedStatus *saved_status)
1798 {
1799 gint index;
1800
1801 switch (gaim_savedstatus_get_type(saved_status))
1802 {
1803 case GAIM_STATUS_AVAILABLE:
1804 index = 0;
1805 break;
1806 case GAIM_STATUS_AWAY:
1807 index = 1;
1808 break;
1809 case GAIM_STATUS_INVISIBLE:
1810 index = 2;
1811 break;
1812 case GAIM_STATUS_OFFLINE:
1813 index = 3;
1814 break;
1815 default:
1816 index = -1;
1817 break;
1818 }
1819
1820 return index;
1821 }
1822
1823 static void imhtml_changed_cb(GtkTextBuffer *buffer, void *data)
1824 {
1825 GtkGaimStatusBox *status_box = (GtkGaimStatusBox*)data;
1826 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box)))
1827 {
1828 if (status_box->typing != 0) {
1829 gtk_gaim_status_box_pulse_typing(status_box);
1830 g_source_remove(status_box->typing);
1831 }
1832 status_box->typing = g_timeout_add(TYPING_TIMEOUT, (GSourceFunc)remove_typing_cb, status_box);
1833 }
1834 gtk_gaim_status_box_refresh(status_box);
1835 }
1836
1837 static void imhtml_format_changed_cb(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons, void *data)
1838 {
1839 imhtml_changed_cb(NULL, data);
1840 }
1841
1842 char *gtk_gaim_status_box_get_message(GtkGaimStatusBox *status_box)
1843 {
1844 if (status_box->imhtml_visible)
1845 return gtk_imhtml_get_markup(GTK_IMHTML(status_box->imhtml));
1846 else
1847 return NULL;
1848 }