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