Replace strcmp() with purple_strequal()
[pidgin-git.git] / pidgin / gtksavedstatuses.c
blobadfb2938bf8d523b07672e2ac657150f79028ce4
1 /**
2 * @file gtksavedstatus.c GTK+ Saved Status Editor UI
3 * @ingroup pidgin
4 */
6 /* pidgin
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.
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.
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.
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
27 #include "internal.h"
29 #include "account.h"
30 #include "notify.h"
31 #include "request.h"
32 #include "savedstatuses.h"
33 #include "status.h"
34 #include "util.h"
36 #include "gtkblist.h"
37 #include "pidgin.h"
38 #include "gtkimhtml.h"
39 #include "gtkimhtmltoolbar.h"
40 #include "gtksavedstatuses.h"
41 #include "pidginstock.h"
42 #include "gtkutils.h"
45 * TODO: Should attach to the account-deleted and account-added signals
46 * and update the GtkListStores in any StatusEditor windows that
47 * may be open.
50 /**
51 * These are used for the GtkTreeView when you're scrolling through
52 * all your saved statuses.
54 enum
56 STATUS_WINDOW_COLUMN_TITLE,
57 STATUS_WINDOW_COLUMN_TYPE,
58 STATUS_WINDOW_COLUMN_MESSAGE,
59 /** A hidden column containing a pointer to the editor for this saved status. */
60 STATUS_WINDOW_COLUMN_WINDOW,
61 STATUS_WINDOW_COLUMN_ICON,
62 STATUS_WINDOW_NUM_COLUMNS
65 /**
66 * These are used for the GtkTreeView containing the list of accounts
67 * at the bottom of the window when you're editing a particular
68 * saved status.
70 enum
72 /** A hidden column containing a pointer to the PurpleAccount. */
73 STATUS_EDITOR_COLUMN_ACCOUNT,
74 /** A hidden column containing a pointer to the editor for this substatus. */
75 STATUS_EDITOR_COLUMN_WINDOW,
76 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS,
77 STATUS_EDITOR_COLUMN_ICON,
78 STATUS_EDITOR_COLUMN_USERNAME,
79 /** A hidden column containing the ID of this PurpleStatusType. */
80 STATUS_EDITOR_COLUMN_STATUS_ID,
81 STATUS_EDITOR_COLUMN_STATUS_NAME,
82 STATUS_EDITOR_COLUMN_STATUS_MESSAGE,
83 STATUS_EDITOR_COLUMN_STATUS_ICON,
84 STATUS_EDITOR_NUM_COLUMNS
87 /**
88 * These are used in the GtkComboBox to select the specific PurpleStatusType
89 * when setting a (sub)status for a particular saved status.
91 enum
93 STATUS_COLUMN_ICON,
94 /** A hidden column containing the ID of this PurpleStatusType. */
95 STATUS_COLUMN_STATUS_ID,
96 STATUS_COLUMN_STATUS_NAME,
97 STATUS_NUM_COLUMNS
100 typedef struct
102 GtkWidget *window;
103 GtkListStore *model;
104 GtkWidget *treeview;
105 GtkWidget *use_button;
106 GtkWidget *modify_button;
107 GtkWidget *delete_button;
108 } StatusWindow;
110 typedef struct
112 GtkWidget *window;
113 GtkListStore *model;
114 GtkWidget *treeview;
115 GtkButton *saveanduse_button;
116 GtkButton *save_button;
118 gchar *original_title;
119 GtkEntry *title;
120 GtkComboBox *type;
121 GtkIMHtml *message;
122 } StatusEditor;
124 typedef struct
126 StatusEditor *status_editor;
127 PurpleAccount *account;
129 GtkWidget *window;
130 GtkListStore *model;
131 GtkComboBox *box;
132 GtkIMHtml *message;
133 GtkIMHtmlToolbar *toolbar;
134 } SubStatusEditor;
136 static StatusWindow *status_window = NULL;
139 /**************************************************************************
140 * Status window
141 **************************************************************************/
143 static gboolean
144 status_window_find_savedstatus(GtkTreeIter *iter, const char *title)
146 GtkTreeModel *model;
147 char *cur;
149 if ((status_window == NULL) || (title == NULL))
150 return FALSE;
152 model = GTK_TREE_MODEL(status_window->model);
154 if (!gtk_tree_model_get_iter_first(model, iter))
155 return FALSE;
157 do {
158 gtk_tree_model_get(model, iter, STATUS_WINDOW_COLUMN_TITLE, &cur, -1);
159 if (purple_strequal(title, cur))
161 g_free(cur);
162 return TRUE;
164 g_free(cur);
165 } while (gtk_tree_model_iter_next(model, iter));
167 return FALSE;
170 static gboolean
171 status_window_destroy_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
173 StatusWindow *dialog = user_data;
175 dialog->window = NULL;
176 pidgin_status_window_hide();
178 return FALSE;
181 static void
182 status_window_use_cb(GtkButton *button, StatusWindow *dialog)
184 GtkTreeSelection *selection;
185 GtkTreeIter iter;
186 GList *list = NULL;
187 int num_selected = 0;
189 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
191 num_selected = gtk_tree_selection_count_selected_rows(selection);
192 if (num_selected != 1)
194 * This shouldn't happen because the "Use" button should have
195 * been grayed out. Oh well.
197 return;
199 list = gtk_tree_selection_get_selected_rows(selection, NULL);
201 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model),
202 &iter, list->data))
204 gchar *title;
205 PurpleSavedStatus *saved_status;
206 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
207 STATUS_WINDOW_COLUMN_TITLE, &title,
208 -1);
209 saved_status = purple_savedstatus_find(title);
210 g_free(title);
211 purple_savedstatus_activate(saved_status);
214 g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL);
215 g_list_free(list);
218 static void
219 status_window_add_cb(GtkButton *button, gpointer user_data)
221 pidgin_status_editor_show(FALSE, NULL);
224 static void
225 status_window_modify_foreach(GtkTreeModel *model, GtkTreePath *path,
226 GtkTreeIter *iter, gpointer user_data)
228 gchar *title;
229 PurpleSavedStatus *saved_status;
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);
237 static void
238 status_window_modify_cb(GtkButton *button, gpointer user_data)
240 StatusWindow *dialog = user_data;
241 GtkTreeSelection *selection;
243 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
245 gtk_tree_selection_selected_foreach(selection, status_window_modify_foreach, user_data);
248 static void
249 status_window_delete_cancel_cb(gpointer data)
251 GList *sel_titles = data;
252 g_list_foreach(sel_titles, (GFunc) g_free, NULL);
253 g_list_free(sel_titles);
256 static void
257 status_window_delete_confirm_cb(gpointer data)
259 GtkTreeIter iter;
260 GList *sel_titles = data, *l;
261 char *title;
263 for (l = sel_titles; l != NULL; l = l->next) {
264 title = l->data;
265 if (purple_savedstatus_find(title) != purple_savedstatus_get_current()) {
266 if (status_window_find_savedstatus(&iter, title))
267 gtk_list_store_remove(status_window->model, &iter);
268 purple_savedstatus_delete(title);
270 g_free(title);
272 g_list_free(sel_titles);
275 static void
276 status_window_delete_cb(GtkButton *button, gpointer user_data)
278 StatusWindow *dialog = user_data;
279 GtkTreeIter iter;
280 GtkTreeSelection *selection;
281 GList *sel_paths, *l, *sel_titles = NULL;
282 GtkTreeModel *model = GTK_TREE_MODEL(dialog->model);
283 char *title;
284 gpointer handle;
286 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
287 sel_paths = gtk_tree_selection_get_selected_rows(selection, NULL);
289 /* This is ugly because we're not allowed to modify the model from within
290 * gtk_tree_selection_selected_foreach() and the GtkTreePaths can become invalid
291 * when something is removed from the model. The selection can also change while
292 * the request dialog is displayed, so we need to capture the selected rows at this time. */
294 for (l = sel_paths; l != NULL; l = l->next) {
295 if (gtk_tree_model_get_iter(model, &iter, l->data)) {
296 gtk_tree_model_get(model, &iter, STATUS_WINDOW_COLUMN_TITLE, &title, -1);
297 sel_titles = g_list_prepend(sel_titles, title);
299 gtk_tree_path_free(l->data);
301 g_list_free(sel_paths);
303 g_return_if_fail(sel_titles != NULL);
304 if (!sel_titles->next) {
305 title = g_strdup_printf(_("Are you sure you want to delete %s?"),
306 (const gchar *)sel_titles->data);
307 handle = purple_savedstatus_find(sel_titles->data);
308 } else {
309 title = g_strdup(_("Are you sure you want to delete the selected saved statuses?"));
310 handle = dialog;
313 purple_request_action(handle, NULL, title, NULL, 0,
314 NULL, NULL, NULL,
315 sel_titles, 2,
316 _("Delete"), status_window_delete_confirm_cb,
317 _("Cancel"), status_window_delete_cancel_cb);
319 g_free(title);
322 static void
323 status_window_close_cb(GtkButton *button, gpointer user_data)
325 pidgin_status_window_hide();
328 static void
329 status_selected_cb(GtkTreeSelection *sel, gpointer user_data)
331 StatusWindow *dialog = user_data;
332 GList *sel_paths, *tmp;
333 gboolean can_use = TRUE, can_delete = TRUE;
334 int num_selected;
335 GtkTreeModel *model = GTK_TREE_MODEL(dialog->model);
337 sel_paths = gtk_tree_selection_get_selected_rows(sel, NULL);
339 for (tmp = sel_paths, num_selected = 0; tmp; tmp = tmp->next, num_selected++) {
340 GtkTreeIter iter;
341 char *title;
343 if (gtk_tree_model_get_iter(model, &iter, tmp->data)) {
344 gtk_tree_model_get(model, &iter,
345 STATUS_WINDOW_COLUMN_TITLE, &title, -1);
346 if (purple_savedstatus_find(title) == purple_savedstatus_get_current()) {
347 can_use = can_delete = FALSE;
350 g_free(title);
353 gtk_tree_path_free(tmp->data);
356 gtk_widget_set_sensitive(dialog->use_button, (num_selected == 1) && can_use);
357 gtk_widget_set_sensitive(dialog->modify_button, (num_selected > 0));
358 gtk_widget_set_sensitive(dialog->delete_button, num_selected > 0 && can_delete);
360 g_list_free(sel_paths);
363 static const gchar *
364 get_stock_icon_from_primitive(PurpleStatusPrimitive type)
366 return pidgin_stock_id_from_status_primitive(type);
369 static void
370 add_status_to_saved_status_list(GtkListStore *model, PurpleSavedStatus *saved_status)
372 GtkTreeIter iter;
373 const char *title;
374 const char *type;
375 const gchar *icon;
376 char *message;
378 if (purple_savedstatus_is_transient(saved_status))
379 return;
381 title = purple_savedstatus_get_title(saved_status);
382 type = purple_primitive_get_name_from_type(purple_savedstatus_get_type(saved_status));
383 message = purple_markup_strip_html(purple_savedstatus_get_message(saved_status));
384 icon = get_stock_icon_from_primitive(purple_savedstatus_get_type(saved_status));
386 gtk_list_store_append(model, &iter);
387 gtk_list_store_set(model, &iter,
388 STATUS_WINDOW_COLUMN_ICON, icon,
389 STATUS_WINDOW_COLUMN_TITLE, title,
390 STATUS_WINDOW_COLUMN_TYPE, type,
391 STATUS_WINDOW_COLUMN_MESSAGE, message,
392 -1);
393 g_free(message);
396 static void
397 populate_saved_status_list(StatusWindow *dialog)
399 GList *saved_statuses;
401 gtk_list_store_clear(dialog->model);
403 for (saved_statuses = purple_savedstatuses_get_all(); saved_statuses != NULL;
404 saved_statuses = g_list_next(saved_statuses))
406 add_status_to_saved_status_list(dialog->model, saved_statuses->data);
410 static gboolean
411 search_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data)
413 gboolean result;
414 char *haystack;
416 gtk_tree_model_get(model, iter, column, &haystack, -1);
418 result = (purple_strcasestr(haystack, key) == NULL);
420 g_free(haystack);
422 return result;
425 static void
426 savedstatus_activated_cb(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, StatusWindow *dialog)
428 status_window_use_cb(NULL, dialog);
429 status_window_close_cb(NULL, dialog);
432 static void
433 saved_status_updated_cb(PurpleSavedStatus *status, StatusWindow *sw)
435 populate_saved_status_list(sw);
438 static GtkWidget *
439 create_saved_status_list(StatusWindow *dialog)
441 GtkWidget *treeview;
442 GtkTreeSelection *sel;
443 GtkTreeViewColumn *column;
444 GtkCellRenderer *renderer;
446 /* Create the list model */
447 dialog->model = gtk_list_store_new(STATUS_WINDOW_NUM_COLUMNS,
448 G_TYPE_STRING,
449 G_TYPE_STRING,
450 G_TYPE_STRING,
451 G_TYPE_POINTER,
452 G_TYPE_STRING);
454 /* Create the treeview */
455 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
456 dialog->treeview = treeview;
457 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
458 g_signal_connect(G_OBJECT(treeview), "row-activated",
459 G_CALLBACK(savedstatus_activated_cb), dialog);
461 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
462 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
463 g_signal_connect(G_OBJECT(sel), "changed",
464 G_CALLBACK(status_selected_cb), dialog);
466 /* Add columns */
467 column = gtk_tree_view_column_new();
468 gtk_tree_view_column_set_title(column, _("Title"));
469 gtk_tree_view_column_set_resizable(column, TRUE);
470 gtk_tree_view_column_set_min_width(column, 100);
471 gtk_tree_view_column_set_sort_column_id(column,
472 STATUS_WINDOW_COLUMN_TITLE);
473 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
474 renderer = gtk_cell_renderer_text_new();
475 gtk_tree_view_column_pack_start(column, renderer, TRUE);
476 gtk_tree_view_column_add_attribute(column, renderer, "text",
477 STATUS_WINDOW_COLUMN_TITLE);
478 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
480 column = gtk_tree_view_column_new();
481 gtk_tree_view_column_set_title(column, _("Type"));
482 gtk_tree_view_column_set_resizable(column, TRUE);
483 gtk_tree_view_column_set_sort_column_id(column,
484 STATUS_WINDOW_COLUMN_TYPE);
485 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
486 renderer = gtk_cell_renderer_pixbuf_new();
487 gtk_tree_view_column_pack_start(column, renderer, TRUE);
488 gtk_tree_view_column_add_attribute(column, renderer, "stock-id",
489 STATUS_WINDOW_COLUMN_ICON);
490 renderer = gtk_cell_renderer_text_new();
491 gtk_tree_view_column_pack_start(column, renderer, TRUE);
492 gtk_tree_view_column_add_attribute(column, renderer, "text",
493 STATUS_WINDOW_COLUMN_TYPE);
495 column = gtk_tree_view_column_new();
496 gtk_tree_view_column_set_title(column, _("Message"));
497 gtk_tree_view_column_set_resizable(column, TRUE);
498 gtk_tree_view_column_set_sort_column_id(column,
499 STATUS_WINDOW_COLUMN_MESSAGE);
500 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
501 renderer = gtk_cell_renderer_text_new();
502 gtk_tree_view_column_pack_start(column, renderer, TRUE);
503 gtk_tree_view_column_add_attribute(column, renderer, "text",
504 STATUS_WINDOW_COLUMN_MESSAGE);
505 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
507 /* Enable CTRL+F searching */
508 gtk_tree_view_set_search_column(GTK_TREE_VIEW(treeview), STATUS_WINDOW_COLUMN_TITLE);
509 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(treeview), search_func, NULL, NULL);
511 /* Sort the title column by default */
512 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dialog->model),
513 STATUS_WINDOW_COLUMN_TITLE,
514 GTK_SORT_ASCENDING);
516 /* Populate list */
517 populate_saved_status_list(dialog);
519 gtk_widget_show_all(treeview);
521 return pidgin_make_scrollable(treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1);
524 static gboolean
525 configure_cb(GtkWidget *widget, GdkEventConfigure *event, StatusWindow *dialog)
527 if (GTK_WIDGET_VISIBLE(widget))
529 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/status/dialog/width", event->width);
530 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/status/dialog/height", event->height);
533 return FALSE;
536 static void
537 current_status_changed(PurpleSavedStatus *old, PurpleSavedStatus *new_status,
538 StatusWindow *dialog)
540 status_selected_cb(gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview)), dialog);
543 void
544 pidgin_status_window_show(void)
546 StatusWindow *dialog;
547 GtkWidget *bbox;
548 GtkWidget *button;
549 GtkWidget *list;
550 GtkWidget *vbox;
551 GtkWidget *win;
552 int width, height;
554 if (status_window != NULL)
556 gtk_window_present(GTK_WINDOW(status_window->window));
557 return;
560 status_window = dialog = g_new0(StatusWindow, 1);
562 width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/width");
563 height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/height");
565 dialog->window = win = pidgin_create_dialog(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE);
566 gtk_window_set_default_size(GTK_WINDOW(win), width, height);
568 g_signal_connect(G_OBJECT(win), "delete_event",
569 G_CALLBACK(status_window_destroy_cb), dialog);
570 g_signal_connect(G_OBJECT(win), "configure_event",
571 G_CALLBACK(configure_cb), dialog);
573 /* Setup the vbox */
574 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
576 /* List of saved status states */
577 list = create_saved_status_list(dialog);
578 gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0);
580 /* Button box. */
581 bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win));
583 /* Use button */
584 button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE,
585 PIDGIN_BUTTON_HORIZONTAL);
586 dialog->use_button = button;
587 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
588 gtk_widget_set_sensitive(button, FALSE);
590 g_signal_connect(G_OBJECT(button), "clicked",
591 G_CALLBACK(status_window_use_cb), dialog);
593 /* Add button */
594 pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_ADD,
595 G_CALLBACK(status_window_add_cb), dialog);
597 /* Modify button */
598 button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY,
599 G_CALLBACK(status_window_modify_cb), dialog);
600 dialog->modify_button = button;
601 gtk_widget_set_sensitive(button, FALSE);
603 /* Delete button */
604 button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE,
605 G_CALLBACK(status_window_delete_cb), dialog);
606 dialog->delete_button = button;
607 gtk_widget_set_sensitive(button, FALSE);
609 /* Close button */
610 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE,
611 G_CALLBACK(status_window_close_cb), dialog);
613 purple_signal_connect(purple_savedstatuses_get_handle(),
614 "savedstatus-changed", status_window,
615 PURPLE_CALLBACK(current_status_changed), dialog);
616 purple_signal_connect(purple_savedstatuses_get_handle(),
617 "savedstatus-added", status_window,
618 PURPLE_CALLBACK(saved_status_updated_cb), dialog);
619 purple_signal_connect(purple_savedstatuses_get_handle(),
620 "savedstatus-deleted", status_window,
621 PURPLE_CALLBACK(saved_status_updated_cb), dialog);
622 purple_signal_connect(purple_savedstatuses_get_handle(),
623 "savedstatus-modified", status_window,
624 PURPLE_CALLBACK(saved_status_updated_cb), dialog);
626 gtk_widget_show_all(win);
629 void
630 pidgin_status_window_hide(void)
632 if (status_window == NULL)
633 return;
635 if (status_window->window != NULL)
636 gtk_widget_destroy(status_window->window);
638 purple_request_close_with_handle(status_window);
639 purple_notify_close_with_handle(status_window);
640 purple_signals_disconnect_by_handle(status_window);
641 g_object_unref(G_OBJECT(status_window->model));
642 g_free(status_window);
643 status_window = NULL;
647 /**************************************************************************
648 * Status editor
649 **************************************************************************/
651 static void substatus_editor_cancel_cb(GtkButton *button, gpointer user_data);
653 static void
654 status_editor_remove_dialog(StatusEditor *dialog)
656 GtkTreeModel *model;
657 GtkTreeIter iter;
659 /* Remove the reference to this dialog from our parent's list store */
660 if (status_window_find_savedstatus(&iter, dialog->original_title))
662 gtk_list_store_set(status_window->model, &iter,
663 STATUS_WINDOW_COLUMN_WINDOW, NULL,
664 -1);
667 /* Close any substatus editors that may be open */
668 model = GTK_TREE_MODEL(dialog->model);
669 if (gtk_tree_model_get_iter_first(model, &iter))
671 do {
672 SubStatusEditor *substatus_dialog;
674 gtk_tree_model_get(model, &iter,
675 STATUS_EDITOR_COLUMN_WINDOW, &substatus_dialog,
676 -1);
677 if (substatus_dialog != NULL)
679 gtk_list_store_set(dialog->model, &iter,
680 STATUS_EDITOR_COLUMN_WINDOW, NULL,
681 -1);
682 substatus_editor_cancel_cb(NULL, substatus_dialog);
684 } while (gtk_tree_model_iter_next(model, &iter));
689 static void
690 status_editor_destroy_cb(GtkWidget *widget, gpointer user_data)
692 StatusEditor *dialog = user_data;
694 status_editor_remove_dialog(dialog);
695 g_free(dialog->original_title);
696 g_object_unref(G_OBJECT(dialog->model));
697 g_free(dialog);
700 static void
701 status_editor_cancel_cb(GtkButton *button, gpointer user_data)
703 StatusEditor *dialog = user_data;
704 gtk_widget_destroy(dialog->window);
707 static void
708 status_editor_ok_cb(GtkButton *button, gpointer user_data)
710 StatusEditor *dialog = user_data;
711 const char *title;
712 PurpleStatusPrimitive type;
713 char *message, *unformatted;
714 PurpleSavedStatus *saved_status = NULL;
715 GtkTreeModel *model;
716 GtkTreeIter iter;
718 title = gtk_entry_get_text(dialog->title);
721 * If we're saving this status, and the title is already taken
722 * then show an error dialog and don't do anything.
724 if (((button == dialog->saveanduse_button) || (button == dialog->save_button)) &&
725 (purple_savedstatus_find(title) != NULL) &&
726 ((dialog->original_title == NULL) || (!purple_strequal(title, dialog->original_title))))
728 purple_notify_error(status_window, NULL, _("Title already in use. You must "
729 "choose a unique title."), NULL);
730 return;
733 type = gtk_combo_box_get_active(dialog->type) + (PURPLE_STATUS_UNSET + 1);
734 message = gtk_imhtml_get_markup(dialog->message);
735 unformatted = purple_markup_strip_html(message);
738 * If we're editing an old status, then lookup the old status.
739 * Note: It is possible that it has been deleted or renamed
740 * or something, and no longer exists.
742 if (dialog->original_title != NULL)
744 GtkTreeIter iter;
746 saved_status = purple_savedstatus_find(dialog->original_title);
748 if (status_window_find_savedstatus(&iter, dialog->original_title))
749 gtk_list_store_remove(status_window->model, &iter);
752 if (saved_status == NULL)
754 /* This is a new status */
755 if ((button == dialog->saveanduse_button)
756 || (button == dialog->save_button))
757 saved_status = purple_savedstatus_new(title, type);
758 else
759 saved_status = purple_savedstatus_new(NULL, type);
761 else
763 /* Modify the old status */
764 if (!purple_strequal(title, dialog->original_title))
765 purple_savedstatus_set_title(saved_status, title);
766 purple_savedstatus_set_type(saved_status, type);
769 if (*unformatted == '\0')
770 purple_savedstatus_set_message(saved_status, NULL);
771 else
772 purple_savedstatus_set_message(saved_status, message);
774 /* Set any substatuses */
775 model = GTK_TREE_MODEL(dialog->model);
776 if (gtk_tree_model_get_iter_first(model, &iter))
778 do {
779 PurpleAccount *account;
780 gboolean enabled;
781 char *id;
782 char *message;
783 PurpleStatusType *type;
785 gtk_tree_model_get(model, &iter,
786 STATUS_EDITOR_COLUMN_ACCOUNT, &account,
787 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, &enabled,
788 STATUS_EDITOR_COLUMN_STATUS_ID, &id,
789 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, &message,
790 -1);
791 if (enabled)
793 type = purple_account_get_status_type(account, id);
794 purple_savedstatus_set_substatus(saved_status, account, type, message);
796 else
798 purple_savedstatus_unset_substatus(saved_status, account);
800 g_free(id);
801 g_free(message);
802 } while (gtk_tree_model_iter_next(model, &iter));
805 g_free(message);
806 g_free(unformatted);
808 /* If they clicked on "Save and Use" or "Use," then activate the status */
809 if (button != dialog->save_button)
810 purple_savedstatus_activate(saved_status);
812 gtk_widget_destroy(dialog->window);
815 static void
816 editor_title_changed_cb(GtkWidget *widget, gpointer user_data)
818 StatusEditor *dialog = user_data;
819 const gchar *text;
821 text = gtk_entry_get_text(dialog->title);
823 gtk_widget_set_sensitive(GTK_WIDGET(dialog->saveanduse_button), (*text != '\0'));
824 gtk_widget_set_sensitive(GTK_WIDGET(dialog->save_button), (*text != '\0'));
827 static GtkWidget *
828 create_status_type_menu(PurpleStatusPrimitive type)
830 int i;
831 GtkWidget *dropdown;
832 GtkListStore *store;
833 GtkTreeIter iter;
834 GtkCellRenderer *renderer;
836 store = gtk_list_store_new(STATUS_NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
838 for (i = PURPLE_STATUS_UNSET + 1; i < PURPLE_STATUS_NUM_PRIMITIVES; i++)
840 /* Someone should fix this for 3.0.0. The independent boolean
841 * should probably be set on the status type, not the status.
842 * I guess that would prevent third party plugins from creating
843 * independent statuses?
845 if (i == PURPLE_STATUS_MOBILE ||
846 i == PURPLE_STATUS_MOOD ||
847 i == PURPLE_STATUS_TUNE)
849 * Special-case these. They're intended to be independent
850 * status types, so don't show them in the list.
852 continue;
854 gtk_list_store_append(store, &iter);
855 gtk_list_store_set(store, &iter,
856 STATUS_COLUMN_ICON, get_stock_icon_from_primitive(i),
857 STATUS_COLUMN_STATUS_ID, purple_primitive_get_id_from_type(i),
858 STATUS_COLUMN_STATUS_NAME, purple_primitive_get_name_from_type(i),
859 -1);
862 dropdown = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
864 renderer = gtk_cell_renderer_pixbuf_new();
865 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(dropdown), renderer, FALSE);
866 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(dropdown), renderer,
867 "stock-id", STATUS_COLUMN_ICON,
868 NULL);
870 renderer = gtk_cell_renderer_text_new();
871 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(dropdown), renderer, TRUE);
872 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(dropdown), renderer,
873 "text", STATUS_COLUMN_STATUS_NAME,
874 NULL);
876 gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown),
877 type - (PURPLE_STATUS_UNSET + 1));
879 return dropdown;
882 static void edit_substatus(StatusEditor *status_editor, PurpleAccount *account);
884 static void
885 edit_substatus_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col, gpointer user_data)
887 StatusEditor *dialog = user_data;
888 GtkTreeIter iter;
889 PurpleAccount *account;
891 gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path);
892 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
893 STATUS_EDITOR_COLUMN_ACCOUNT, &account,
894 -1);
896 edit_substatus(dialog, account);
899 static void
900 status_editor_substatus_cb(GtkCellRendererToggle *renderer, gchar *path_str, gpointer data)
902 StatusEditor *dialog = (StatusEditor *)data;
903 GtkTreeIter iter;
904 gboolean enabled;
905 PurpleAccount *account;
907 gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(dialog->model), &iter, path_str);
908 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
909 STATUS_EDITOR_COLUMN_ACCOUNT, &account,
910 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, &enabled,
911 -1);
913 enabled = !enabled;
915 if (enabled)
917 edit_substatus(dialog, account);
919 else
921 /* Remove the substatus */
922 gtk_list_store_set(dialog->model, &iter,
923 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, enabled,
924 STATUS_EDITOR_COLUMN_STATUS_ID, NULL,
925 STATUS_EDITOR_COLUMN_STATUS_NAME, NULL,
926 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, NULL,
927 STATUS_EDITOR_COLUMN_STATUS_ICON, NULL,
928 -1);
932 static void
933 status_editor_add_columns(StatusEditor *dialog)
935 GtkCellRenderer *renderer;
936 GtkTreeViewColumn *column;
938 /* Enable Different status column */
939 renderer = gtk_cell_renderer_toggle_new();
940 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(dialog->treeview),
941 -1, _("Different"),
942 renderer,
943 "active", STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS,
944 NULL);
945 g_signal_connect(G_OBJECT(renderer), "toggled",
946 G_CALLBACK(status_editor_substatus_cb), dialog);
948 /* Username column */
949 column = gtk_tree_view_column_new();
950 gtk_tree_view_column_set_resizable(column, TRUE);
951 gtk_tree_view_column_set_title(column, _("Username"));
952 gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
953 gtk_tree_view_column_set_resizable(column, TRUE);
955 /* Icon */
956 renderer = gtk_cell_renderer_pixbuf_new();
957 gtk_tree_view_column_pack_start(column, renderer, FALSE);
958 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf",
959 STATUS_EDITOR_COLUMN_ICON);
961 /* Username */
962 renderer = gtk_cell_renderer_text_new();
963 gtk_tree_view_column_pack_start(column, renderer, TRUE);
964 gtk_tree_view_column_add_attribute(column, renderer, "text",
965 STATUS_EDITOR_COLUMN_USERNAME);
967 /* Status column */
968 column = gtk_tree_view_column_new();
969 gtk_tree_view_column_set_resizable(column, TRUE);
970 gtk_tree_view_column_set_title(column, _("Status"));
971 gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
972 gtk_tree_view_column_set_resizable(column, TRUE);
973 renderer = gtk_cell_renderer_pixbuf_new();
974 gtk_tree_view_column_pack_start(column, renderer, FALSE);
975 gtk_tree_view_column_add_attribute(column, renderer, "stock-id",
976 STATUS_EDITOR_COLUMN_STATUS_ICON);
977 renderer = gtk_cell_renderer_text_new();
978 gtk_tree_view_column_pack_start(column, renderer, TRUE);
979 gtk_tree_view_column_add_attribute(column, renderer, "text",
980 STATUS_EDITOR_COLUMN_STATUS_NAME);
982 /* Message column */
983 column = gtk_tree_view_column_new();
984 gtk_tree_view_column_set_resizable(column, TRUE);
985 gtk_tree_view_column_set_title(column, _("Message"));
986 gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
987 gtk_tree_view_column_set_resizable(column, TRUE);
988 renderer = gtk_cell_renderer_text_new();
989 gtk_tree_view_column_pack_start(column, renderer, TRUE);
990 gtk_tree_view_column_add_attribute(column, renderer, "text",
991 STATUS_EDITOR_COLUMN_STATUS_MESSAGE);
993 g_signal_connect(G_OBJECT(dialog->treeview), "row-activated",
994 G_CALLBACK(edit_substatus_cb), dialog);
997 static void
998 status_editor_set_account(GtkListStore *store, PurpleAccount *account,
999 GtkTreeIter *iter, PurpleSavedStatusSub *substatus)
1001 GdkPixbuf *pixbuf;
1002 const char *id = NULL, *name = NULL, *message = NULL;
1003 PurpleStatusPrimitive prim = PURPLE_STATUS_UNSET;
1005 pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_MEDIUM);
1006 if ((pixbuf != NULL) && !purple_account_is_connected(account))
1008 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
1011 if (substatus != NULL)
1013 const PurpleStatusType *type;
1015 type = purple_savedstatus_substatus_get_type(substatus);
1016 id = purple_status_type_get_id(type);
1017 name = purple_status_type_get_name(type);
1018 prim = purple_status_type_get_primitive(type);
1019 if (purple_status_type_get_attr(type, "message"))
1020 message = purple_savedstatus_substatus_get_message(substatus);
1023 gtk_list_store_set(store, iter,
1024 STATUS_EDITOR_COLUMN_ACCOUNT, account,
1025 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, (substatus != NULL),
1026 STATUS_EDITOR_COLUMN_ICON, pixbuf,
1027 STATUS_EDITOR_COLUMN_USERNAME, purple_account_get_username(account),
1028 STATUS_EDITOR_COLUMN_STATUS_ID, id,
1029 STATUS_EDITOR_COLUMN_STATUS_NAME, name,
1030 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, message,
1031 STATUS_EDITOR_COLUMN_STATUS_ICON, get_stock_icon_from_primitive(prim),
1032 -1);
1034 if (pixbuf != NULL)
1035 g_object_unref(G_OBJECT(pixbuf));
1038 static void
1039 status_editor_add_account(StatusEditor *dialog, PurpleAccount *account,
1040 PurpleSavedStatusSub *substatus)
1042 GtkTreeIter iter;
1044 gtk_list_store_append(dialog->model, &iter);
1046 status_editor_set_account(dialog->model, account, &iter, substatus);
1049 static void
1050 status_editor_populate_list(StatusEditor *dialog, PurpleSavedStatus *saved_status)
1052 GList *iter;
1053 PurpleSavedStatusSub *substatus;
1055 gtk_list_store_clear(dialog->model);
1057 for (iter = purple_accounts_get_all(); iter != NULL; iter = iter->next)
1059 PurpleAccount *account = (PurpleAccount *)iter->data;
1061 if (saved_status != NULL)
1062 substatus = purple_savedstatus_get_substatus(saved_status, account);
1063 else
1064 substatus = NULL;
1066 status_editor_add_account(dialog, account, substatus);
1070 void
1071 pidgin_status_editor_show(gboolean edit, PurpleSavedStatus *saved_status)
1073 GtkTreeIter iter;
1074 StatusEditor *dialog;
1075 GtkSizeGroup *sg;
1076 GtkWidget *bbox;
1077 GtkWidget *button;
1078 GtkWidget *dbox;
1079 GtkWidget *expander;
1080 GtkWidget *dropdown;
1081 GtkWidget *entry;
1082 GtkWidget *frame;
1083 GtkWidget *hbox;
1084 GtkWidget *text;
1085 GtkWidget *toolbar;
1086 GtkWidget *vbox;
1087 GtkWidget *win;
1088 GList *focus_chain = NULL;
1090 if (edit)
1092 g_return_if_fail(saved_status != NULL);
1093 g_return_if_fail(!purple_savedstatus_is_transient(saved_status));
1096 /* Find a possible window for this saved status and present it */
1097 if (edit && status_window_find_savedstatus(&iter, purple_savedstatus_get_title(saved_status)))
1099 gtk_tree_model_get(GTK_TREE_MODEL(status_window->model), &iter,
1100 STATUS_WINDOW_COLUMN_WINDOW, &dialog,
1101 -1);
1102 if (dialog != NULL)
1104 gtk_window_present(GTK_WINDOW(dialog->window));
1105 return;
1109 dialog = g_new0(StatusEditor, 1);
1110 if (edit && status_window_find_savedstatus(&iter, purple_savedstatus_get_title(saved_status)))
1112 gtk_list_store_set(status_window->model, &iter,
1113 STATUS_WINDOW_COLUMN_WINDOW, dialog,
1114 -1);
1117 if (edit)
1118 dialog->original_title = g_strdup(purple_savedstatus_get_title(saved_status));
1120 dialog->window = win = pidgin_create_dialog(_("Status"), PIDGIN_HIG_BORDER, "status", TRUE);
1122 g_signal_connect(G_OBJECT(win), "destroy",
1123 G_CALLBACK(status_editor_destroy_cb), dialog);
1125 /* Setup the vbox */
1126 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
1128 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1130 /* Title */
1131 entry = gtk_entry_new();
1132 dialog->title = GTK_ENTRY(entry);
1133 if ((saved_status != NULL)
1134 && !purple_savedstatus_is_transient(saved_status)
1135 && (purple_savedstatus_get_title(saved_status) != NULL))
1136 gtk_entry_set_text(GTK_ENTRY(entry), purple_savedstatus_get_title(saved_status));
1137 g_signal_connect(G_OBJECT(entry), "changed",
1138 G_CALLBACK(editor_title_changed_cb), dialog);
1139 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Title:"), sg, entry, TRUE, NULL);
1141 /* Status type */
1142 if (saved_status != NULL)
1143 dropdown = create_status_type_menu(purple_savedstatus_get_type(saved_status));
1144 else
1145 dropdown = create_status_type_menu(PURPLE_STATUS_AWAY);
1146 dialog->type = GTK_COMBO_BOX(dropdown);
1147 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Status:"), sg, dropdown, TRUE, NULL);
1149 /* Status message */
1150 frame = pidgin_create_imhtml(TRUE, &text, &toolbar, NULL);
1151 dialog->message = GTK_IMHTML(text);
1152 hbox = pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Message:"), sg, frame, TRUE, NULL);
1153 gtk_container_child_set(GTK_CONTAINER(vbox), hbox, "expand", TRUE, "fill", TRUE, NULL);
1154 focus_chain = g_list_prepend(focus_chain, dialog->message);
1155 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), focus_chain);
1156 g_list_free(focus_chain);
1158 gtk_imhtml_set_return_inserts_newline(dialog->message);
1160 if ((saved_status != NULL) && (purple_savedstatus_get_message(saved_status) != NULL))
1161 gtk_imhtml_append_text(GTK_IMHTML(text),
1162 purple_savedstatus_get_message(saved_status), 0);
1164 /* Different status message expander */
1165 expander = gtk_expander_new_with_mnemonic(_("Use a _different status for some accounts"));
1166 gtk_box_pack_start(GTK_BOX(vbox), expander, FALSE, FALSE, 0);
1168 /* Setup the box that the expander will cover */
1169 dbox = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
1170 gtk_container_add(GTK_CONTAINER(expander), dbox);
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 G_TYPE_STRING);
1184 /* Create the treeview */
1185 dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
1186 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(dialog->treeview), TRUE);
1187 gtk_widget_set_size_request(dialog->treeview, -1, 150);
1188 gtk_box_pack_start(GTK_BOX(dbox),
1189 pidgin_make_scrollable(dialog->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1190 TRUE, TRUE, 0);
1192 /* Add columns */
1193 status_editor_add_columns(dialog);
1195 /* Populate list */
1196 status_editor_populate_list(dialog, saved_status);
1198 /* Expand the treeview if we have substatuses */
1199 gtk_expander_set_expanded(GTK_EXPANDER(expander),
1200 (saved_status != NULL) && purple_savedstatus_has_substatuses(saved_status));
1202 /* Button box */
1203 bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win));
1204 gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
1205 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
1207 /* Cancel button */
1208 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL,
1209 G_CALLBACK(status_editor_cancel_cb), dialog);
1211 /* Use button */
1212 button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE,
1213 PIDGIN_BUTTON_HORIZONTAL);
1214 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1215 g_signal_connect(G_OBJECT(button), "clicked",
1216 G_CALLBACK(status_editor_ok_cb), dialog);
1218 /* Save and Use button */
1219 button = pidgin_pixbuf_button_from_stock(_("Sa_ve and Use"), GTK_STOCK_OK,
1220 PIDGIN_BUTTON_HORIZONTAL);
1221 dialog->saveanduse_button = GTK_BUTTON(button);
1222 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1223 if (dialog->original_title == NULL)
1224 gtk_widget_set_sensitive(button, FALSE);
1225 g_signal_connect(G_OBJECT(button), "clicked",
1226 G_CALLBACK(status_editor_ok_cb), dialog);
1228 /* Save button */
1229 button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_SAVE,
1230 G_CALLBACK(status_editor_ok_cb), dialog);
1231 if (dialog->original_title == NULL)
1232 gtk_widget_set_sensitive(button, FALSE);
1233 dialog->save_button = GTK_BUTTON(button);
1235 gtk_widget_show_all(win);
1236 g_object_unref(sg);
1240 /**************************************************************************
1241 * SubStatus editor
1242 **************************************************************************/
1244 static void
1245 substatus_selection_changed_cb(GtkComboBox *box, gpointer user_data)
1247 SubStatusEditor *select = user_data;
1248 GtkTreeIter iter;
1249 char *id;
1250 PurpleStatusType *type;
1252 if (!gtk_combo_box_get_active_iter(box, &iter))
1253 return;
1254 gtk_tree_model_get(GTK_TREE_MODEL(select->model), &iter,
1255 STATUS_COLUMN_STATUS_ID, &id,
1256 -1);
1257 type = purple_account_get_status_type(select->account, id);
1258 g_free(id);
1260 if (purple_status_type_get_attr(type, "message") == NULL)
1262 gtk_widget_set_sensitive(GTK_WIDGET(select->message), FALSE);
1263 gtk_widget_set_sensitive(GTK_WIDGET(select->toolbar), FALSE);
1265 else
1267 gtk_widget_set_sensitive(GTK_WIDGET(select->message), TRUE);
1268 gtk_widget_set_sensitive(GTK_WIDGET(select->toolbar), TRUE);
1272 static gboolean
1273 status_editor_find_account_in_treemodel(GtkTreeIter *iter,
1274 StatusEditor *status_editor,
1275 PurpleAccount *account)
1277 GtkTreeModel *model;
1278 PurpleAccount *cur;
1280 g_return_val_if_fail(status_editor != NULL, FALSE);
1281 g_return_val_if_fail(account != NULL, FALSE);
1283 model = GTK_TREE_MODEL(status_editor->model);
1285 if (!gtk_tree_model_get_iter_first(model, iter))
1286 return FALSE;
1288 do {
1289 gtk_tree_model_get(model, iter, STATUS_EDITOR_COLUMN_ACCOUNT, &cur, -1);
1290 if (cur == account)
1291 return TRUE;
1292 } while (gtk_tree_model_iter_next(model, iter));
1294 return FALSE;
1297 static void
1298 substatus_editor_remove_dialog(SubStatusEditor *dialog)
1300 GtkTreeIter iter;
1302 if (status_editor_find_account_in_treemodel(&iter, dialog->status_editor, dialog->account))
1304 /* Remove the reference to this dialog from our parent's list store */
1305 gtk_list_store_set(dialog->status_editor->model, &iter,
1306 STATUS_EDITOR_COLUMN_WINDOW, NULL,
1307 -1);
1311 static void
1312 substatus_editor_destroy_cb(GtkWidget *widget, gpointer user_data)
1314 SubStatusEditor *dialog = user_data;
1316 substatus_editor_remove_dialog(dialog);
1317 g_free(dialog);
1320 static void
1321 substatus_editor_cancel_cb(GtkButton *button, gpointer user_data)
1323 SubStatusEditor *dialog = user_data;
1324 gtk_widget_destroy(dialog->window);
1328 static void
1329 substatus_editor_ok_cb(GtkButton *button, gpointer user_data)
1331 SubStatusEditor *dialog = user_data;
1332 StatusEditor *status_editor;
1333 GtkTreeIter iter;
1334 PurpleStatusType *type;
1335 char *id = NULL;
1336 char *message = NULL;
1337 const char *name = NULL, *stock = NULL;
1339 if (!gtk_combo_box_get_active_iter(dialog->box, &iter))
1341 gtk_widget_destroy(dialog->window);
1342 return;
1345 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
1346 STATUS_COLUMN_STATUS_ID, &id,
1347 -1);
1348 type = purple_account_get_status_type(dialog->account, id);
1349 if (purple_status_type_get_attr(type, "message") != NULL)
1350 message = gtk_imhtml_get_markup(GTK_IMHTML(dialog->message));
1351 name = purple_status_type_get_name(type);
1352 stock = get_stock_icon_from_primitive(purple_status_type_get_primitive(type));
1354 status_editor = dialog->status_editor;
1356 if (status_editor_find_account_in_treemodel(&iter, status_editor, dialog->account))
1358 gtk_list_store_set(status_editor->model, &iter,
1359 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, TRUE,
1360 STATUS_EDITOR_COLUMN_STATUS_ID, id,
1361 STATUS_EDITOR_COLUMN_STATUS_NAME, name,
1362 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, message,
1363 STATUS_EDITOR_COLUMN_WINDOW, NULL,
1364 STATUS_EDITOR_COLUMN_STATUS_ICON, stock,
1365 -1);
1368 gtk_widget_destroy(dialog->window);
1369 g_free(id);
1370 g_free(message);
1373 static void
1374 edit_substatus(StatusEditor *status_editor, PurpleAccount *account)
1376 char *tmp;
1377 SubStatusEditor *dialog;
1378 GtkSizeGroup *sg;
1379 GtkWidget *combo;
1380 GtkWidget *hbox;
1381 GtkWidget *frame;
1382 GtkWidget *label;
1383 GtkWidget *text;
1384 GtkWidget *toolbar;
1385 GtkWidget *vbox;
1386 GtkWidget *win;
1387 GtkTreeIter iter;
1388 GtkCellRenderer *rend;
1389 char *status_id = NULL;
1390 char *message = NULL;
1391 gboolean parent_dialog_has_substatus = FALSE;
1392 GList *list;
1393 gboolean select = FALSE;
1395 g_return_if_fail(status_editor != NULL);
1396 g_return_if_fail(account != NULL);
1398 status_editor_find_account_in_treemodel(&iter, status_editor, account);
1399 gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter,
1400 STATUS_EDITOR_COLUMN_WINDOW, &dialog,
1401 -1);
1402 if (dialog != NULL)
1404 gtk_window_present(GTK_WINDOW(dialog->window));
1405 return;
1408 dialog = g_new0(SubStatusEditor, 1);
1409 gtk_list_store_set(status_editor->model, &iter,
1410 STATUS_EDITOR_COLUMN_WINDOW, dialog,
1411 -1);
1412 dialog->status_editor = status_editor;
1413 dialog->account = account;
1415 tmp = g_strdup_printf(_("Status for %s"), purple_account_get_username(account));
1416 dialog->window = win = pidgin_create_dialog(tmp, PIDGIN_HIG_BORDER, "substatus", TRUE);
1417 g_free(tmp);
1419 g_signal_connect(G_OBJECT(win), "destroy",
1420 G_CALLBACK(substatus_editor_destroy_cb), dialog);
1422 /* Setup the vbox */
1423 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
1425 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1427 /* Status type */
1428 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
1429 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1431 label = gtk_label_new_with_mnemonic(_("_Status:"));
1432 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1433 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1434 gtk_size_group_add_widget(sg, label);
1436 dialog->model = gtk_list_store_new(STATUS_NUM_COLUMNS,
1437 G_TYPE_STRING,
1438 G_TYPE_STRING,
1439 G_TYPE_STRING);
1440 combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(dialog->model));
1441 dialog->box = GTK_COMBO_BOX(combo);
1443 rend = GTK_CELL_RENDERER(gtk_cell_renderer_pixbuf_new());
1444 g_object_set(G_OBJECT(rend),
1445 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL),
1446 NULL);
1447 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), rend, FALSE);
1448 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), rend,
1449 "stock-id", STATUS_COLUMN_ICON, NULL);
1451 rend = GTK_CELL_RENDERER(gtk_cell_renderer_text_new());
1452 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), rend, TRUE);
1453 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), rend,
1454 "text", STATUS_COLUMN_STATUS_NAME, NULL);
1456 g_signal_connect(G_OBJECT(combo), "changed",
1457 G_CALLBACK(substatus_selection_changed_cb), dialog);
1459 gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 0);
1461 /* Status mesage */
1462 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
1463 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1465 label = gtk_label_new_with_mnemonic(_("_Message:"));
1466 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1467 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1468 gtk_size_group_add_widget(sg, label);
1470 frame = pidgin_create_imhtml(TRUE, &text, &toolbar, NULL);
1471 dialog->message = GTK_IMHTML(text);
1472 dialog->toolbar = GTK_IMHTMLTOOLBAR(toolbar);
1473 gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
1475 /* Cancel button */
1476 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL,
1477 G_CALLBACK(substatus_editor_cancel_cb), dialog);
1479 /* OK button */
1480 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_OK,
1481 G_CALLBACK(substatus_editor_ok_cb), dialog);
1483 /* Seed the input widgets with the current values */
1485 /* Only look at the saved status if we can't find it in the parent status dialog's substatuses model */
1486 gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter,
1487 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, &parent_dialog_has_substatus, -1);
1488 if (parent_dialog_has_substatus) {
1489 gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter,
1490 STATUS_EDITOR_COLUMN_STATUS_ID, &status_id,
1491 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, &message, -1);
1492 } else if (status_editor->original_title != NULL) {
1493 PurpleSavedStatus *saved_status = NULL;
1494 PurpleSavedStatusSub *substatus = NULL;
1496 if ((saved_status = purple_savedstatus_find(status_editor->original_title)) != NULL) {
1497 if ((substatus = purple_savedstatus_get_substatus(saved_status, account)) != NULL) {
1498 message = (char *)purple_savedstatus_substatus_get_message(substatus);
1499 status_id = (char *)purple_status_type_get_id(purple_savedstatus_substatus_get_type(substatus));
1503 /* TODO: Else get the generic status type from our parent */
1505 if (message)
1506 gtk_imhtml_append_text(dialog->message, message, 0);
1508 for (list = purple_account_get_status_types(account); list; list = list->next)
1510 PurpleStatusType *status_type;
1511 const char *id, *name;
1512 PurpleStatusPrimitive prim;
1514 status_type = list->data;
1517 * Only allow users to select statuses that are flagged as
1518 * "user settable" and that aren't independent.
1520 if (!purple_status_type_is_user_settable(status_type) ||
1521 purple_status_type_is_independent(status_type))
1522 continue;
1524 id = purple_status_type_get_id(status_type);
1525 prim = purple_status_type_get_primitive(status_type);
1526 name = purple_status_type_get_name(status_type);
1528 gtk_list_store_append(dialog->model, &iter);
1529 gtk_list_store_set(dialog->model, &iter,
1530 STATUS_COLUMN_ICON, pidgin_stock_id_from_status_primitive(prim),
1531 STATUS_COLUMN_STATUS_ID, id,
1532 STATUS_COLUMN_STATUS_NAME, name,
1533 -1);
1534 if ((status_id != NULL) && purple_strequal(status_id, id))
1536 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter);
1537 select = TRUE;
1541 if (!select)
1542 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1544 if (parent_dialog_has_substatus) {
1545 /* These two were gotten from the parent tree model, so they need to be freed */
1546 g_free(status_id);
1547 g_free(message);
1550 gtk_widget_show_all(win);
1551 g_object_unref(sg);
1555 /**************************************************************************
1556 * Utilities *
1557 **************************************************************************/
1559 enum {
1560 SS_MENU_ENTRY_TYPE_PRIMITIVE,
1561 SS_MENU_ENTRY_TYPE_SAVEDSTATUS
1564 enum {
1565 /** _SSMenuEntryType */
1566 SS_MENU_TYPE_COLUMN,
1569 * This is a GdkPixbuf (the other columns are strings).
1570 * This column is visible.
1572 SS_MENU_ICON_COLUMN,
1574 /** The text displayed on the status box. This column is visible. */
1575 SS_MENU_TEXT_COLUMN,
1578 * This value depends on SS_MENU_TYPE_COLUMN. For _SAVEDSTATUS types,
1579 * this is the creation time. For _PRIMITIVE types,
1580 * this is the PurpleStatusPrimitive.
1582 SS_MENU_DATA_COLUMN,
1585 * This is the emblem to use for this status
1587 SS_MENU_EMBLEM_COLUMN,
1590 * And whether or not that emblem is visible
1592 SS_MENU_EMBLEM_VISIBLE_COLUMN,
1594 SS_MENU_NUM_COLUMNS
1597 static void
1598 status_menu_cb(GtkComboBox *widget, void(*callback)(PurpleSavedStatus*))
1600 GtkTreeIter iter;
1601 int type;
1602 gpointer data;
1603 PurpleSavedStatus *status = NULL;
1605 if (!gtk_combo_box_get_active_iter(widget, &iter))
1606 return;
1608 gtk_tree_model_get(gtk_combo_box_get_model(widget), &iter,
1609 SS_MENU_TYPE_COLUMN, &type,
1610 SS_MENU_DATA_COLUMN, &data,
1611 -1);
1613 if (type == SS_MENU_ENTRY_TYPE_PRIMITIVE)
1615 PurpleStatusPrimitive primitive = GPOINTER_TO_INT(data);
1616 status = purple_savedstatus_find_transient_by_type_and_message(primitive, NULL);
1617 if (status == NULL)
1618 status = purple_savedstatus_new(NULL, primitive);
1620 else if (type == SS_MENU_ENTRY_TYPE_SAVEDSTATUS)
1621 status = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
1623 callback(status);
1626 static gint
1627 saved_status_sort_alphabetically_func(gconstpointer a, gconstpointer b)
1629 const PurpleSavedStatus *saved_status_a = a;
1630 const PurpleSavedStatus *saved_status_b = b;
1631 return g_utf8_collate(purple_savedstatus_get_title(saved_status_a),
1632 purple_savedstatus_get_title(saved_status_b));
1635 static gboolean pidgin_status_menu_add_primitive(GtkListStore *model, GtkWidget *w, PurpleStatusPrimitive primitive,
1636 PurpleSavedStatus *current_status)
1638 GtkTreeIter iter;
1639 gboolean currently_selected = FALSE;
1641 gtk_list_store_append(model, &iter);
1642 gtk_list_store_set(model, &iter,
1643 SS_MENU_TYPE_COLUMN, SS_MENU_ENTRY_TYPE_PRIMITIVE,
1644 SS_MENU_ICON_COLUMN, pidgin_stock_id_from_status_primitive(primitive),
1645 SS_MENU_TEXT_COLUMN, purple_primitive_get_name_from_type(primitive),
1646 SS_MENU_DATA_COLUMN, GINT_TO_POINTER(primitive),
1647 SS_MENU_EMBLEM_VISIBLE_COLUMN, FALSE,
1648 -1);
1650 if (purple_savedstatus_is_transient(current_status)
1651 && !purple_savedstatus_has_substatuses(current_status)
1652 && purple_savedstatus_get_type(current_status) == primitive)
1653 currently_selected = TRUE;
1655 return currently_selected;
1658 static void
1659 pidgin_status_menu_update_iter(GtkWidget *combobox, GtkListStore *store, GtkTreeIter *iter,
1660 PurpleSavedStatus *status)
1662 PurpleStatusPrimitive primitive;
1664 if (store == NULL)
1665 store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox)));
1667 primitive = purple_savedstatus_get_type(status);
1668 gtk_list_store_set(store, iter,
1669 SS_MENU_TYPE_COLUMN, SS_MENU_ENTRY_TYPE_SAVEDSTATUS,
1670 SS_MENU_ICON_COLUMN, pidgin_stock_id_from_status_primitive(primitive),
1671 SS_MENU_TEXT_COLUMN, purple_savedstatus_get_title(status),
1672 SS_MENU_DATA_COLUMN, GINT_TO_POINTER(purple_savedstatus_get_creation_time(status)),
1673 SS_MENU_EMBLEM_COLUMN, GTK_STOCK_SAVE,
1674 SS_MENU_EMBLEM_VISIBLE_COLUMN, TRUE,
1675 -1);
1678 static gboolean
1679 pidgin_status_menu_find_iter(GtkListStore *store, GtkTreeIter *iter, PurpleSavedStatus *find)
1681 int type;
1682 gpointer data;
1683 time_t creation_time = purple_savedstatus_get_creation_time(find);
1684 GtkTreeModel *model = GTK_TREE_MODEL(store);
1686 if (!gtk_tree_model_get_iter_first(model, iter))
1687 return FALSE;
1689 do {
1690 gtk_tree_model_get(model, iter,
1691 SS_MENU_TYPE_COLUMN, &type,
1692 SS_MENU_DATA_COLUMN, &data,
1693 -1);
1694 if (type == SS_MENU_ENTRY_TYPE_PRIMITIVE)
1695 continue;
1696 if (GPOINTER_TO_INT(data) == creation_time)
1697 return TRUE;
1698 } while (gtk_tree_model_iter_next(model, iter));
1700 return FALSE;
1703 static void
1704 savedstatus_added_cb(PurpleSavedStatus *status, GtkWidget *combobox)
1706 GtkListStore *store;
1707 GtkTreeIter iter;
1709 if (purple_savedstatus_is_transient(status))
1710 return;
1712 store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox)));
1713 gtk_list_store_append(store, &iter);
1714 pidgin_status_menu_update_iter(combobox, store, &iter, status);
1717 static void
1718 savedstatus_deleted_cb(PurpleSavedStatus *status, GtkWidget *combobox)
1720 GtkListStore *store;
1721 GtkTreeIter iter;
1723 if (purple_savedstatus_is_transient(status))
1724 return;
1726 store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox)));
1727 if (pidgin_status_menu_find_iter(store, &iter, status))
1728 gtk_list_store_remove(store, &iter);
1731 static void
1732 savedstatus_modified_cb(PurpleSavedStatus *status, GtkWidget *combobox)
1734 GtkListStore *store;
1735 GtkTreeIter iter;
1737 if (purple_savedstatus_is_transient(status))
1738 return;
1740 store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox)));
1741 if (pidgin_status_menu_find_iter(store, &iter, status))
1742 pidgin_status_menu_update_iter(combobox, store, &iter, status);
1745 GtkWidget *pidgin_status_menu(PurpleSavedStatus *current_status, GCallback callback)
1747 GtkWidget *combobox;
1748 GtkListStore *model;
1749 GList *sorted, *cur;
1750 int i = 0;
1751 int index = -1;
1752 GtkTreeIter iter;
1753 GtkCellRenderer *text_rend;
1754 GtkCellRenderer *icon_rend;
1755 GtkCellRenderer *emblem_rend;
1757 model = gtk_list_store_new(SS_MENU_NUM_COLUMNS, G_TYPE_INT, G_TYPE_STRING,
1758 G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
1760 combobox = gtk_combo_box_new();
1762 if (pidgin_status_menu_add_primitive(model, combobox, PURPLE_STATUS_AVAILABLE, current_status))
1763 index = i;
1764 i++;
1766 if (pidgin_status_menu_add_primitive(model, combobox, PURPLE_STATUS_AWAY, current_status))
1767 index = i;
1768 i++;
1770 if (pidgin_status_menu_add_primitive(model, combobox, PURPLE_STATUS_INVISIBLE, current_status))
1771 index = i;
1772 i++;
1774 if (pidgin_status_menu_add_primitive(model, combobox, PURPLE_STATUS_OFFLINE, current_status))
1775 index = i;
1776 i++;
1778 sorted = g_list_copy((GList *)purple_savedstatuses_get_all());
1779 sorted = g_list_sort(sorted, saved_status_sort_alphabetically_func);
1780 for (cur = sorted; cur; cur = cur->next)
1782 PurpleSavedStatus *status = (PurpleSavedStatus *) cur->data;
1783 if (!purple_savedstatus_is_transient(status))
1785 gtk_list_store_append(model, &iter);
1787 pidgin_status_menu_update_iter(combobox, model, &iter, status);
1789 if (status == current_status)
1790 index = i;
1791 i++;
1794 g_list_free(sorted);
1796 gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(model));
1798 text_rend = gtk_cell_renderer_text_new();
1799 icon_rend = gtk_cell_renderer_pixbuf_new();
1800 emblem_rend = gtk_cell_renderer_pixbuf_new();
1801 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), icon_rend, FALSE);
1802 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), text_rend, TRUE);
1803 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), emblem_rend, FALSE);
1804 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox), icon_rend, "stock-id", SS_MENU_ICON_COLUMN, NULL);
1805 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox), text_rend, "markup", SS_MENU_TEXT_COLUMN, NULL);
1806 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox), emblem_rend,
1807 "stock-id", SS_MENU_EMBLEM_COLUMN, "visible", SS_MENU_EMBLEM_VISIBLE_COLUMN, NULL);
1808 g_object_set(G_OBJECT(icon_rend),
1809 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL),
1810 NULL);
1811 g_object_set(text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1813 gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), index);
1814 g_signal_connect(G_OBJECT(combobox), "changed", G_CALLBACK(status_menu_cb), callback);
1816 /* Make sure the list is updated dynamically when a substatus is changed/deleted
1817 * or a new one is added. */
1818 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-added",
1819 combobox, G_CALLBACK(savedstatus_added_cb), combobox);
1820 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-deleted",
1821 combobox, G_CALLBACK(savedstatus_deleted_cb), combobox);
1822 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-modified",
1823 combobox, G_CALLBACK(savedstatus_modified_cb), combobox);
1824 g_signal_connect(G_OBJECT(combobox), "destroy",
1825 G_CALLBACK(purple_signals_disconnect_by_handle), NULL);
1827 return combobox;
1831 /**************************************************************************
1832 * GTK+ saved status glue
1833 **************************************************************************/
1835 void *
1836 pidgin_status_get_handle(void)
1838 static int handle;
1840 return &handle;
1843 void
1844 pidgin_status_init(void)
1846 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/status");
1847 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/status/dialog");
1848 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/status/dialog/width", 550);
1849 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/status/dialog/height", 250);
1852 void
1853 pidgin_status_uninit(void)
1855 pidgin_status_window_hide();