Mercurial > pidgin.yaz
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 } |