comparison pidgin/gtksavedstatuses.c @ 15373:5fe8042783c1

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