1 /**
2 * @file gtksavedstatus.c GTK+ Saved Status Editor UI
3 * @ingroup pidgin
4 */
5
6 /* pidgin
7 *
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
25 */
26
27 #include "internal.h"
28
29 #include "account.h"
30 #include "glibcompat.h"
31 #include "notify.h"
32 #include "request.h"
33 #include "savedstatuses.h"
34 #include "status.h"
35 #include "util.h"
36
37 #include "gtkblist.h"
38 #include "pidgin.h"
39 #include "gtkimhtml.h"
40 #include "gtkimhtmltoolbar.h"
41 #include "gtksavedstatuses.h"
42 #include "pidginstock.h"
43 #include "gtkutils.h"
44
45 /*
46 * TODO: Should attach to the account-deleted and account-added signals
47 * and update the GtkListStores in any StatusEditor windows that
48 * may be open.
49 */
50
51 /**
52 * These are used for the GtkTreeView when you're scrolling through
53 * all your saved statuses.
54 */
55 enum
56 {
57 STATUS_WINDOW_COLUMN_TITLE,
58 STATUS_WINDOW_COLUMN_TYPE,
59 STATUS_WINDOW_COLUMN_MESSAGE,
60 /** A hidden column containing a pointer to the editor for this saved status. */
61 STATUS_WINDOW_COLUMN_WINDOW,
62 STATUS_WINDOW_COLUMN_ICON,
63 STATUS_WINDOW_NUM_COLUMNS
64 };
65
66 /**
67 * These are used for the GtkTreeView containing the list of accounts
68 * at the bottom of the window when you're editing a particular
69 * saved status.
70 */
71 enum
72 {
73 /** A hidden column containing a pointer to the PurpleAccount. */
74 STATUS_EDITOR_COLUMN_ACCOUNT,
75 /** A hidden column containing a pointer to the editor for this substatus. */
76 STATUS_EDITOR_COLUMN_WINDOW,
77 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS,
78 STATUS_EDITOR_COLUMN_ICON,
79 STATUS_EDITOR_COLUMN_USERNAME,
80 /** A hidden column containing the ID of this PurpleStatusType. */
81 STATUS_EDITOR_COLUMN_STATUS_ID,
82 STATUS_EDITOR_COLUMN_STATUS_NAME,
83 STATUS_EDITOR_COLUMN_STATUS_MESSAGE,
84 STATUS_EDITOR_COLUMN_STATUS_ICON,
85 STATUS_EDITOR_NUM_COLUMNS
86 };
87
88 /**
89 * These are used in the GtkComboBox to select the specific PurpleStatusType
90 * when setting a (sub)status for a particular saved status.
91 */
92 enum
93 {
94 STATUS_COLUMN_ICON,
95 /** A hidden column containing the ID of this PurpleStatusType. */
96 STATUS_COLUMN_STATUS_ID,
97 STATUS_COLUMN_STATUS_NAME,
98 STATUS_NUM_COLUMNS
99 };
100
101 typedef struct
102 {
103 GtkWidget *window;
104 GtkListStore *model;
105 GtkWidget *treeview;
106 GtkWidget *use_button;
107 GtkWidget *modify_button;
108 GtkWidget *delete_button;
109 } StatusWindow;
110
111 typedef struct
112 {
113 GtkWidget *window;
114 GtkListStore *model;
115 GtkWidget *treeview;
116 GtkButton *saveanduse_button;
117 GtkButton *save_button;
118
119 gchar *original_title;
120 GtkEntry *title;
121 GtkComboBox *type;
122 GtkIMHtml *message;
123 } StatusEditor;
124
125 typedef struct
126 {
127 StatusEditor *status_editor;
128 PurpleAccount *account;
129
130 GtkWidget *window;
131 GtkListStore *model;
132 GtkComboBox *box;
133 GtkIMHtml *message;
134 GtkIMHtmlToolbar *toolbar;
135 } SubStatusEditor;
136
137 static StatusWindow *status_window = NULL;
138
139
140 /**************************************************************************
141 * Status window
142 **************************************************************************/
143
144 static gboolean
status_window_find_savedstatus(GtkTreeIter * iter,const char * title)145 status_window_find_savedstatus(GtkTreeIter *iter, const char *title)
146 {
147 GtkTreeModel *model;
148 char *cur;
149
150 if ((status_window == NULL) || (title == NULL))
151 return FALSE;
152
153 model = GTK_TREE_MODEL(status_window->model);
154
155 if (!gtk_tree_model_get_iter_first(model, iter))
156 return FALSE;
157
158 do {
159 gtk_tree_model_get(model, iter, STATUS_WINDOW_COLUMN_TITLE, &cur, -1);
160 if (purple_strequal(title, cur))
161 {
162 g_free(cur);
163 return TRUE;
164 }
165 g_free(cur);
166 } while (gtk_tree_model_iter_next(model, iter));
167
168 return FALSE;
169 }
170
171 static gboolean
status_window_destroy_cb(GtkWidget * widget,GdkEvent * event,gpointer user_data)172 status_window_destroy_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
173 {
174 StatusWindow *dialog = user_data;
175
176 dialog->window = NULL;
177 pidgin_status_window_hide();
178
179 return FALSE;
180 }
181
182 static void
status_window_use_cb(GtkButton * button,StatusWindow * dialog)183 status_window_use_cb(GtkButton *button, StatusWindow *dialog)
184 {
185 GtkTreeSelection *selection;
186 GtkTreeIter iter;
187 GList *list = NULL;
188 int num_selected = 0;
189
190 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
191
192 num_selected = gtk_tree_selection_count_selected_rows(selection);
193 if (num_selected != 1)
194 /*
195 * This shouldn't happen because the "Use" button should have
196 * been grayed out. Oh well.
197 */
198 return;
199
200 list = gtk_tree_selection_get_selected_rows(selection, NULL);
201
202 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model),
203 &iter, list->data))
204 {
205 gchar *title;
206 PurpleSavedStatus *saved_status;
207 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
208 STATUS_WINDOW_COLUMN_TITLE, &title,
209 -1);
210 saved_status = purple_savedstatus_find(title);
211 g_free(title);
212 purple_savedstatus_activate(saved_status);
213 }
214
215 g_list_free_full(list, (GDestroyNotify)gtk_tree_path_free);
216 }
217
218 static void
status_window_add_cb(GtkButton * button,gpointer user_data)219 status_window_add_cb(GtkButton *button, gpointer user_data)
220 {
221 pidgin_status_editor_show(FALSE, NULL);
222 }
223
224 static void
status_window_modify_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)225 status_window_modify_foreach(GtkTreeModel *model, GtkTreePath *path,
226 GtkTreeIter *iter, gpointer user_data)
227 {
228 gchar *title;
229 PurpleSavedStatus *saved_status;
230
231 gtk_tree_model_get(model, iter, STATUS_WINDOW_COLUMN_TITLE, &title, -1);
232 saved_status = purple_savedstatus_find(title);
233 g_free(title);
234 pidgin_status_editor_show(TRUE, saved_status);
235 }
236
237 static void
status_window_modify_cb(GtkButton * button,gpointer user_data)238 status_window_modify_cb(GtkButton *button, gpointer user_data)
239 {
240 StatusWindow *dialog = user_data;
241 GtkTreeSelection *selection;
242
243 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
244
245 gtk_tree_selection_selected_foreach(selection, status_window_modify_foreach, user_data);
246 }
247
248 static void
status_window_delete_cancel_cb(gpointer data)249 status_window_delete_cancel_cb(gpointer data)
250 {
251 GList *sel_titles = data;
252 g_list_free_full(sel_titles, (GDestroyNotify)g_free);
253 }
254
255 static void
status_window_delete_confirm_cb(gpointer data)256 status_window_delete_confirm_cb(gpointer data)
257 {
258 GtkTreeIter iter;
259 GList *sel_titles = data, *l;
260 char *title;
261
262 for (l = sel_titles; l != NULL; l = l->next) {
263 title = l->data;
264 if (purple_savedstatus_find(title) != purple_savedstatus_get_current()) {
265 if (status_window_find_savedstatus(&iter, title))
266 gtk_list_store_remove(status_window->model, &iter);
267 purple_savedstatus_delete(title);
268 }
269 g_free(title);
270 }
271 g_list_free(sel_titles);
272 }
273
274 static void
status_window_delete_cb(GtkButton * button,gpointer user_data)275 status_window_delete_cb(GtkButton *button, gpointer user_data)
276 {
277 StatusWindow *dialog = user_data;
278 GtkTreeIter iter;
279 GtkTreeSelection *selection;
280 GList *sel_paths, *l, *sel_titles = NULL;
281 GtkTreeModel *model = GTK_TREE_MODEL(dialog->model);
282 char *title;
283 gpointer handle;
284
285 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
286 sel_paths = gtk_tree_selection_get_selected_rows(selection, NULL);
287
288 /* This is ugly because we're not allowed to modify the model from within
289 * gtk_tree_selection_selected_foreach() and the GtkTreePaths can become invalid
290 * when something is removed from the model. The selection can also change while
291 * the request dialog is displayed, so we need to capture the selected rows at this time. */
292
293 for (l = sel_paths; l != NULL; l = l->next) {
294 if (gtk_tree_model_get_iter(model, &iter, l->data)) {
295 gtk_tree_model_get(model, &iter, STATUS_WINDOW_COLUMN_TITLE, &title, -1);
296 sel_titles = g_list_prepend(sel_titles, title);
297 }
298 gtk_tree_path_free(l->data);
299 }
300 g_list_free(sel_paths);
301
302 g_return_if_fail(sel_titles != NULL);
303 if (!sel_titles->next) {
304 title = g_strdup_printf(_("Are you sure you want to delete %s?"),
305 (const gchar *)sel_titles->data);
306 handle = purple_savedstatus_find(sel_titles->data);
307 } else {
308 title = g_strdup(_("Are you sure you want to delete the selected saved statuses?"));
309 handle = dialog;
310 }
311
312 purple_request_action(handle, NULL, title, NULL, 0,
313 NULL, NULL, NULL,
314 sel_titles, 2,
315 _("Delete"), status_window_delete_confirm_cb,
316 _("Cancel"), status_window_delete_cancel_cb);
317
318 g_free(title);
319 }
320
321 static void
status_window_close_cb(GtkButton * button,gpointer user_data)322 status_window_close_cb(GtkButton *button, gpointer user_data)
323 {
324 pidgin_status_window_hide();
325 }
326
327 static void
status_selected_cb(GtkTreeSelection * sel,gpointer user_data)328 status_selected_cb(GtkTreeSelection *sel, gpointer user_data)
329 {
330 StatusWindow *dialog = user_data;
331 GList *sel_paths, *tmp;
332 gboolean can_use = TRUE, can_delete = TRUE;
333 int num_selected;
334 GtkTreeModel *model = GTK_TREE_MODEL(dialog->model);
335
336 sel_paths = gtk_tree_selection_get_selected_rows(sel, NULL);
337
338 for (tmp = sel_paths, num_selected = 0; tmp; tmp = tmp->next, num_selected++) {
339 GtkTreeIter iter;
340 char *title;
341
342 if (gtk_tree_model_get_iter(model, &iter, tmp->data)) {
343 gtk_tree_model_get(model, &iter,
344 STATUS_WINDOW_COLUMN_TITLE, &title, -1);
345 if (purple_savedstatus_find(title) == purple_savedstatus_get_current()) {
346 can_use = can_delete = FALSE;
347 }
348
349 g_free(title);
350 }
351
352 gtk_tree_path_free(tmp->data);
353 }
354
355 gtk_widget_set_sensitive(dialog->use_button, (num_selected == 1) && can_use);
356 gtk_widget_set_sensitive(dialog->modify_button, (num_selected > 0));
357 gtk_widget_set_sensitive(dialog->delete_button, num_selected > 0 && can_delete);
358
359 g_list_free(sel_paths);
360 }
361
362 static const gchar *
get_stock_icon_from_primitive(PurpleStatusPrimitive type)363 get_stock_icon_from_primitive(PurpleStatusPrimitive type)
364 {
365 return pidgin_stock_id_from_status_primitive(type);
366 }
367
368 static void
add_status_to_saved_status_list(GtkListStore * model,PurpleSavedStatus * saved_status)369 add_status_to_saved_status_list(GtkListStore *model, PurpleSavedStatus *saved_status)
370 {
371 GtkTreeIter iter;
372 const char *title;
373 const char *type;
374 const gchar *icon;
375 char *message;
376
377 if (purple_savedstatus_is_transient(saved_status))
378 return;
379
380 title = purple_savedstatus_get_title(saved_status);
381 type = purple_primitive_get_name_from_type(purple_savedstatus_get_type(saved_status));
382 message = purple_markup_strip_html(purple_savedstatus_get_message(saved_status));
383 icon = get_stock_icon_from_primitive(purple_savedstatus_get_type(saved_status));
384
385 gtk_list_store_append(model, &iter);
386 gtk_list_store_set(model, &iter,
387 STATUS_WINDOW_COLUMN_ICON, icon,
388 STATUS_WINDOW_COLUMN_TITLE, title,
389 STATUS_WINDOW_COLUMN_TYPE, type,
390 STATUS_WINDOW_COLUMN_MESSAGE, message,
391 -1);
392 g_free(message);
393 }
394
395 static void
populate_saved_status_list(StatusWindow * dialog)396 populate_saved_status_list(StatusWindow *dialog)
397 {
398 GList *saved_statuses;
399
400 gtk_list_store_clear(dialog->model);
401
402 for (saved_statuses = purple_savedstatuses_get_all(); saved_statuses != NULL;
403 saved_statuses = g_list_next(saved_statuses))
404 {
405 add_status_to_saved_status_list(dialog->model, saved_statuses->data);
406 }
407 }
408
409 static gboolean
search_func(GtkTreeModel * model,gint column,const gchar * key,GtkTreeIter * iter,gpointer search_data)410 search_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data)
411 {
412 gboolean result;
413 char *haystack;
414
415 gtk_tree_model_get(model, iter, column, &haystack, -1);
416
417 result = (purple_strcasestr(haystack, key) == NULL);
418
419 g_free(haystack);
420
421 return result;
422 }
423
424 static void
savedstatus_activated_cb(GtkTreeView * view,GtkTreePath * path,GtkTreeViewColumn * column,StatusWindow * dialog)425 savedstatus_activated_cb(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, StatusWindow *dialog)
426 {
427 status_window_use_cb(NULL, dialog);
428 status_window_close_cb(NULL, dialog);
429 }
430
431 static void
saved_status_updated_cb(PurpleSavedStatus * status,StatusWindow * sw)432 saved_status_updated_cb(PurpleSavedStatus *status, StatusWindow *sw)
433 {
434 populate_saved_status_list(sw);
435 }
436
437 static GtkWidget *
create_saved_status_list(StatusWindow * dialog)438 create_saved_status_list(StatusWindow *dialog)
439 {
440 GtkWidget *treeview;
441 GtkTreeSelection *sel;
442 GtkTreeViewColumn *column;
443 GtkCellRenderer *renderer;
444
445 /* Create the list model */
446 dialog->model = gtk_list_store_new(STATUS_WINDOW_NUM_COLUMNS,
447 G_TYPE_STRING,
448 G_TYPE_STRING,
449 G_TYPE_STRING,
450 G_TYPE_POINTER,
451 G_TYPE_STRING);
452
453 /* Create the treeview */
454 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
455 dialog->treeview = treeview;
456 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
457 g_signal_connect(G_OBJECT(treeview), "row-activated",
458 G_CALLBACK(savedstatus_activated_cb), dialog);
459
460 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
461 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
462 g_signal_connect(G_OBJECT(sel), "changed",
463 G_CALLBACK(status_selected_cb), dialog);
464
465 /* Add columns */
466 column = gtk_tree_view_column_new();
467 gtk_tree_view_column_set_title(column, _("Title"));
468 gtk_tree_view_column_set_resizable(column, TRUE);
469 gtk_tree_view_column_set_min_width(column, 100);
470 gtk_tree_view_column_set_sort_column_id(column,
471 STATUS_WINDOW_COLUMN_TITLE);
472 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
473 renderer = gtk_cell_renderer_text_new();
474 gtk_tree_view_column_pack_start(column, renderer, TRUE);
475 gtk_tree_view_column_add_attribute(column, renderer, "text",
476 STATUS_WINDOW_COLUMN_TITLE);
477 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
478
479 column = gtk_tree_view_column_new();
480 gtk_tree_view_column_set_title(column, _("Type"));
481 gtk_tree_view_column_set_resizable(column, TRUE);
482 gtk_tree_view_column_set_sort_column_id(column,
483 STATUS_WINDOW_COLUMN_TYPE);
484 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
485 renderer = gtk_cell_renderer_pixbuf_new();
486 gtk_tree_view_column_pack_start(column, renderer, TRUE);
487 gtk_tree_view_column_add_attribute(column, renderer, "stock-id",
488 STATUS_WINDOW_COLUMN_ICON);
489 renderer = gtk_cell_renderer_text_new();
490 gtk_tree_view_column_pack_start(column, renderer, TRUE);
491 gtk_tree_view_column_add_attribute(column, renderer, "text",
492 STATUS_WINDOW_COLUMN_TYPE);
493
494 column = gtk_tree_view_column_new();
495 gtk_tree_view_column_set_title(column, _("Message"));
496 gtk_tree_view_column_set_resizable(column, TRUE);
497 gtk_tree_view_column_set_sort_column_id(column,
498 STATUS_WINDOW_COLUMN_MESSAGE);
499 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
500 renderer = gtk_cell_renderer_text_new();
501 gtk_tree_view_column_pack_start(column, renderer, TRUE);
502 gtk_tree_view_column_add_attribute(column, renderer, "text",
503 STATUS_WINDOW_COLUMN_MESSAGE);
504 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
505
506 /* Enable CTRL+F searching */
507 gtk_tree_view_set_search_column(GTK_TREE_VIEW(treeview), STATUS_WINDOW_COLUMN_TITLE);
508 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(treeview), search_func, NULL, NULL);
509
510 /* Sort the title column by default */
511 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dialog->model),
512 STATUS_WINDOW_COLUMN_TITLE,
513 GTK_SORT_ASCENDING);
514
515 /* Populate list */
516 populate_saved_status_list(dialog);
517
518 gtk_widget_show_all(treeview);
519
520 return pidgin_make_scrollable(treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1);
521 }
522
523 static gboolean
configure_cb(GtkWidget * widget,GdkEventConfigure * event,StatusWindow * dialog)524 configure_cb(GtkWidget *widget, GdkEventConfigure *event, StatusWindow *dialog)
525 {
526 if (GTK_WIDGET_VISIBLE(widget))
527 {
528 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/status/dialog/width", event->width);
529 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/status/dialog/height", event->height);
530 }
531
532 return FALSE;
533 }
534
535 static void
current_status_changed(PurpleSavedStatus * old,PurpleSavedStatus * new_status,StatusWindow * dialog)536 current_status_changed(PurpleSavedStatus *old, PurpleSavedStatus *new_status,
537 StatusWindow *dialog)
538 {
539 status_selected_cb(gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview)), dialog);
540 }
541
542 void
pidgin_status_window_show(void)543 pidgin_status_window_show(void)
544 {
545 StatusWindow *dialog;
546 GtkWidget *bbox;
547 GtkWidget *button;
548 GtkWidget *list;
549 GtkWidget *vbox;
550 GtkWidget *win;
551 int width, height;
552
553 if (status_window != NULL)
554 {
555 gtk_window_present(GTK_WINDOW(status_window->window));
556 return;
557 }
558
559 status_window = dialog = g_new0(StatusWindow, 1);
560
561 width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/width");
562 height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/height");
563
564 dialog->window = win = pidgin_create_dialog(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE);
565 gtk_window_set_default_size(GTK_WINDOW(win), width, height);
566
567 g_signal_connect(G_OBJECT(win), "delete_event",
568 G_CALLBACK(status_window_destroy_cb), dialog);
569 g_signal_connect(G_OBJECT(win), "configure_event",
570 G_CALLBACK(configure_cb), dialog);
571
572 /* Setup the vbox */
573 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
574
575 /* List of saved status states */
576 list = create_saved_status_list(dialog);
577 gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0);
578
579 /* Button box. */
580 bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win));
581
582 /* Use button */
583 button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE,
584 PIDGIN_BUTTON_HORIZONTAL);
585 dialog->use_button = button;
586 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
587 gtk_widget_set_sensitive(button, FALSE);
588
589 g_signal_connect(G_OBJECT(button), "clicked",
590 G_CALLBACK(status_window_use_cb), dialog);
591
592 /* Add button */
593 pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_ADD,
594 G_CALLBACK(status_window_add_cb), dialog);
595
596 /* Modify button */
597 button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY,
598 G_CALLBACK(status_window_modify_cb), dialog);
599 dialog->modify_button = button;
600 gtk_widget_set_sensitive(button, FALSE);
601
602 /* Delete button */
603 button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE,
604 G_CALLBACK(status_window_delete_cb), dialog);
605 dialog->delete_button = button;
606 gtk_widget_set_sensitive(button, FALSE);
607
608 /* Close button */
609 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE,
610 G_CALLBACK(status_window_close_cb), dialog);
611
612 purple_signal_connect(purple_savedstatuses_get_handle(),
613 "savedstatus-changed", status_window,
614 PURPLE_CALLBACK(current_status_changed), dialog);
615 purple_signal_connect(purple_savedstatuses_get_handle(),
616 "savedstatus-added", status_window,
617 PURPLE_CALLBACK(saved_status_updated_cb), dialog);
618 purple_signal_connect(purple_savedstatuses_get_handle(),
619 "savedstatus-deleted", status_window,
620 PURPLE_CALLBACK(saved_status_updated_cb), dialog);
621 purple_signal_connect(purple_savedstatuses_get_handle(),
622 "savedstatus-modified", status_window,
623 PURPLE_CALLBACK(saved_status_updated_cb), dialog);
624
625 gtk_widget_show_all(win);
626 }
627
628 void
pidgin_status_window_hide(void)629 pidgin_status_window_hide(void)
630 {
631 if (status_window == NULL)
632 return;
633
634 if (status_window->window != NULL)
635 gtk_widget_destroy(status_window->window);
636
637 purple_request_close_with_handle(status_window);
638 purple_notify_close_with_handle(status_window);
639 purple_signals_disconnect_by_handle(status_window);
640 g_object_unref(G_OBJECT(status_window->model));
641 g_free(status_window);
642 status_window = NULL;
643 }
644
645
646 /**************************************************************************
647 * Status editor
648 **************************************************************************/
649
650 static void substatus_editor_cancel_cb(GtkButton *button, gpointer user_data);
651
652 static void
status_editor_remove_dialog(StatusEditor * dialog)653 status_editor_remove_dialog(StatusEditor *dialog)
654 {
655 GtkTreeModel *model;
656 GtkTreeIter iter;
657
658 /* Remove the reference to this dialog from our parent's list store */
659 if (status_window_find_savedstatus(&iter, dialog->original_title))
660 {
661 gtk_list_store_set(status_window->model, &iter,
662 STATUS_WINDOW_COLUMN_WINDOW, NULL,
663 -1);
664 }
665
666 /* Close any substatus editors that may be open */
667 model = GTK_TREE_MODEL(dialog->model);
668 if (gtk_tree_model_get_iter_first(model, &iter))
669 {
670 do {
671 SubStatusEditor *substatus_dialog;
672
673 gtk_tree_model_get(model, &iter,
674 STATUS_EDITOR_COLUMN_WINDOW, &substatus_dialog,
675 -1);
676 if (substatus_dialog != NULL)
677 {
678 gtk_list_store_set(dialog->model, &iter,
679 STATUS_EDITOR_COLUMN_WINDOW, NULL,
680 -1);
681 substatus_editor_cancel_cb(NULL, substatus_dialog);
682 }
683 } while (gtk_tree_model_iter_next(model, &iter));
684 }
685 }
686
687
688 static void
status_editor_destroy_cb(GtkWidget * widget,gpointer user_data)689 status_editor_destroy_cb(GtkWidget *widget, gpointer user_data)
690 {
691 StatusEditor *dialog = user_data;
692
693 status_editor_remove_dialog(dialog);
694 g_free(dialog->original_title);
695 g_object_unref(G_OBJECT(dialog->model));
696 g_free(dialog);
697 }
698
699 static void
status_editor_cancel_cb(GtkButton * button,gpointer user_data)700 status_editor_cancel_cb(GtkButton *button, gpointer user_data)
701 {
702 StatusEditor *dialog = user_data;
703 gtk_widget_destroy(dialog->window);
704 }
705
706 static void
status_editor_ok_cb(GtkButton * button,gpointer user_data)707 status_editor_ok_cb(GtkButton *button, gpointer user_data)
708 {
709 StatusEditor *dialog = user_data;
710 const char *title;
711 PurpleStatusPrimitive type;
712 char *message, *unformatted;
713 PurpleSavedStatus *saved_status = NULL;
714 GtkTreeModel *model;
715 GtkTreeIter iter;
716
717 title = gtk_entry_get_text(dialog->title);
718
719 /*
720 * If we're saving this status, and the title is already taken
721 * then show an error dialog and don't do anything.
722 */
723 if (((button == dialog->saveanduse_button) || (button == dialog->save_button)) &&
724 (purple_savedstatus_find(title) != NULL) &&
725 ((dialog->original_title == NULL) || (!purple_strequal(title, dialog->original_title))))
726 {
727 purple_notify_error(status_window, NULL, _("Title already in use. You must "
728 "choose a unique title."), NULL);
729 return;
730 }
731
732 type = gtk_combo_box_get_active(dialog->type) + (PURPLE_STATUS_UNSET + 1);
733 message = gtk_imhtml_get_markup(dialog->message);
734 unformatted = purple_markup_strip_html(message);
735
736 /*
737 * If we're editing an old status, then lookup the old status.
738 * Note: It is possible that it has been deleted or renamed
739 * or something, and no longer exists.
740 */
741 if (dialog->original_title != NULL)
742 {
743 GtkTreeIter iter;
744
745 saved_status = purple_savedstatus_find(dialog->original_title);
746
747 if (status_window_find_savedstatus(&iter, dialog->original_title))
748 gtk_list_store_remove(status_window->model, &iter);
749 }
750
751 if (saved_status == NULL)
752 {
753 /* This is a new status */
754 if ((button == dialog->saveanduse_button)
755 || (button == dialog->save_button))
756 saved_status = purple_savedstatus_new(title, type);
757 else
758 saved_status = purple_savedstatus_new(NULL, type);
759 }
760 else
761 {
762 /* Modify the old status */
763 if (!purple_strequal(title, dialog->original_title))
764 purple_savedstatus_set_title(saved_status, title);
765 purple_savedstatus_set_type(saved_status, type);
766 }
767
768 if (*unformatted == '\0')
769 purple_savedstatus_set_message(saved_status, NULL);
770 else
771 purple_savedstatus_set_message(saved_status, message);
772
773 /* Set any substatuses */
774 model = GTK_TREE_MODEL(dialog->model);
775 if (gtk_tree_model_get_iter_first(model, &iter))
776 {
777 do {
778 PurpleAccount *account;
779 gboolean enabled;
780 char *id;
781 char *message;
782 PurpleStatusType *type;
783
784 gtk_tree_model_get(model, &iter,
785 STATUS_EDITOR_COLUMN_ACCOUNT, &account,
786 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, &enabled,
787 STATUS_EDITOR_COLUMN_STATUS_ID, &id,
788 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, &message,
789 -1);
790 if (enabled)
791 {
792 type = purple_account_get_status_type(account, id);
793 purple_savedstatus_set_substatus(saved_status, account, type, message);
794 }
795 else
796 {
797 purple_savedstatus_unset_substatus(saved_status, account);
798 }
799 g_free(id);
800 g_free(message);
801 } while (gtk_tree_model_iter_next(model, &iter));
802 }
803
804 g_free(message);
805 g_free(unformatted);
806
807 /* If they clicked on "Save and Use" or "Use," then activate the status */
808 if (button != dialog->save_button)
809 purple_savedstatus_activate(saved_status);
810
811 gtk_widget_destroy(dialog->window);
812 }
813
814 static void
editor_title_changed_cb(GtkWidget * widget,gpointer user_data)815 editor_title_changed_cb(GtkWidget *widget, gpointer user_data)
816 {
817 StatusEditor *dialog = user_data;
818 const gchar *text;
819
820 text = gtk_entry_get_text(dialog->title);
821
822 gtk_widget_set_sensitive(GTK_WIDGET(dialog->saveanduse_button), (*text != '\0'));
823 gtk_widget_set_sensitive(GTK_WIDGET(dialog->save_button), (*text != '\0'));
824 }
825
826 static GtkWidget *
create_status_type_menu(PurpleStatusPrimitive type)827 create_status_type_menu(PurpleStatusPrimitive type)
828 {
829 int i;
830 GtkWidget *dropdown;
831 GtkListStore *store;
832 GtkTreeIter iter;
833 GtkCellRenderer *renderer;
834
835 store = gtk_list_store_new(STATUS_NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
836
837 for (i = PURPLE_STATUS_UNSET + 1; i < PURPLE_STATUS_NUM_PRIMITIVES; i++)
838 {
839 /* Someone should fix this for 3.0.0. The independent boolean
840 * should probably be set on the status type, not the status.
841 * I guess that would prevent third party plugins from creating
842 * independent statuses?
843 */
844 if (i == PURPLE_STATUS_MOBILE ||
845 i == PURPLE_STATUS_MOOD ||
846 i == PURPLE_STATUS_TUNE)
847 /*
848 * Special-case these. They're intended to be independent
849 * status types, so don't show them in the list.
850 */
851 continue;
852
853 gtk_list_store_append(store, &iter);
854 gtk_list_store_set(store, &iter,
855 STATUS_COLUMN_ICON, get_stock_icon_from_primitive(i),
856 STATUS_COLUMN_STATUS_ID, purple_primitive_get_id_from_type(i),
857 STATUS_COLUMN_STATUS_NAME, purple_primitive_get_name_from_type(i),
858 -1);
859 }
860
861 dropdown = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
862
863 renderer = gtk_cell_renderer_pixbuf_new();
864 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(dropdown), renderer, FALSE);
865 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(dropdown), renderer,
866 "stock-id", STATUS_COLUMN_ICON,
867 NULL);
868
869 renderer = gtk_cell_renderer_text_new();
870 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(dropdown), renderer, TRUE);
871 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(dropdown), renderer,
872 "text", STATUS_COLUMN_STATUS_NAME,
873 NULL);
874
875 gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown),
876 type - (PURPLE_STATUS_UNSET + 1));
877
878 return dropdown;
879 }
880
881 static void edit_substatus(StatusEditor *status_editor, PurpleAccount *account);
882
883 static void
edit_substatus_cb(GtkTreeView * tv,GtkTreePath * path,GtkTreeViewColumn * col,gpointer user_data)884 edit_substatus_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col, gpointer user_data)
885 {
886 StatusEditor *dialog = user_data;
887 GtkTreeIter iter;
888 PurpleAccount *account;
889
890 gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path);
891 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
892 STATUS_EDITOR_COLUMN_ACCOUNT, &account,
893 -1);
894
895 edit_substatus(dialog, account);
896 }
897
898 static void
status_editor_substatus_cb(GtkCellRendererToggle * renderer,gchar * path_str,gpointer data)899 status_editor_substatus_cb(GtkCellRendererToggle *renderer, gchar *path_str, gpointer data)
900 {
901 StatusEditor *dialog = (StatusEditor *)data;
902 GtkTreeIter iter;
903 gboolean enabled;
904 PurpleAccount *account;
905
906 gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(dialog->model), &iter, path_str);
907 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
908 STATUS_EDITOR_COLUMN_ACCOUNT, &account,
909 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, &enabled,
910 -1);
911
912 enabled = !enabled;
913
914 if (enabled)
915 {
916 edit_substatus(dialog, account);
917 }
918 else
919 {
920 /* Remove the substatus */
921 gtk_list_store_set(dialog->model, &iter,
922 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, enabled,
923 STATUS_EDITOR_COLUMN_STATUS_ID, NULL,
924 STATUS_EDITOR_COLUMN_STATUS_NAME, NULL,
925 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, NULL,
926 STATUS_EDITOR_COLUMN_STATUS_ICON, NULL,
927 -1);
928 }
929 }
930
931 static void
status_editor_add_columns(StatusEditor * dialog)932 status_editor_add_columns(StatusEditor *dialog)
933 {
934 GtkCellRenderer *renderer;
935 GtkTreeViewColumn *column;
936
937 /* Enable Different status column */
938 renderer = gtk_cell_renderer_toggle_new();
939 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(dialog->treeview),
940 -1, _("Different"),
941 renderer,
942 "active", STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS,
943 NULL);
944 g_signal_connect(G_OBJECT(renderer), "toggled",
945 G_CALLBACK(status_editor_substatus_cb), dialog);
946
947 /* Username column */
948 column = gtk_tree_view_column_new();
949 gtk_tree_view_column_set_resizable(column, TRUE);
950 gtk_tree_view_column_set_title(column, _("Username"));
951 gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
952 gtk_tree_view_column_set_resizable(column, TRUE);
953
954 /* Icon */
955 renderer = gtk_cell_renderer_pixbuf_new();
956 gtk_tree_view_column_pack_start(column, renderer, FALSE);
957 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf",
958 STATUS_EDITOR_COLUMN_ICON);
959
960 /* Username */
961 renderer = gtk_cell_renderer_text_new();
962 gtk_tree_view_column_pack_start(column, renderer, TRUE);
963 gtk_tree_view_column_add_attribute(column, renderer, "text",
964 STATUS_EDITOR_COLUMN_USERNAME);
965
966 /* Status column */
967 column = gtk_tree_view_column_new();
968 gtk_tree_view_column_set_resizable(column, TRUE);
969 gtk_tree_view_column_set_title(column, _("Status"));
970 gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
971 gtk_tree_view_column_set_resizable(column, TRUE);
972 renderer = gtk_cell_renderer_pixbuf_new();
973 gtk_tree_view_column_pack_start(column, renderer, FALSE);
974 gtk_tree_view_column_add_attribute(column, renderer, "stock-id",
975 STATUS_EDITOR_COLUMN_STATUS_ICON);
976 renderer = gtk_cell_renderer_text_new();
977 gtk_tree_view_column_pack_start(column, renderer, TRUE);
978 gtk_tree_view_column_add_attribute(column, renderer, "text",
979 STATUS_EDITOR_COLUMN_STATUS_NAME);
980
981 /* Message column */
982 column = gtk_tree_view_column_new();
983 gtk_tree_view_column_set_resizable(column, TRUE);
984 gtk_tree_view_column_set_title(column, _("Message"));
985 gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
986 gtk_tree_view_column_set_resizable(column, TRUE);
987 renderer = gtk_cell_renderer_text_new();
988 gtk_tree_view_column_pack_start(column, renderer, TRUE);
989 gtk_tree_view_column_add_attribute(column, renderer, "text",
990 STATUS_EDITOR_COLUMN_STATUS_MESSAGE);
991
992 g_signal_connect(G_OBJECT(dialog->treeview), "row-activated",
993 G_CALLBACK(edit_substatus_cb), dialog);
994 }
995
996 static void
status_editor_set_account(GtkListStore * store,PurpleAccount * account,GtkTreeIter * iter,PurpleSavedStatusSub * substatus)997 status_editor_set_account(GtkListStore *store, PurpleAccount *account,
998 GtkTreeIter *iter, PurpleSavedStatusSub *substatus)
999 {
1000 GdkPixbuf *pixbuf;
1001 const char *id = NULL, *name = NULL, *message = NULL;
1002 PurpleStatusPrimitive prim = PURPLE_STATUS_UNSET;
1003
1004 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_MEDIUM);
1005 if ((pixbuf != NULL) && !purple_account_is_connected(account))
1006 {
1007 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
1008 }
1009
1010 if (substatus != NULL)
1011 {
1012 const PurpleStatusType *type;
1013
1014 type = purple_savedstatus_substatus_get_type(substatus);
1015 id = purple_status_type_get_id(type);
1016 name = purple_status_type_get_name(type);
1017 prim = purple_status_type_get_primitive(type);
1018 if (purple_status_type_get_attr(type, "message"))
1019 message = purple_savedstatus_substatus_get_message(substatus);
1020 }
1021
1022 gtk_list_store_set(store, iter,
1023 STATUS_EDITOR_COLUMN_ACCOUNT, account,
1024 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, (substatus != NULL),
1025 STATUS_EDITOR_COLUMN_ICON, pixbuf,
1026 STATUS_EDITOR_COLUMN_USERNAME, purple_account_get_username(account),
1027 STATUS_EDITOR_COLUMN_STATUS_ID, id,
1028 STATUS_EDITOR_COLUMN_STATUS_NAME, name,
1029 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, message,
1030 STATUS_EDITOR_COLUMN_STATUS_ICON, get_stock_icon_from_primitive(prim),
1031 -1);
1032
1033 if (pixbuf != NULL)
1034 g_object_unref(G_OBJECT(pixbuf));
1035 }
1036
1037 static void
status_editor_add_account(StatusEditor * dialog,PurpleAccount * account,PurpleSavedStatusSub * substatus)1038 status_editor_add_account(StatusEditor *dialog, PurpleAccount *account,
1039 PurpleSavedStatusSub *substatus)
1040 {
1041 GtkTreeIter iter;
1042
1043 gtk_list_store_append(dialog->model, &iter);
1044
1045 status_editor_set_account(dialog->model, account, &iter, substatus);
1046 }
1047
1048 static void
status_editor_populate_list(StatusEditor * dialog,PurpleSavedStatus * saved_status)1049 status_editor_populate_list(StatusEditor *dialog, PurpleSavedStatus *saved_status)
1050 {
1051 GList *iter;
1052 PurpleSavedStatusSub *substatus;
1053
1054 gtk_list_store_clear(dialog->model);
1055
1056 for (iter = purple_accounts_get_all(); iter != NULL; iter = iter->next)
1057 {
1058 PurpleAccount *account = (PurpleAccount *)iter->data;
1059
1060 if (saved_status != NULL)
1061 substatus = purple_savedstatus_get_substatus(saved_status, account);
1062 else
1063 substatus = NULL;
1064
1065 status_editor_add_account(dialog, account, substatus);
1066 }
1067 }
1068
1069 void
pidgin_status_editor_show(gboolean edit,PurpleSavedStatus * saved_status)1070 pidgin_status_editor_show(gboolean edit, PurpleSavedStatus *saved_status)
1071 {
1072 GtkTreeIter iter;
1073 StatusEditor *dialog;
1074 GtkSizeGroup *sg;
1075 GtkWidget *bbox;
1076 GtkWidget *button;
1077 GtkWidget *dbox;
1078 GtkWidget *expander;
1079 GtkWidget *dropdown;
1080 GtkWidget *entry;
1081 GtkWidget *frame;
1082 GtkWidget *hbox;
1083 GtkWidget *text;
1084 GtkWidget *toolbar;
1085 GtkWidget *vbox;
1086 GtkWidget *win;
1087 GList *focus_chain = NULL;
1088
1089 if (edit)
1090 {
1091 g_return_if_fail(saved_status != NULL);
1092 g_return_if_fail(!purple_savedstatus_is_transient(saved_status));
1093 }
1094
1095 /* Find a possible window for this saved status and present it */
1096 if (edit && status_window_find_savedstatus(&iter, purple_savedstatus_get_title(saved_status)))
1097 {
1098 gtk_tree_model_get(GTK_TREE_MODEL(status_window->model), &iter,
1099 STATUS_WINDOW_COLUMN_WINDOW, &dialog,
1100 -1);
1101 if (dialog != NULL)
1102 {
1103 gtk_window_present(GTK_WINDOW(dialog->window));
1104 return;
1105 }
1106 }
1107
1108 dialog = g_new0(StatusEditor, 1);
1109 if (edit && status_window_find_savedstatus(&iter, purple_savedstatus_get_title(saved_status)))
1110 {
1111 gtk_list_store_set(status_window->model, &iter,
1112 STATUS_WINDOW_COLUMN_WINDOW, dialog,
1113 -1);
1114 }
1115
1116 if (edit)
1117 dialog->original_title = g_strdup(purple_savedstatus_get_title(saved_status));
1118
1119 dialog->window = win = pidgin_create_dialog(_("Status"), PIDGIN_HIG_BORDER, "status", TRUE);
1120
1121 g_signal_connect(G_OBJECT(win), "destroy",
1122 G_CALLBACK(status_editor_destroy_cb), dialog);
1123
1124 /* Setup the vbox */
1125 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
1126
1127 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1128
1129 /* Title */
1130 entry = gtk_entry_new();
1131 dialog->title = GTK_ENTRY(entry);
1132 if ((saved_status != NULL)
1133 && !purple_savedstatus_is_transient(saved_status)
1134 && (purple_savedstatus_get_title(saved_status) != NULL))
1135 gtk_entry_set_text(GTK_ENTRY(entry), purple_savedstatus_get_title(saved_status));
1136 g_signal_connect(G_OBJECT(entry), "changed",
1137 G_CALLBACK(editor_title_changed_cb), dialog);
1138 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Title:"), sg, entry, TRUE, NULL);
1139
1140 /* Status type */
1141 if (saved_status != NULL)
1142 dropdown = create_status_type_menu(purple_savedstatus_get_type(saved_status));
1143 else
1144 dropdown = create_status_type_menu(PURPLE_STATUS_AWAY);
1145 dialog->type = GTK_COMBO_BOX(dropdown);
1146 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Status:"), sg, dropdown, TRUE, NULL);
1147
1148 /* Status message */
1149 frame = pidgin_create_imhtml(TRUE, &text, &toolbar, NULL);
1150 dialog->message = GTK_IMHTML(text);
1151 hbox = pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Message:"), sg, frame, TRUE, NULL);
1152 gtk_container_child_set(GTK_CONTAINER(vbox), hbox, "expand", TRUE, "fill", TRUE, NULL);
1153 focus_chain = g_list_prepend(focus_chain, dialog->message);
1154 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), focus_chain);
1155 g_list_free(focus_chain);
1156
1157 gtk_imhtml_set_return_inserts_newline(dialog->message);
1158
1159 if ((saved_status != NULL) && (purple_savedstatus_get_message(saved_status) != NULL))
1160 gtk_imhtml_append_text(GTK_IMHTML(text),
1161 purple_savedstatus_get_message(saved_status), 0);
1162
1163 /* Different status message expander */
1164 expander = gtk_expander_new_with_mnemonic(_("Use a _different status for some accounts"));
1165 gtk_box_pack_start(GTK_BOX(vbox), expander, FALSE, FALSE, 0);
1166
1167 /* Setup the box that the expander will cover */
1168 dbox = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
1169 gtk_container_add(GTK_CONTAINER(expander), dbox);
1170
1171 /* Create the list model */
1172 dialog->model = gtk_list_store_new(STATUS_EDITOR_NUM_COLUMNS,
1173 G_TYPE_POINTER,
1174 G_TYPE_POINTER,
1175 G_TYPE_BOOLEAN,
1176 GDK_TYPE_PIXBUF,
1177 G_TYPE_STRING,
1178 G_TYPE_STRING,
1179 G_TYPE_STRING,
1180 G_TYPE_STRING,
1181 G_TYPE_STRING);
1182
1183 /* Create the treeview */
1184 dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
1185 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(dialog->treeview), TRUE);
1186 gtk_widget_set_size_request(dialog->treeview, -1, 150);
1187 gtk_box_pack_start(GTK_BOX(dbox),
1188 pidgin_make_scrollable(dialog->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1189 TRUE, TRUE, 0);
1190
1191 /* Add columns */
1192 status_editor_add_columns(dialog);
1193
1194 /* Populate list */
1195 status_editor_populate_list(dialog, saved_status);
1196
1197 /* Expand the treeview if we have substatuses */
1198 gtk_expander_set_expanded(GTK_EXPANDER(expander),
1199 (saved_status != NULL) && purple_savedstatus_has_substatuses(saved_status));
1200
1201 /* Button box */
1202 bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win));
1203 gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
1204 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
1205
1206 /* Cancel button */
1207 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL,
1208 G_CALLBACK(status_editor_cancel_cb), dialog);
1209
1210 /* Use button */
1211 button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE,
1212 PIDGIN_BUTTON_HORIZONTAL);
1213 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1214 g_signal_connect(G_OBJECT(button), "clicked",
1215 G_CALLBACK(status_editor_ok_cb), dialog);
1216
1217 /* Save and Use button */
1218 button = pidgin_pixbuf_button_from_stock(_("Sa_ve and Use"), GTK_STOCK_OK,
1219 PIDGIN_BUTTON_HORIZONTAL);
1220 dialog->saveanduse_button = GTK_BUTTON(button);
1221 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1222 if (dialog->original_title == NULL)
1223 gtk_widget_set_sensitive(button, FALSE);
1224 g_signal_connect(G_OBJECT(button), "clicked",
1225 G_CALLBACK(status_editor_ok_cb), dialog);
1226
1227 /* Save button */
1228 button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_SAVE,
1229 G_CALLBACK(status_editor_ok_cb), dialog);
1230 if (dialog->original_title == NULL)
1231 gtk_widget_set_sensitive(button, FALSE);
1232 dialog->save_button = GTK_BUTTON(button);
1233
1234 gtk_widget_show_all(win);
1235 g_object_unref(sg);
1236 }
1237
1238
1239 /**************************************************************************
1240 * SubStatus editor
1241 **************************************************************************/
1242
1243 static void
substatus_selection_changed_cb(GtkComboBox * box,gpointer user_data)1244 substatus_selection_changed_cb(GtkComboBox *box, gpointer user_data)
1245 {
1246 SubStatusEditor *select = user_data;
1247 GtkTreeIter iter;
1248 char *id;
1249 PurpleStatusType *type;
1250
1251 if (!gtk_combo_box_get_active_iter(box, &iter))
1252 return;
1253 gtk_tree_model_get(GTK_TREE_MODEL(select->model), &iter,
1254 STATUS_COLUMN_STATUS_ID, &id,
1255 -1);
1256 type = purple_account_get_status_type(select->account, id);
1257 g_free(id);
1258
1259 if (purple_status_type_get_attr(type, "message") == NULL)
1260 {
1261 gtk_widget_set_sensitive(GTK_WIDGET(select->message), FALSE);
1262 gtk_widget_set_sensitive(GTK_WIDGET(select->toolbar), FALSE);
1263 }
1264 else
1265 {
1266 gtk_widget_set_sensitive(GTK_WIDGET(select->message), TRUE);
1267 gtk_widget_set_sensitive(GTK_WIDGET(select->toolbar), TRUE);
1268 }
1269 }
1270
1271 static gboolean
status_editor_find_account_in_treemodel(GtkTreeIter * iter,StatusEditor * status_editor,PurpleAccount * account)1272 status_editor_find_account_in_treemodel(GtkTreeIter *iter,
1273 StatusEditor *status_editor,
1274 PurpleAccount *account)
1275 {
1276 GtkTreeModel *model;
1277 PurpleAccount *cur;
1278
1279 g_return_val_if_fail(status_editor != NULL, FALSE);
1280 g_return_val_if_fail(account != NULL, FALSE);
1281
1282 model = GTK_TREE_MODEL(status_editor->model);
1283
1284 if (!gtk_tree_model_get_iter_first(model, iter))
1285 return FALSE;
1286
1287 do {
1288 gtk_tree_model_get(model, iter, STATUS_EDITOR_COLUMN_ACCOUNT, &cur, -1);
1289 if (cur == account)
1290 return TRUE;
1291 } while (gtk_tree_model_iter_next(model, iter));
1292
1293 return FALSE;
1294 }
1295
1296 static void
substatus_editor_remove_dialog(SubStatusEditor * dialog)1297 substatus_editor_remove_dialog(SubStatusEditor *dialog)
1298 {
1299 GtkTreeIter iter;
1300
1301 if (status_editor_find_account_in_treemodel(&iter, dialog->status_editor, dialog->account))
1302 {
1303 /* Remove the reference to this dialog from our parent's list store */
1304 gtk_list_store_set(dialog->status_editor->model, &iter,
1305 STATUS_EDITOR_COLUMN_WINDOW, NULL,
1306 -1);
1307 }
1308 }
1309
1310 static void
substatus_editor_destroy_cb(GtkWidget * widget,gpointer user_data)1311 substatus_editor_destroy_cb(GtkWidget *widget, gpointer user_data)
1312 {
1313 SubStatusEditor *dialog = user_data;
1314
1315 substatus_editor_remove_dialog(dialog);
1316 g_free(dialog);
1317 }
1318
1319 static void
substatus_editor_cancel_cb(GtkButton * button,gpointer user_data)1320 substatus_editor_cancel_cb(GtkButton *button, gpointer user_data)
1321 {
1322 SubStatusEditor *dialog = user_data;
1323 gtk_widget_destroy(dialog->window);
1324 }
1325
1326
1327 static void
substatus_editor_ok_cb(GtkButton * button,gpointer user_data)1328 substatus_editor_ok_cb(GtkButton *button, gpointer user_data)
1329 {
1330 SubStatusEditor *dialog = user_data;
1331 StatusEditor *status_editor;
1332 GtkTreeIter iter;
1333 PurpleStatusType *type;
1334 char *id = NULL;
1335 char *message = NULL;
1336 const char *name = NULL, *stock = NULL;
1337
1338 if (!gtk_combo_box_get_active_iter(dialog->box, &iter))
1339 {
1340 gtk_widget_destroy(dialog->window);
1341 return;
1342 }
1343
1344 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
1345 STATUS_COLUMN_STATUS_ID, &id,
1346 -1);
1347 type = purple_account_get_status_type(dialog->account, id);
1348 if (purple_status_type_get_attr(type, "message") != NULL)
1349 message = gtk_imhtml_get_markup(GTK_IMHTML(dialog->message));
1350 name = purple_status_type_get_name(type);
1351 stock = get_stock_icon_from_primitive(purple_status_type_get_primitive(type));
1352
1353 status_editor = dialog->status_editor;
1354
1355 if (status_editor_find_account_in_treemodel(&iter, status_editor, dialog->account))
1356 {
1357 gtk_list_store_set(status_editor->model, &iter,
1358 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, TRUE,
1359 STATUS_EDITOR_COLUMN_STATUS_ID, id,
1360 STATUS_EDITOR_COLUMN_STATUS_NAME, name,
1361 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, message,
1362 STATUS_EDITOR_COLUMN_WINDOW, NULL,
1363 STATUS_EDITOR_COLUMN_STATUS_ICON, stock,
1364 -1);
1365 }
1366
1367 gtk_widget_destroy(dialog->window);
1368 g_free(id);
1369 g_free(message);
1370 }
1371
1372 static void
edit_substatus(StatusEditor * status_editor,PurpleAccount * account)1373 edit_substatus(StatusEditor *status_editor, PurpleAccount *account)
1374 {
1375 char *tmp;
1376 SubStatusEditor *dialog;
1377 GtkSizeGroup *sg;
1378 GtkWidget *combo;
1379 GtkWidget *hbox;
1380 GtkWidget *frame;
1381 GtkWidget *label;
1382 GtkWidget *text;
1383 GtkWidget *toolbar;
1384 GtkWidget *vbox;
1385 GtkWidget *win;
1386 GtkTreeIter iter;
1387 GtkCellRenderer *rend;
1388 char *status_id = NULL;
1389 char *message = NULL;
1390 gboolean parent_dialog_has_substatus = FALSE;
1391 GList *list;
1392 gboolean select = FALSE;
1393
1394 g_return_if_fail(status_editor != NULL);
1395 g_return_if_fail(account != NULL);
1396
1397 status_editor_find_account_in_treemodel(&iter, status_editor, account);
1398 gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter,
1399 STATUS_EDITOR_COLUMN_WINDOW, &dialog,
1400 -1);
1401 if (dialog != NULL)
1402 {
1403 gtk_window_present(GTK_WINDOW(dialog->window));
1404 return;
1405 }
1406
1407 dialog = g_new0(SubStatusEditor, 1);
1408 gtk_list_store_set(status_editor->model, &iter,
1409 STATUS_EDITOR_COLUMN_WINDOW, dialog,
1410 -1);
1411 dialog->status_editor = status_editor;
1412 dialog->account = account;
1413
1414 tmp = g_strdup_printf(_("Status for %s"), purple_account_get_username(account));
1415 dialog->window = win = pidgin_create_dialog(tmp, PIDGIN_HIG_BORDER, "substatus", TRUE);
1416 g_free(tmp);
1417
1418 g_signal_connect(G_OBJECT(win), "destroy",
1419 G_CALLBACK(substatus_editor_destroy_cb), dialog);
1420
1421 /* Setup the vbox */
1422 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
1423
1424 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1425
1426 /* Status type */
1427 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
1428 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1429
1430 label = gtk_label_new_with_mnemonic(_("_Status:"));
1431 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1432 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1433 gtk_size_group_add_widget(sg, label);
1434
1435 dialog->model = gtk_list_store_new(STATUS_NUM_COLUMNS,
1436 G_TYPE_STRING,
1437 G_TYPE_STRING,
1438 G_TYPE_STRING);
1439 combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(dialog->model));
1440 dialog->box = GTK_COMBO_BOX(combo);
1441
1442 rend = GTK_CELL_RENDERER(gtk_cell_renderer_pixbuf_new());
1443 g_object_set(G_OBJECT(rend),
1444 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL),
1445 NULL);
1446 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), rend, FALSE);
1447 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), rend,
1448 "stock-id", STATUS_COLUMN_ICON, NULL);
1449
1450 rend = GTK_CELL_RENDERER(gtk_cell_renderer_text_new());
1451 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), rend, TRUE);
1452 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), rend,
1453 "text", STATUS_COLUMN_STATUS_NAME, NULL);
1454
1455 g_signal_connect(G_OBJECT(combo), "changed",
1456 G_CALLBACK(substatus_selection_changed_cb), dialog);
1457
1458 gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 0);
1459
1460 /* Status mesage */
1461 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
1462 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1463
1464 label = gtk_label_new_with_mnemonic(_("_Message:"));
1465 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1466 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1467 gtk_size_group_add_widget(sg, label);
1468
1469 frame = pidgin_create_imhtml(TRUE, &text, &toolbar, NULL);
1470 dialog->message = GTK_IMHTML(text);
1471 dialog->toolbar = GTK_IMHTMLTOOLBAR(toolbar);
1472 gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
1473
1474 /* Cancel button */
1475 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL,
1476 G_CALLBACK(substatus_editor_cancel_cb), dialog);
1477
1478 /* OK button */
1479 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_OK,
1480 G_CALLBACK(substatus_editor_ok_cb), dialog);
1481
1482 /* Seed the input widgets with the current values */
1483
1484 /* Only look at the saved status if we can't find it in the parent status dialog's substatuses model */
1485 gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter,
1486 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, &parent_dialog_has_substatus, -1);
1487 if (parent_dialog_has_substatus) {
1488 gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter,
1489 STATUS_EDITOR_COLUMN_STATUS_ID, &status_id,
1490 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, &message, -1);
1491 } else if (status_editor->original_title != NULL) {
1492 PurpleSavedStatus *saved_status = NULL;
1493 PurpleSavedStatusSub *substatus = NULL;
1494
1495 if ((saved_status = purple_savedstatus_find(status_editor->original_title)) != NULL) {
1496 if ((substatus = purple_savedstatus_get_substatus(saved_status, account)) != NULL) {
1497 message = (char *)purple_savedstatus_substatus_get_message(substatus);
1498 status_id = (char *)purple_status_type_get_id(purple_savedstatus_substatus_get_type(substatus));
1499 }
1500 }
1501 }
1502 /* TODO: Else get the generic status type from our parent */
1503
1504 if (message)
1505 gtk_imhtml_append_text(dialog->message, message, 0);
1506
1507 for (list = purple_account_get_status_types(account); list; list = list->next)
1508 {
1509 PurpleStatusType *status_type;
1510 const char *id, *name;
1511 PurpleStatusPrimitive prim;
1512
1513 status_type = list->data;
1514
1515 /*
1516 * Only allow users to select statuses that are flagged as
1517 * "user settable" and that aren't independent.
1518 */
1519 if (!purple_status_type_is_user_settable(status_type) ||
1520 purple_status_type_is_independent(status_type))
1521 continue;
1522
1523 id = purple_status_type_get_id(status_type);
1524 prim = purple_status_type_get_primitive(status_type);
1525 name = purple_status_type_get_name(status_type);
1526
1527 gtk_list_store_append(dialog->model, &iter);
1528 gtk_list_store_set(dialog->model, &iter,
1529 STATUS_COLUMN_ICON, pidgin_stock_id_from_status_primitive(prim),
1530 STATUS_COLUMN_STATUS_ID, id,
1531 STATUS_COLUMN_STATUS_NAME, name,
1532 -1);
1533 if ((status_id != NULL) && purple_strequal(status_id, id))
1534 {
1535 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter);
1536 select = TRUE;
1537 }
1538 }
1539
1540 if (!select)
1541 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1542
1543 if (parent_dialog_has_substatus) {
1544 /* These two were gotten from the parent tree model, so they need to be freed */
1545 g_free(status_id);
1546 g_free(message);
1547 }
1548
1549 gtk_widget_show_all(win);
1550 g_object_unref(sg);
1551 }
1552
1553
1554 /**************************************************************************
1555 * Utilities *
1556 **************************************************************************/
1557
1558 enum {
1559 SS_MENU_ENTRY_TYPE_PRIMITIVE,
1560 SS_MENU_ENTRY_TYPE_SAVEDSTATUS
1561 };
1562
1563 enum {
1564 /** _SSMenuEntryType */
1565 SS_MENU_TYPE_COLUMN,
1566
1567 /**
1568 * This is a GdkPixbuf (the other columns are strings).
1569 * This column is visible.
1570 */
1571 SS_MENU_ICON_COLUMN,
1572
1573 /** The text displayed on the status box. This column is visible. */
1574 SS_MENU_TEXT_COLUMN,
1575
1576 /**
1577 * This value depends on SS_MENU_TYPE_COLUMN. For _SAVEDSTATUS types,
1578 * this is the creation time. For _PRIMITIVE types,
1579 * this is the PurpleStatusPrimitive.
1580 */
1581 SS_MENU_DATA_COLUMN,
1582
1583 /**
1584 * This is the emblem to use for this status
1585 */
1586 SS_MENU_EMBLEM_COLUMN,
1587
1588 /**
1589 * And whether or not that emblem is visible
1590 */
1591 SS_MENU_EMBLEM_VISIBLE_COLUMN,
1592
1593 SS_MENU_NUM_COLUMNS
1594 };
1595
1596 static void
status_menu_cb(GtkComboBox * widget,void (* callback)(PurpleSavedStatus *))1597 status_menu_cb(GtkComboBox *widget, void(*callback)(PurpleSavedStatus*))
1598 {
1599 GtkTreeIter iter;
1600 int type;
1601 gpointer data;
1602 PurpleSavedStatus *status = NULL;
1603
1604 if (!gtk_combo_box_get_active_iter(widget, &iter))
1605 return;
1606
1607 gtk_tree_model_get(gtk_combo_box_get_model(widget), &iter,
1608 SS_MENU_TYPE_COLUMN, &type,
1609 SS_MENU_DATA_COLUMN, &data,
1610 -1);
1611
1612 if (type == SS_MENU_ENTRY_TYPE_PRIMITIVE)
1613 {
1614 PurpleStatusPrimitive primitive = GPOINTER_TO_INT(data);
1615 status = purple_savedstatus_find_transient_by_type_and_message(primitive, NULL);
1616 if (status == NULL)
1617 status = purple_savedstatus_new(NULL, primitive);
1618 }
1619 else if (type == SS_MENU_ENTRY_TYPE_SAVEDSTATUS)
1620 status = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
1621
1622 callback(status);
1623 }
1624
1625 static gint
saved_status_sort_alphabetically_func(gconstpointer a,gconstpointer b)1626 saved_status_sort_alphabetically_func(gconstpointer a, gconstpointer b)
1627 {
1628 const PurpleSavedStatus *saved_status_a = a;
1629 const PurpleSavedStatus *saved_status_b = b;
1630 return g_utf8_collate(purple_savedstatus_get_title(saved_status_a),
1631 purple_savedstatus_get_title(saved_status_b));
1632 }
1633
pidgin_status_menu_add_primitive(GtkListStore * model,GtkWidget * w,PurpleStatusPrimitive primitive,PurpleSavedStatus * current_status)1634 static gboolean pidgin_status_menu_add_primitive(GtkListStore *model, GtkWidget *w, PurpleStatusPrimitive primitive,
1635 PurpleSavedStatus *current_status)
1636 {
1637 GtkTreeIter iter;
1638 gboolean currently_selected = FALSE;
1639
1640 gtk_list_store_append(model, &iter);
1641 gtk_list_store_set(model, &iter,
1642 SS_MENU_TYPE_COLUMN, SS_MENU_ENTRY_TYPE_PRIMITIVE,
1643 SS_MENU_ICON_COLUMN, pidgin_stock_id_from_status_primitive(primitive),
1644 SS_MENU_TEXT_COLUMN, purple_primitive_get_name_from_type(primitive),
1645 SS_MENU_DATA_COLUMN, GINT_TO_POINTER(primitive),
1646 SS_MENU_EMBLEM_VISIBLE_COLUMN, FALSE,
1647 -1);
1648
1649 if (purple_savedstatus_is_transient(current_status)
1650 && !purple_savedstatus_has_substatuses(current_status)
1651 && purple_savedstatus_get_type(current_status) == primitive)
1652 currently_selected = TRUE;
1653
1654 return currently_selected;
1655 }
1656
1657 static void
pidgin_status_menu_update_iter(GtkWidget * combobox,GtkListStore * store,GtkTreeIter * iter,PurpleSavedStatus * status)1658 pidgin_status_menu_update_iter(GtkWidget *combobox, GtkListStore *store, GtkTreeIter *iter,
1659 PurpleSavedStatus *status)
1660 {
1661 PurpleStatusPrimitive primitive;
1662
1663 if (store == NULL)
1664 store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox)));
1665
1666 primitive = purple_savedstatus_get_type(status);
1667 gtk_list_store_set(store, iter,
1668 SS_MENU_TYPE_COLUMN, SS_MENU_ENTRY_TYPE_SAVEDSTATUS,
1669 SS_MENU_ICON_COLUMN, pidgin_stock_id_from_status_primitive(primitive),
1670 SS_MENU_TEXT_COLUMN, purple_savedstatus_get_title(status),
1671 SS_MENU_DATA_COLUMN, GINT_TO_POINTER(purple_savedstatus_get_creation_time(status)),
1672 SS_MENU_EMBLEM_COLUMN, GTK_STOCK_SAVE,
1673 SS_MENU_EMBLEM_VISIBLE_COLUMN, TRUE,
1674 -1);
1675 }
1676
1677 static gboolean
pidgin_status_menu_find_iter(GtkListStore * store,GtkTreeIter * iter,PurpleSavedStatus * find)1678 pidgin_status_menu_find_iter(GtkListStore *store, GtkTreeIter *iter, PurpleSavedStatus *find)
1679 {
1680 int type;
1681 gpointer data;
1682 time_t creation_time = purple_savedstatus_get_creation_time(find);
1683 GtkTreeModel *model = GTK_TREE_MODEL(store);
1684
1685 if (!gtk_tree_model_get_iter_first(model, iter))
1686 return FALSE;
1687
1688 do {
1689 gtk_tree_model_get(model, iter,
1690 SS_MENU_TYPE_COLUMN, &type,
1691 SS_MENU_DATA_COLUMN, &data,
1692 -1);
1693 if (type == SS_MENU_ENTRY_TYPE_PRIMITIVE)
1694 continue;
1695 if (GPOINTER_TO_INT(data) == creation_time)
1696 return TRUE;
1697 } while (gtk_tree_model_iter_next(model, iter));
1698
1699 return FALSE;
1700 }
1701
1702 static void
savedstatus_added_cb(PurpleSavedStatus * status,GtkWidget * combobox)1703 savedstatus_added_cb(PurpleSavedStatus *status, GtkWidget *combobox)
1704 {
1705 GtkListStore *store;
1706 GtkTreeIter iter;
1707
1708 if (purple_savedstatus_is_transient(status))
1709 return;
1710
1711 store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox)));
1712 gtk_list_store_append(store, &iter);
1713 pidgin_status_menu_update_iter(combobox, store, &iter, status);
1714 }
1715
1716 static void
savedstatus_deleted_cb(PurpleSavedStatus * status,GtkWidget * combobox)1717 savedstatus_deleted_cb(PurpleSavedStatus *status, GtkWidget *combobox)
1718 {
1719 GtkListStore *store;
1720 GtkTreeIter iter;
1721
1722 if (purple_savedstatus_is_transient(status))
1723 return;
1724
1725 store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox)));
1726 if (pidgin_status_menu_find_iter(store, &iter, status))
1727 gtk_list_store_remove(store, &iter);
1728 }
1729
1730 static void
savedstatus_modified_cb(PurpleSavedStatus * status,GtkWidget * combobox)1731 savedstatus_modified_cb(PurpleSavedStatus *status, GtkWidget *combobox)
1732 {
1733 GtkListStore *store;
1734 GtkTreeIter iter;
1735
1736 if (purple_savedstatus_is_transient(status))
1737 return;
1738
1739 store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox)));
1740 if (pidgin_status_menu_find_iter(store, &iter, status))
1741 pidgin_status_menu_update_iter(combobox, store, &iter, status);
1742 }
1743
pidgin_status_menu(PurpleSavedStatus * current_status,GCallback callback)1744 GtkWidget *pidgin_status_menu(PurpleSavedStatus *current_status, GCallback callback)
1745 {
1746 GtkWidget *combobox;
1747 GtkListStore *model;
1748 GList *sorted, *cur;
1749 int i = 0;
1750 int index = -1;
1751 GtkTreeIter iter;
1752 GtkCellRenderer *text_rend;
1753 GtkCellRenderer *icon_rend;
1754 GtkCellRenderer *emblem_rend;
1755
1756 model = gtk_list_store_new(SS_MENU_NUM_COLUMNS, G_TYPE_INT, G_TYPE_STRING,
1757 G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
1758
1759 combobox = gtk_combo_box_new();
1760
1761 if (pidgin_status_menu_add_primitive(model, combobox, PURPLE_STATUS_AVAILABLE, current_status))
1762 index = i;
1763 i++;
1764
1765 if (pidgin_status_menu_add_primitive(model, combobox, PURPLE_STATUS_AWAY, current_status))
1766 index = i;
1767 i++;
1768
1769 if (pidgin_status_menu_add_primitive(model, combobox, PURPLE_STATUS_INVISIBLE, current_status))
1770 index = i;
1771 i++;
1772
1773 if (pidgin_status_menu_add_primitive(model, combobox, PURPLE_STATUS_OFFLINE, current_status))
1774 index = i;
1775 i++;
1776
1777 sorted = g_list_copy((GList *)purple_savedstatuses_get_all());
1778 sorted = g_list_sort(sorted, saved_status_sort_alphabetically_func);
1779 for (cur = sorted; cur; cur = cur->next)
1780 {
1781 PurpleSavedStatus *status = (PurpleSavedStatus *) cur->data;
1782 if (!purple_savedstatus_is_transient(status))
1783 {
1784 gtk_list_store_append(model, &iter);
1785
1786 pidgin_status_menu_update_iter(combobox, model, &iter, status);
1787
1788 if (status == current_status)
1789 index = i;
1790 i++;
1791 }
1792 }
1793 g_list_free(sorted);
1794
1795 gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(model));
1796
1797 text_rend = gtk_cell_renderer_text_new();
1798 icon_rend = gtk_cell_renderer_pixbuf_new();
1799 emblem_rend = gtk_cell_renderer_pixbuf_new();
1800 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), icon_rend, FALSE);
1801 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), text_rend, TRUE);
1802 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), emblem_rend, FALSE);
1803 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox), icon_rend, "stock-id", SS_MENU_ICON_COLUMN, NULL);
1804 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox), text_rend, "markup", SS_MENU_TEXT_COLUMN, NULL);
1805 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox), emblem_rend,
1806 "stock-id", SS_MENU_EMBLEM_COLUMN, "visible", SS_MENU_EMBLEM_VISIBLE_COLUMN, NULL);
1807 g_object_set(G_OBJECT(icon_rend),
1808 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL),
1809 NULL);
1810 g_object_set(text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1811
1812 gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), index);
1813 g_signal_connect(G_OBJECT(combobox), "changed", G_CALLBACK(status_menu_cb), callback);
1814
1815 /* Make sure the list is updated dynamically when a substatus is changed/deleted
1816 * or a new one is added. */
1817 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-added",
1818 combobox, G_CALLBACK(savedstatus_added_cb), combobox);
1819 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-deleted",
1820 combobox, G_CALLBACK(savedstatus_deleted_cb), combobox);
1821 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-modified",
1822 combobox, G_CALLBACK(savedstatus_modified_cb), combobox);
1823 g_signal_connect(G_OBJECT(combobox), "destroy",
1824 G_CALLBACK(purple_signals_disconnect_by_handle), NULL);
1825
1826 return combobox;
1827 }
1828
1829
1830 /**************************************************************************
1831 * GTK+ saved status glue
1832 **************************************************************************/
1833
1834 void *
pidgin_status_get_handle(void)1835 pidgin_status_get_handle(void)
1836 {
1837 static int handle;
1838
1839 return &handle;
1840 }
1841
1842 void
pidgin_status_init(void)1843 pidgin_status_init(void)
1844 {
1845 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/status");
1846 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/status/dialog");
1847 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/status/dialog/width", 550);
1848 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/status/dialog/height", 250);
1849 }
1850
1851 void
pidgin_status_uninit(void)1852 pidgin_status_uninit(void)
1853 {
1854 pidgin_status_window_hide();
1855 }
1856