Merged in default (pull request #594)
[pidgin-git.git] / pidgin / gtksavedstatuses.c
blob5c5774c98ce0132ec826a3621f2ca415bac07a2b
1 /* pidgin
3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
22 #include <talkatu.h>
24 #include "internal.h"
26 #include "account.h"
27 #include "notify.h"
28 #include "request.h"
29 #include "savedstatuses.h"
30 #include "status.h"
31 #include "util.h"
33 #include "gtkblist.h"
34 #include "pidgin.h"
35 #include "gtksavedstatuses.h"
36 #include "pidginstock.h"
37 #include "gtkutils.h"
39 #include "gtk3compat.h"
42 * TODO: Should attach to the account-deleted and account-added signals
43 * and update the GtkListStores in any StatusEditor windows that
44 * may be open.
48 * These are used for the GtkTreeView when you're scrolling through
49 * all your saved statuses.
51 enum
53 STATUS_WINDOW_COLUMN_TITLE,
54 STATUS_WINDOW_COLUMN_TYPE,
55 STATUS_WINDOW_COLUMN_MESSAGE,
56 /** A hidden column containing a pointer to the editor for this saved status. */
57 STATUS_WINDOW_COLUMN_WINDOW,
58 STATUS_WINDOW_COLUMN_ICON,
59 STATUS_WINDOW_NUM_COLUMNS
63 * These are used for the GtkTreeView containing the list of accounts
64 * at the bottom of the window when you're editing a particular
65 * saved status.
67 enum
69 /* A hidden column containing a pointer to the PurpleAccount. */
70 STATUS_EDITOR_COLUMN_ACCOUNT,
71 /* A hidden column containing a pointer to the editor for this substatus. */
72 STATUS_EDITOR_COLUMN_WINDOW,
73 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS,
74 STATUS_EDITOR_COLUMN_ICON,
75 STATUS_EDITOR_COLUMN_USERNAME,
76 /* A hidden column containing the ID of this PurpleStatusType. */
77 STATUS_EDITOR_COLUMN_STATUS_ID,
78 STATUS_EDITOR_COLUMN_STATUS_NAME,
79 STATUS_EDITOR_COLUMN_STATUS_MESSAGE,
80 STATUS_EDITOR_COLUMN_STATUS_ICON,
81 STATUS_EDITOR_NUM_COLUMNS
85 * These are used in the GtkComboBox to select the specific PurpleStatusType
86 * when setting a (sub)status for a particular saved status.
88 enum
90 STATUS_COLUMN_ICON,
91 /** A hidden column containing the ID of this PurpleStatusType. */
92 STATUS_COLUMN_STATUS_ID,
93 STATUS_COLUMN_STATUS_NAME,
94 STATUS_NUM_COLUMNS
97 typedef struct
99 GtkWidget *window;
100 GtkListStore *model;
101 GtkWidget *treeview;
102 GtkWidget *use_button;
103 GtkWidget *modify_button;
104 GtkWidget *delete_button;
105 } StatusWindow;
107 typedef struct
109 GtkWidget *window;
110 GtkListStore *model;
111 GtkWidget *treeview;
112 GtkButton *saveanduse_button;
113 GtkButton *save_button;
115 gchar *original_title;
116 GtkEntry *title;
117 GtkComboBox *type;
118 GtkWidget *message_view;
119 GtkTextBuffer *message_buffer;
120 } StatusEditor;
122 typedef struct
124 StatusEditor *status_editor;
125 PurpleAccount *account;
127 GtkWidget *window;
128 GtkListStore *model;
129 GtkComboBox *box;
130 GtkWidget *message_view;
131 GtkTextBuffer *message_buffer;
132 } SubStatusEditor;
134 static StatusWindow *status_window = NULL;
137 /**************************************************************************
138 * Status window
139 **************************************************************************/
141 static gboolean
142 status_window_find_savedstatus(GtkTreeIter *iter, const char *title)
144 GtkTreeModel *model;
145 char *cur;
147 if ((status_window == NULL) || (title == NULL))
148 return FALSE;
150 model = GTK_TREE_MODEL(status_window->model);
152 if (!gtk_tree_model_get_iter_first(model, iter))
153 return FALSE;
155 do {
156 gtk_tree_model_get(model, iter, STATUS_WINDOW_COLUMN_TITLE, &cur, -1);
157 if (purple_strequal(title, cur))
159 g_free(cur);
160 return TRUE;
162 g_free(cur);
163 } while (gtk_tree_model_iter_next(model, iter));
165 return FALSE;
168 static gboolean
169 status_window_destroy_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
171 StatusWindow *dialog = user_data;
173 dialog->window = NULL;
174 pidgin_status_window_hide();
176 return FALSE;
179 static void
180 status_window_use_cb(GtkButton *button, StatusWindow *dialog)
182 GtkTreeSelection *selection;
183 GtkTreeIter iter;
184 GList *list = NULL;
185 int num_selected = 0;
187 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
189 num_selected = gtk_tree_selection_count_selected_rows(selection);
190 if (num_selected != 1)
192 * This shouldn't happen because the "Use" button should have
193 * been grayed out. Oh well.
195 return;
197 list = gtk_tree_selection_get_selected_rows(selection, NULL);
199 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model),
200 &iter, list->data))
202 gchar *title;
203 PurpleSavedStatus *saved_status;
204 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
205 STATUS_WINDOW_COLUMN_TITLE, &title,
206 -1);
207 saved_status = purple_savedstatus_find(title);
208 g_free(title);
209 purple_savedstatus_activate(saved_status);
212 g_list_free_full(list, (GDestroyNotify)gtk_tree_path_free);
215 static void
216 status_window_add_cb(GtkButton *button, gpointer user_data)
218 pidgin_status_editor_show(FALSE, NULL);
221 static void
222 status_window_modify_foreach(GtkTreeModel *model, GtkTreePath *path,
223 GtkTreeIter *iter, gpointer user_data)
225 gchar *title;
226 PurpleSavedStatus *saved_status;
228 gtk_tree_model_get(model, iter, STATUS_WINDOW_COLUMN_TITLE, &title, -1);
229 saved_status = purple_savedstatus_find(title);
230 g_free(title);
231 pidgin_status_editor_show(TRUE, saved_status);
234 static void
235 status_window_modify_cb(GtkButton *button, gpointer user_data)
237 StatusWindow *dialog = user_data;
238 GtkTreeSelection *selection;
240 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
242 gtk_tree_selection_selected_foreach(selection, status_window_modify_foreach, user_data);
245 static void
246 status_window_delete_cancel_cb(gpointer data)
248 GList *sel_titles = data;
249 g_list_free_full(sel_titles, g_free);
252 static void
253 status_window_delete_confirm_cb(gpointer data)
255 GtkTreeIter iter;
256 GList *sel_titles = data, *l;
257 char *title;
259 for (l = sel_titles; l != NULL; l = l->next) {
260 title = l->data;
261 if (purple_savedstatus_find(title) != purple_savedstatus_get_current()) {
262 if (status_window_find_savedstatus(&iter, title))
263 gtk_list_store_remove(status_window->model, &iter);
264 purple_savedstatus_delete(title);
266 g_free(title);
268 g_list_free(sel_titles);
271 static void
272 status_window_delete_cb(GtkButton *button, gpointer user_data)
274 StatusWindow *dialog = user_data;
275 GtkTreeIter iter;
276 GtkTreeSelection *selection;
277 GList *sel_paths, *l, *sel_titles = NULL;
278 GtkTreeModel *model = GTK_TREE_MODEL(dialog->model);
279 char *title;
280 gpointer handle;
282 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
283 sel_paths = gtk_tree_selection_get_selected_rows(selection, NULL);
285 /* This is ugly because we're not allowed to modify the model from within
286 * gtk_tree_selection_selected_foreach() and the GtkTreePaths can become invalid
287 * when something is removed from the model. The selection can also change while
288 * the request dialog is displayed, so we need to capture the selected rows at this time. */
290 for (l = sel_paths; l != NULL; l = l->next) {
291 if (gtk_tree_model_get_iter(model, &iter, l->data)) {
292 gtk_tree_model_get(model, &iter, STATUS_WINDOW_COLUMN_TITLE, &title, -1);
293 sel_titles = g_list_prepend(sel_titles, title);
295 gtk_tree_path_free(l->data);
297 g_list_free(sel_paths);
299 g_return_if_fail(sel_titles != NULL);
300 if (!sel_titles->next) {
301 title = g_strdup_printf(_("Are you sure you want to delete %s?"),
302 (const gchar *)sel_titles->data);
303 handle = purple_savedstatus_find(sel_titles->data);
304 } else {
305 title = g_strdup(_("Are you sure you want to delete the selected saved statuses?"));
306 handle = dialog;
309 purple_request_action(handle, NULL, title, NULL, 0,
310 NULL,
311 sel_titles, 2,
312 _("Delete"), status_window_delete_confirm_cb,
313 _("Cancel"), status_window_delete_cancel_cb);
315 g_free(title);
318 static void
319 status_window_close_cb(GtkButton *button, gpointer user_data)
321 pidgin_status_window_hide();
324 static void
325 status_selected_cb(GtkTreeSelection *sel, gpointer user_data)
327 StatusWindow *dialog = user_data;
328 GList *sel_paths, *tmp;
329 gboolean can_use = TRUE, can_delete = TRUE;
330 int num_selected;
331 GtkTreeModel *model = GTK_TREE_MODEL(dialog->model);
333 sel_paths = gtk_tree_selection_get_selected_rows(sel, NULL);
335 for (tmp = sel_paths, num_selected = 0; tmp; tmp = tmp->next, num_selected++) {
336 GtkTreeIter iter;
337 char *title;
339 if (gtk_tree_model_get_iter(model, &iter, tmp->data)) {
340 gtk_tree_model_get(model, &iter,
341 STATUS_WINDOW_COLUMN_TITLE, &title, -1);
342 if (purple_savedstatus_find(title) == purple_savedstatus_get_current()) {
343 can_use = can_delete = FALSE;
346 g_free(title);
349 gtk_tree_path_free(tmp->data);
352 gtk_widget_set_sensitive(dialog->use_button, (num_selected == 1) && can_use);
353 gtk_widget_set_sensitive(dialog->modify_button, (num_selected > 0));
354 gtk_widget_set_sensitive(dialog->delete_button, num_selected > 0 && can_delete);
356 g_list_free(sel_paths);
359 static const gchar *
360 get_stock_icon_from_primitive(PurpleStatusPrimitive type)
362 return pidgin_stock_id_from_status_primitive(type);
365 static void
366 add_status_to_saved_status_list(GtkListStore *model, PurpleSavedStatus *saved_status)
368 GtkTreeIter iter;
369 const char *title;
370 const char *type;
371 const gchar *icon;
372 char *message;
374 if (purple_savedstatus_is_transient(saved_status))
375 return;
377 title = purple_savedstatus_get_title(saved_status);
378 type = purple_primitive_get_name_from_type(purple_savedstatus_get_primitive_type(saved_status));
379 message = purple_markup_strip_html(purple_savedstatus_get_message(saved_status));
380 icon = get_stock_icon_from_primitive(purple_savedstatus_get_primitive_type(saved_status));
382 gtk_list_store_append(model, &iter);
383 gtk_list_store_set(model, &iter,
384 STATUS_WINDOW_COLUMN_ICON, icon,
385 STATUS_WINDOW_COLUMN_TITLE, title,
386 STATUS_WINDOW_COLUMN_TYPE, type,
387 STATUS_WINDOW_COLUMN_MESSAGE, message,
388 -1);
389 g_free(message);
392 static void
393 populate_saved_status_list(StatusWindow *dialog)
395 GList *saved_statuses;
397 gtk_list_store_clear(dialog->model);
399 for (saved_statuses = purple_savedstatuses_get_all(); saved_statuses != NULL;
400 saved_statuses = g_list_next(saved_statuses))
402 add_status_to_saved_status_list(dialog->model, saved_statuses->data);
406 static gboolean
407 search_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data)
409 gboolean result;
410 char *haystack;
412 gtk_tree_model_get(model, iter, column, &haystack, -1);
414 result = (purple_strcasestr(haystack, key) == NULL);
416 g_free(haystack);
418 return result;
421 static void
422 savedstatus_activated_cb(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, StatusWindow *dialog)
424 status_window_use_cb(NULL, dialog);
425 status_window_close_cb(NULL, dialog);
428 static void
429 saved_status_updated_cb(PurpleSavedStatus *status, StatusWindow *sw)
431 populate_saved_status_list(sw);
434 static GtkWidget *
435 create_saved_status_list(StatusWindow *dialog)
437 GtkWidget *treeview;
438 GtkTreeSelection *sel;
439 GtkTreeViewColumn *column;
440 GtkCellRenderer *renderer;
442 /* Create the list model */
443 dialog->model = gtk_list_store_new(STATUS_WINDOW_NUM_COLUMNS,
444 G_TYPE_STRING,
445 G_TYPE_STRING,
446 G_TYPE_STRING,
447 G_TYPE_POINTER,
448 G_TYPE_STRING);
450 /* Create the treeview */
451 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
452 dialog->treeview = treeview;
453 g_signal_connect(G_OBJECT(treeview), "row-activated",
454 G_CALLBACK(savedstatus_activated_cb), dialog);
456 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
457 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
458 g_signal_connect(G_OBJECT(sel), "changed",
459 G_CALLBACK(status_selected_cb), dialog);
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 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
475 column = gtk_tree_view_column_new();
476 gtk_tree_view_column_set_title(column, _("Type"));
477 gtk_tree_view_column_set_resizable(column, TRUE);
478 gtk_tree_view_column_set_sort_column_id(column,
479 STATUS_WINDOW_COLUMN_TYPE);
480 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
481 renderer = gtk_cell_renderer_pixbuf_new();
482 gtk_tree_view_column_pack_start(column, renderer, TRUE);
483 gtk_tree_view_column_add_attribute(column, renderer, "stock-id",
484 STATUS_WINDOW_COLUMN_ICON);
485 renderer = gtk_cell_renderer_text_new();
486 gtk_tree_view_column_pack_start(column, renderer, TRUE);
487 gtk_tree_view_column_add_attribute(column, renderer, "text",
488 STATUS_WINDOW_COLUMN_TYPE);
490 column = gtk_tree_view_column_new();
491 gtk_tree_view_column_set_title(column, _("Message"));
492 gtk_tree_view_column_set_resizable(column, TRUE);
493 gtk_tree_view_column_set_sort_column_id(column,
494 STATUS_WINDOW_COLUMN_MESSAGE);
495 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
496 renderer = gtk_cell_renderer_text_new();
497 gtk_tree_view_column_pack_start(column, renderer, TRUE);
498 gtk_tree_view_column_add_attribute(column, renderer, "text",
499 STATUS_WINDOW_COLUMN_MESSAGE);
500 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
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);
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);
511 /* Populate list */
512 populate_saved_status_list(dialog);
514 gtk_widget_show_all(treeview);
516 return pidgin_make_scrollable(treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1);
519 static gboolean
520 configure_cb(GtkWidget *widget, GdkEventConfigure *event, StatusWindow *dialog)
522 if (gtk_widget_get_visible(widget))
524 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/status/dialog/width", event->width);
525 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/status/dialog/height", event->height);
528 return FALSE;
531 static void
532 current_status_changed(PurpleSavedStatus *old, PurpleSavedStatus *new_status,
533 StatusWindow *dialog)
535 status_selected_cb(gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview)), dialog);
538 void
539 pidgin_status_window_show(void)
541 StatusWindow *dialog;
542 GtkWidget *bbox;
543 GtkWidget *button;
544 GtkWidget *list;
545 GtkWidget *vbox;
546 GtkWidget *win;
547 int width, height;
549 if (status_window != NULL)
551 gtk_window_present(GTK_WINDOW(status_window->window));
552 return;
555 status_window = dialog = g_new0(StatusWindow, 1);
557 width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/width");
558 height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/height");
560 dialog->window = win = pidgin_create_dialog(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE);
561 gtk_window_set_default_size(GTK_WINDOW(win), width, height);
563 g_signal_connect(G_OBJECT(win), "delete_event",
564 G_CALLBACK(status_window_destroy_cb), dialog);
565 g_signal_connect(G_OBJECT(win), "configure_event",
566 G_CALLBACK(configure_cb), dialog);
568 /* Setup the vbox */
569 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
571 /* List of saved status states */
572 list = create_saved_status_list(dialog);
573 gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0);
575 /* Button box. */
576 bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win));
578 /* Use button */
579 button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE,
580 PIDGIN_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);
585 g_signal_connect(G_OBJECT(button), "clicked",
586 G_CALLBACK(status_window_use_cb), dialog);
588 /* Add button */
589 pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_ADD,
590 G_CALLBACK(status_window_add_cb), dialog);
592 /* Modify button */
593 button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY,
594 G_CALLBACK(status_window_modify_cb), dialog);
595 dialog->modify_button = button;
596 gtk_widget_set_sensitive(button, FALSE);
598 /* Delete button */
599 button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE,
600 G_CALLBACK(status_window_delete_cb), dialog);
601 dialog->delete_button = button;
602 gtk_widget_set_sensitive(button, FALSE);
604 /* Close button */
605 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE,
606 G_CALLBACK(status_window_close_cb), dialog);
608 purple_signal_connect(purple_savedstatuses_get_handle(),
609 "savedstatus-changed", status_window,
610 PURPLE_CALLBACK(current_status_changed), dialog);
611 purple_signal_connect(purple_savedstatuses_get_handle(),
612 "savedstatus-added", status_window,
613 PURPLE_CALLBACK(saved_status_updated_cb), dialog);
614 purple_signal_connect(purple_savedstatuses_get_handle(),
615 "savedstatus-deleted", status_window,
616 PURPLE_CALLBACK(saved_status_updated_cb), dialog);
617 purple_signal_connect(purple_savedstatuses_get_handle(),
618 "savedstatus-modified", status_window,
619 PURPLE_CALLBACK(saved_status_updated_cb), dialog);
621 gtk_widget_show_all(win);
624 void
625 pidgin_status_window_hide(void)
627 if (status_window == NULL)
628 return;
630 if (status_window->window != NULL)
631 gtk_widget_destroy(status_window->window);
633 purple_request_close_with_handle(status_window);
634 purple_notify_close_with_handle(status_window);
635 purple_signals_disconnect_by_handle(status_window);
636 g_object_unref(G_OBJECT(status_window->model));
637 g_free(status_window);
638 status_window = NULL;
642 /**************************************************************************
643 * Status editor
644 **************************************************************************/
646 static void substatus_editor_cancel_cb(GtkButton *button, gpointer user_data);
648 static void
649 status_editor_remove_dialog(StatusEditor *dialog)
651 GtkTreeModel *model;
652 GtkTreeIter iter;
654 /* Remove the reference to this dialog from our parent's list store */
655 if (status_window_find_savedstatus(&iter, dialog->original_title))
657 gtk_list_store_set(status_window->model, &iter,
658 STATUS_WINDOW_COLUMN_WINDOW, NULL,
659 -1);
662 /* Close any substatus editors that may be open */
663 model = GTK_TREE_MODEL(dialog->model);
664 if (gtk_tree_model_get_iter_first(model, &iter))
666 do {
667 SubStatusEditor *substatus_dialog;
669 gtk_tree_model_get(model, &iter,
670 STATUS_EDITOR_COLUMN_WINDOW, &substatus_dialog,
671 -1);
672 if (substatus_dialog != NULL)
674 gtk_list_store_set(dialog->model, &iter,
675 STATUS_EDITOR_COLUMN_WINDOW, NULL,
676 -1);
677 substatus_editor_cancel_cb(NULL, substatus_dialog);
679 } while (gtk_tree_model_iter_next(model, &iter));
684 static void
685 status_editor_destroy_cb(GtkWidget *widget, gpointer user_data)
687 StatusEditor *dialog = user_data;
689 status_editor_remove_dialog(dialog);
690 g_free(dialog->original_title);
691 g_object_unref(G_OBJECT(dialog->model));
692 g_free(dialog);
695 static void
696 status_editor_cancel_cb(GtkButton *button, gpointer user_data)
698 StatusEditor *dialog = user_data;
699 gtk_widget_destroy(dialog->window);
702 static void
703 status_editor_ok_cb(GtkButton *button, gpointer user_data)
705 StatusEditor *dialog = user_data;
706 const char *title;
707 PurpleStatusPrimitive type;
708 char *message, *unformatted;
709 PurpleSavedStatus *saved_status = NULL;
710 GtkTreeModel *model;
711 GtkTreeIter iter;
713 title = gtk_entry_get_text(dialog->title);
716 * If we're saving this status, and the title is already taken
717 * then show an error dialog and don't do anything.
719 if (((button == dialog->saveanduse_button) || (button == dialog->save_button)) &&
720 (purple_savedstatus_find(title) != NULL) &&
721 ((dialog->original_title == NULL) || (!purple_strequal(title, dialog->original_title))))
723 purple_notify_error(status_window, NULL, _("Title already in use. You must "
724 "choose a unique title."), NULL, NULL);
725 return;
728 type = gtk_combo_box_get_active(dialog->type) + (PURPLE_STATUS_UNSET + 1);
729 message = talkatu_markup_get_html(dialog->message_buffer, NULL);
730 unformatted = purple_markup_strip_html(message);
733 * If we're editing an old status, then lookup the old status.
734 * Note: It is possible that it has been deleted or renamed
735 * or something, and no longer exists.
737 if (dialog->original_title != NULL)
739 GtkTreeIter iter;
741 saved_status = purple_savedstatus_find(dialog->original_title);
743 if (status_window_find_savedstatus(&iter, dialog->original_title))
744 gtk_list_store_remove(status_window->model, &iter);
747 if (saved_status == NULL)
749 /* This is a new status */
750 if ((button == dialog->saveanduse_button)
751 || (button == dialog->save_button))
752 saved_status = purple_savedstatus_new(title, type);
753 else
754 saved_status = purple_savedstatus_new(NULL, type);
756 else
758 /* Modify the old status */
759 if (!purple_strequal(title, dialog->original_title))
760 purple_savedstatus_set_title(saved_status, title);
761 purple_savedstatus_set_primitive_type(saved_status, type);
764 if (*unformatted == '\0')
765 purple_savedstatus_set_message(saved_status, NULL);
766 else
767 purple_savedstatus_set_message(saved_status, message);
769 /* Set any substatuses */
770 model = GTK_TREE_MODEL(dialog->model);
771 if (gtk_tree_model_get_iter_first(model, &iter))
773 do {
774 PurpleAccount *account;
775 gboolean enabled;
776 char *id;
777 char *message;
778 PurpleStatusType *type;
780 gtk_tree_model_get(model, &iter,
781 STATUS_EDITOR_COLUMN_ACCOUNT, &account,
782 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, &enabled,
783 STATUS_EDITOR_COLUMN_STATUS_ID, &id,
784 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, &message,
785 -1);
786 if (enabled)
788 type = purple_account_get_status_type(account, id);
789 purple_savedstatus_set_substatus(saved_status, account, type, message);
791 else
793 purple_savedstatus_unset_substatus(saved_status, account);
795 g_free(id);
796 g_free(message);
797 } while (gtk_tree_model_iter_next(model, &iter));
800 g_free(message);
801 g_free(unformatted);
803 /* If they clicked on "Save and Use" or "Use," then activate the status */
804 if (button != dialog->save_button)
805 purple_savedstatus_activate(saved_status);
807 gtk_widget_destroy(dialog->window);
810 static void
811 editor_title_changed_cb(GtkWidget *widget, gpointer user_data)
813 StatusEditor *dialog = user_data;
814 const gchar *text;
816 text = gtk_entry_get_text(dialog->title);
818 gtk_widget_set_sensitive(GTK_WIDGET(dialog->saveanduse_button), (*text != '\0'));
819 gtk_widget_set_sensitive(GTK_WIDGET(dialog->save_button), (*text != '\0'));
822 static GtkWidget *
823 create_status_type_menu(PurpleStatusPrimitive type)
825 int i;
826 GtkWidget *dropdown;
827 GtkListStore *store;
828 GtkTreeIter iter;
829 GtkCellRenderer *renderer;
831 store = gtk_list_store_new(STATUS_NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
833 for (i = PURPLE_STATUS_UNSET + 1; i < PURPLE_STATUS_NUM_PRIMITIVES; i++)
835 /* Someone should fix this for 3.0.0. The independent boolean
836 * should probably be set on the status type, not the status.
837 * I guess that would prevent third party plugins from creating
838 * independent statuses?
840 if (i == PURPLE_STATUS_MOBILE ||
841 i == PURPLE_STATUS_MOOD ||
842 i == PURPLE_STATUS_TUNE)
844 * Special-case these. They're intended to be independent
845 * status types, so don't show them in the list.
847 continue;
849 gtk_list_store_append(store, &iter);
850 gtk_list_store_set(store, &iter,
851 STATUS_COLUMN_ICON, get_stock_icon_from_primitive(i),
852 STATUS_COLUMN_STATUS_ID, purple_primitive_get_id_from_type(i),
853 STATUS_COLUMN_STATUS_NAME, purple_primitive_get_name_from_type(i),
854 -1);
857 dropdown = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
859 renderer = gtk_cell_renderer_pixbuf_new();
860 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(dropdown), renderer, FALSE);
861 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(dropdown), renderer,
862 "stock-id", STATUS_COLUMN_ICON,
863 NULL);
865 renderer = gtk_cell_renderer_text_new();
866 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(dropdown), renderer, TRUE);
867 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(dropdown), renderer,
868 "text", STATUS_COLUMN_STATUS_NAME,
869 NULL);
871 gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown),
872 type - (PURPLE_STATUS_UNSET + 1));
874 return dropdown;
877 static void edit_substatus(StatusEditor *status_editor, PurpleAccount *account);
879 static void
880 edit_substatus_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col, gpointer user_data)
882 StatusEditor *dialog = user_data;
883 GtkTreeIter iter;
884 PurpleAccount *account;
886 gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path);
887 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
888 STATUS_EDITOR_COLUMN_ACCOUNT, &account,
889 -1);
891 edit_substatus(dialog, account);
894 static void
895 status_editor_substatus_cb(GtkCellRendererToggle *renderer, gchar *path_str, gpointer data)
897 StatusEditor *dialog = (StatusEditor *)data;
898 GtkTreeIter iter;
899 gboolean enabled;
900 PurpleAccount *account;
902 gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(dialog->model), &iter, path_str);
903 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
904 STATUS_EDITOR_COLUMN_ACCOUNT, &account,
905 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, &enabled,
906 -1);
908 enabled = !enabled;
910 if (enabled)
912 edit_substatus(dialog, account);
914 else
916 /* Remove the substatus */
917 gtk_list_store_set(dialog->model, &iter,
918 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, enabled,
919 STATUS_EDITOR_COLUMN_STATUS_ID, NULL,
920 STATUS_EDITOR_COLUMN_STATUS_NAME, NULL,
921 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, NULL,
922 STATUS_EDITOR_COLUMN_STATUS_ICON, NULL,
923 -1);
927 static void
928 status_editor_add_columns(StatusEditor *dialog)
930 GtkCellRenderer *renderer;
931 GtkTreeViewColumn *column;
933 /* Enable Different status column */
934 renderer = gtk_cell_renderer_toggle_new();
935 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(dialog->treeview),
936 -1, _("Different"),
937 renderer,
938 "active", STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS,
939 NULL);
940 g_signal_connect(G_OBJECT(renderer), "toggled",
941 G_CALLBACK(status_editor_substatus_cb), dialog);
943 /* Username column */
944 column = gtk_tree_view_column_new();
945 gtk_tree_view_column_set_resizable(column, TRUE);
946 gtk_tree_view_column_set_title(column, _("Username"));
947 gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
948 gtk_tree_view_column_set_resizable(column, TRUE);
950 /* Icon */
951 renderer = gtk_cell_renderer_pixbuf_new();
952 gtk_tree_view_column_pack_start(column, renderer, FALSE);
953 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf",
954 STATUS_EDITOR_COLUMN_ICON);
956 /* Username */
957 renderer = gtk_cell_renderer_text_new();
958 gtk_tree_view_column_pack_start(column, renderer, TRUE);
959 gtk_tree_view_column_add_attribute(column, renderer, "text",
960 STATUS_EDITOR_COLUMN_USERNAME);
962 /* Status column */
963 column = gtk_tree_view_column_new();
964 gtk_tree_view_column_set_resizable(column, TRUE);
965 gtk_tree_view_column_set_title(column, _("Status"));
966 gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
967 gtk_tree_view_column_set_resizable(column, TRUE);
968 renderer = gtk_cell_renderer_pixbuf_new();
969 gtk_tree_view_column_pack_start(column, renderer, FALSE);
970 gtk_tree_view_column_add_attribute(column, renderer, "stock-id",
971 STATUS_EDITOR_COLUMN_STATUS_ICON);
972 renderer = gtk_cell_renderer_text_new();
973 gtk_tree_view_column_pack_start(column, renderer, TRUE);
974 gtk_tree_view_column_add_attribute(column, renderer, "text",
975 STATUS_EDITOR_COLUMN_STATUS_NAME);
977 /* Message column */
978 column = gtk_tree_view_column_new();
979 gtk_tree_view_column_set_resizable(column, TRUE);
980 gtk_tree_view_column_set_title(column, _("Message"));
981 gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
982 gtk_tree_view_column_set_resizable(column, TRUE);
983 renderer = gtk_cell_renderer_text_new();
984 gtk_tree_view_column_pack_start(column, renderer, TRUE);
985 gtk_tree_view_column_add_attribute(column, renderer, "text",
986 STATUS_EDITOR_COLUMN_STATUS_MESSAGE);
988 g_signal_connect(G_OBJECT(dialog->treeview), "row-activated",
989 G_CALLBACK(edit_substatus_cb), dialog);
992 static void
993 status_editor_set_account(GtkListStore *store, PurpleAccount *account,
994 GtkTreeIter *iter, PurpleSavedStatusSub *substatus)
996 GdkPixbuf *pixbuf;
997 const char *id = NULL, *name = NULL, *message = NULL;
998 PurpleStatusPrimitive prim = PURPLE_STATUS_UNSET;
1000 pixbuf = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_MEDIUM);
1001 if ((pixbuf != NULL) && !purple_account_is_connected(account))
1003 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
1006 if (substatus != NULL)
1008 const PurpleStatusType *type;
1010 type = purple_savedstatus_substatus_get_status_type(substatus);
1011 id = purple_status_type_get_id(type);
1012 name = purple_status_type_get_name(type);
1013 prim = purple_status_type_get_primitive(type);
1014 if (purple_status_type_get_attr(type, "message"))
1015 message = purple_savedstatus_substatus_get_message(substatus);
1018 gtk_list_store_set(store, iter,
1019 STATUS_EDITOR_COLUMN_ACCOUNT, account,
1020 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, (substatus != NULL),
1021 STATUS_EDITOR_COLUMN_ICON, pixbuf,
1022 STATUS_EDITOR_COLUMN_USERNAME, purple_account_get_username(account),
1023 STATUS_EDITOR_COLUMN_STATUS_ID, id,
1024 STATUS_EDITOR_COLUMN_STATUS_NAME, name,
1025 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, message,
1026 STATUS_EDITOR_COLUMN_STATUS_ICON, get_stock_icon_from_primitive(prim),
1027 -1);
1029 if (pixbuf != NULL)
1030 g_object_unref(G_OBJECT(pixbuf));
1033 static void
1034 status_editor_add_account(StatusEditor *dialog, PurpleAccount *account,
1035 PurpleSavedStatusSub *substatus)
1037 GtkTreeIter iter;
1039 gtk_list_store_append(dialog->model, &iter);
1041 status_editor_set_account(dialog->model, account, &iter, substatus);
1044 static void
1045 status_editor_populate_list(StatusEditor *dialog, PurpleSavedStatus *saved_status)
1047 GList *iter;
1048 PurpleSavedStatusSub *substatus;
1050 gtk_list_store_clear(dialog->model);
1052 for (iter = purple_accounts_get_all(); iter != NULL; iter = iter->next)
1054 PurpleAccount *account = (PurpleAccount *)iter->data;
1056 if (saved_status != NULL)
1057 substatus = purple_savedstatus_get_substatus(saved_status, account);
1058 else
1059 substatus = NULL;
1061 status_editor_add_account(dialog, account, substatus);
1065 void
1066 pidgin_status_editor_show(gboolean edit, PurpleSavedStatus *saved_status)
1068 GtkTreeIter iter;
1069 StatusEditor *dialog;
1070 GtkSizeGroup *sg;
1071 GtkWidget *bbox;
1072 GtkWidget *button;
1073 GtkWidget *dbox;
1074 GtkWidget *expander;
1075 GtkWidget *dropdown;
1076 GtkWidget *entry;
1077 GtkWidget *editor;
1078 GtkWidget *hbox;
1079 GtkWidget *vbox;
1080 GtkWidget *win;
1081 GList *focus_chain = NULL;
1083 if (edit)
1085 g_return_if_fail(saved_status != NULL);
1086 g_return_if_fail(!purple_savedstatus_is_transient(saved_status));
1089 /* Find a possible window for this saved status and present it */
1090 if (edit && status_window_find_savedstatus(&iter, purple_savedstatus_get_title(saved_status)))
1092 gtk_tree_model_get(GTK_TREE_MODEL(status_window->model), &iter,
1093 STATUS_WINDOW_COLUMN_WINDOW, &dialog,
1094 -1);
1095 if (dialog != NULL)
1097 gtk_window_present(GTK_WINDOW(dialog->window));
1098 return;
1102 dialog = g_new0(StatusEditor, 1);
1103 if (edit && status_window_find_savedstatus(&iter, purple_savedstatus_get_title(saved_status)))
1105 gtk_list_store_set(status_window->model, &iter,
1106 STATUS_WINDOW_COLUMN_WINDOW, dialog,
1107 -1);
1110 if (edit)
1111 dialog->original_title = g_strdup(purple_savedstatus_get_title(saved_status));
1113 dialog->window = win = pidgin_create_dialog(_("Status"), PIDGIN_HIG_BORDER, "status", TRUE);
1115 g_signal_connect(G_OBJECT(win), "destroy",
1116 G_CALLBACK(status_editor_destroy_cb), dialog);
1118 /* Setup the vbox */
1119 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
1121 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1123 /* Title */
1124 entry = gtk_entry_new();
1125 dialog->title = GTK_ENTRY(entry);
1126 if ((saved_status != NULL)
1127 && !purple_savedstatus_is_transient(saved_status)
1128 && (purple_savedstatus_get_title(saved_status) != NULL))
1129 gtk_entry_set_text(GTK_ENTRY(entry), purple_savedstatus_get_title(saved_status));
1130 g_signal_connect(G_OBJECT(entry), "changed",
1131 G_CALLBACK(editor_title_changed_cb), dialog);
1132 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Title:"), sg, entry, TRUE, NULL);
1134 /* Status type */
1135 if (saved_status != NULL)
1136 dropdown = create_status_type_menu(purple_savedstatus_get_primitive_type(saved_status));
1137 else
1138 dropdown = create_status_type_menu(PURPLE_STATUS_AWAY);
1139 dialog->type = GTK_COMBO_BOX(dropdown);
1140 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Status:"), sg, dropdown, TRUE, NULL);
1142 /* Status message */
1143 editor = talkatu_editor_new();
1145 dialog->message_view = talkatu_editor_get_view(TALKATU_EDITOR(editor));
1146 hbox = pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Message:"), sg, editor, TRUE, NULL);
1148 dialog->message_buffer = talkatu_html_buffer_new();
1149 gtk_text_view_set_buffer(GTK_TEXT_VIEW(dialog->message_view), dialog->message_buffer);
1151 gtk_container_child_set(GTK_CONTAINER(vbox), hbox, "expand", TRUE, "fill", TRUE, NULL);
1152 focus_chain = g_list_prepend(focus_chain, dialog->message_view);
1153 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), focus_chain);
1154 g_list_free(focus_chain);
1156 if ((saved_status != NULL) && (purple_savedstatus_get_message(saved_status) != NULL)) {
1157 talkatu_markup_set_html(
1158 TALKATU_BUFFER(dialog->message_buffer),
1159 purple_savedstatus_get_message(saved_status),
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_box_new(GTK_ORIENTATION_VERTICAL, 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_widget_set_size_request(dialog->treeview, -1, 150);
1187 gtk_box_pack_start(GTK_BOX(dbox),
1188 pidgin_make_scrollable(dialog->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1189 TRUE, TRUE, 0);
1191 /* Add columns */
1192 status_editor_add_columns(dialog);
1194 /* Populate list */
1195 status_editor_populate_list(dialog, saved_status);
1197 /* Expand the treeview if we have substatuses */
1198 gtk_expander_set_expanded(GTK_EXPANDER(expander),
1199 (saved_status != NULL) && purple_savedstatus_has_substatuses(saved_status));
1201 /* Button box */
1202 bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win));
1203 gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
1204 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
1206 /* Cancel button */
1207 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL,
1208 G_CALLBACK(status_editor_cancel_cb), dialog);
1210 /* Use button */
1211 button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE,
1212 PIDGIN_BUTTON_HORIZONTAL);
1213 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1214 g_signal_connect(G_OBJECT(button), "clicked",
1215 G_CALLBACK(status_editor_ok_cb), dialog);
1217 /* Save and Use button */
1218 button = pidgin_pixbuf_button_from_stock(_("Sa_ve and Use"), GTK_STOCK_OK,
1219 PIDGIN_BUTTON_HORIZONTAL);
1220 dialog->saveanduse_button = GTK_BUTTON(button);
1221 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1222 if (dialog->original_title == NULL)
1223 gtk_widget_set_sensitive(button, FALSE);
1224 g_signal_connect(G_OBJECT(button), "clicked",
1225 G_CALLBACK(status_editor_ok_cb), dialog);
1227 /* Save button */
1228 button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_SAVE,
1229 G_CALLBACK(status_editor_ok_cb), dialog);
1230 if (dialog->original_title == NULL)
1231 gtk_widget_set_sensitive(button, FALSE);
1232 dialog->save_button = GTK_BUTTON(button);
1234 gtk_widget_show_all(win);
1235 g_object_unref(sg);
1239 /**************************************************************************
1240 * SubStatus editor
1241 **************************************************************************/
1243 static void
1244 substatus_selection_changed_cb(GtkComboBox *box, gpointer user_data)
1246 SubStatusEditor *select = user_data;
1247 GtkTreeIter iter;
1248 char *id;
1249 PurpleStatusType *type;
1251 if (!gtk_combo_box_get_active_iter(box, &iter))
1252 return;
1253 gtk_tree_model_get(GTK_TREE_MODEL(select->model), &iter,
1254 STATUS_COLUMN_STATUS_ID, &id,
1255 -1);
1256 type = purple_account_get_status_type(select->account, id);
1257 g_free(id);
1259 if (purple_status_type_get_attr(type, "message") == NULL)
1261 gtk_widget_set_sensitive(select->message_view, FALSE);
1263 else
1265 gtk_widget_set_sensitive(select->message_view, TRUE);
1269 static gboolean
1270 status_editor_find_account_in_treemodel(GtkTreeIter *iter,
1271 StatusEditor *status_editor,
1272 PurpleAccount *account)
1274 GtkTreeModel *model;
1275 PurpleAccount *cur;
1277 g_return_val_if_fail(status_editor != NULL, FALSE);
1278 g_return_val_if_fail(account != NULL, FALSE);
1280 model = GTK_TREE_MODEL(status_editor->model);
1282 if (!gtk_tree_model_get_iter_first(model, iter))
1283 return FALSE;
1285 do {
1286 gtk_tree_model_get(model, iter, STATUS_EDITOR_COLUMN_ACCOUNT, &cur, -1);
1287 if (cur == account)
1288 return TRUE;
1289 } while (gtk_tree_model_iter_next(model, iter));
1291 return FALSE;
1294 static void
1295 substatus_editor_remove_dialog(SubStatusEditor *dialog)
1297 GtkTreeIter iter;
1299 if (status_editor_find_account_in_treemodel(&iter, dialog->status_editor, dialog->account))
1301 /* Remove the reference to this dialog from our parent's list store */
1302 gtk_list_store_set(dialog->status_editor->model, &iter,
1303 STATUS_EDITOR_COLUMN_WINDOW, NULL,
1304 -1);
1308 static void
1309 substatus_editor_destroy_cb(GtkWidget *widget, gpointer user_data)
1311 SubStatusEditor *dialog = user_data;
1313 substatus_editor_remove_dialog(dialog);
1314 g_free(dialog);
1317 static void
1318 substatus_editor_cancel_cb(GtkButton *button, gpointer user_data)
1320 SubStatusEditor *dialog = user_data;
1321 gtk_widget_destroy(dialog->window);
1325 static void
1326 substatus_editor_ok_cb(GtkButton *button, gpointer user_data)
1328 SubStatusEditor *dialog = user_data;
1329 StatusEditor *status_editor;
1330 GtkTreeIter iter;
1331 PurpleStatusType *type;
1332 char *id = NULL;
1333 char *message = NULL;
1334 const char *name = NULL, *stock = NULL;
1336 if (!gtk_combo_box_get_active_iter(dialog->box, &iter))
1338 gtk_widget_destroy(dialog->window);
1339 return;
1342 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
1343 STATUS_COLUMN_STATUS_ID, &id,
1344 -1);
1345 type = purple_account_get_status_type(dialog->account, id);
1346 if (purple_status_type_get_attr(type, "message") != NULL) {
1347 message = talkatu_markup_get_html(dialog->message_buffer, NULL);
1349 name = purple_status_type_get_name(type);
1350 stock = get_stock_icon_from_primitive(purple_status_type_get_primitive(type));
1352 status_editor = dialog->status_editor;
1354 if (status_editor_find_account_in_treemodel(&iter, status_editor, dialog->account))
1356 gtk_list_store_set(status_editor->model, &iter,
1357 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, TRUE,
1358 STATUS_EDITOR_COLUMN_STATUS_ID, id,
1359 STATUS_EDITOR_COLUMN_STATUS_NAME, name,
1360 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, message,
1361 STATUS_EDITOR_COLUMN_WINDOW, NULL,
1362 STATUS_EDITOR_COLUMN_STATUS_ICON, stock,
1363 -1);
1366 gtk_widget_destroy(dialog->window);
1367 g_free(id);
1368 g_free(message);
1371 static void
1372 edit_substatus(StatusEditor *status_editor, PurpleAccount *account)
1374 char *tmp;
1375 SubStatusEditor *dialog;
1376 GtkSizeGroup *sg;
1377 GtkWidget *combo;
1378 GtkWidget *hbox;
1379 GtkWidget *editor;
1380 GtkWidget *label;
1381 GtkWidget *vbox;
1382 GtkWidget *win;
1383 GtkTreeIter iter;
1384 GtkCellRenderer *rend;
1385 char *status_id = NULL;
1386 char *message = NULL;
1387 gboolean parent_dialog_has_substatus = FALSE;
1388 GList *list;
1389 gboolean select = FALSE;
1391 g_return_if_fail(status_editor != NULL);
1392 g_return_if_fail(account != NULL);
1394 status_editor_find_account_in_treemodel(&iter, status_editor, account);
1395 gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter,
1396 STATUS_EDITOR_COLUMN_WINDOW, &dialog,
1397 -1);
1398 if (dialog != NULL)
1400 gtk_window_present(GTK_WINDOW(dialog->window));
1401 return;
1404 dialog = g_new0(SubStatusEditor, 1);
1405 gtk_list_store_set(status_editor->model, &iter,
1406 STATUS_EDITOR_COLUMN_WINDOW, dialog,
1407 -1);
1408 dialog->status_editor = status_editor;
1409 dialog->account = account;
1411 tmp = g_strdup_printf(_("Status for %s"), purple_account_get_username(account));
1412 dialog->window = win = pidgin_create_dialog(tmp, PIDGIN_HIG_BORDER, "substatus", TRUE);
1413 g_free(tmp);
1415 g_signal_connect(G_OBJECT(win), "destroy",
1416 G_CALLBACK(substatus_editor_destroy_cb), dialog);
1418 /* Setup the vbox */
1419 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
1421 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1423 /* Status type */
1424 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BOX_SPACE);
1425 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1427 label = gtk_label_new_with_mnemonic(_("_Status:"));
1428 gtk_label_set_xalign(GTK_LABEL(label), 0);
1429 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1430 gtk_size_group_add_widget(sg, label);
1432 dialog->model = gtk_list_store_new(STATUS_NUM_COLUMNS,
1433 G_TYPE_STRING,
1434 G_TYPE_STRING,
1435 G_TYPE_STRING);
1436 combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(dialog->model));
1437 dialog->box = GTK_COMBO_BOX(combo);
1439 rend = GTK_CELL_RENDERER(gtk_cell_renderer_pixbuf_new());
1440 g_object_set(G_OBJECT(rend),
1441 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL),
1442 NULL);
1443 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), rend, FALSE);
1444 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), rend,
1445 "stock-id", STATUS_COLUMN_ICON, NULL);
1447 rend = GTK_CELL_RENDERER(gtk_cell_renderer_text_new());
1448 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), rend, TRUE);
1449 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), rend,
1450 "text", STATUS_COLUMN_STATUS_NAME, NULL);
1452 g_signal_connect(G_OBJECT(combo), "changed",
1453 G_CALLBACK(substatus_selection_changed_cb), dialog);
1455 gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 0);
1457 /* Status mesage */
1458 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BOX_SPACE);
1459 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1461 label = gtk_label_new_with_mnemonic(_("_Message:"));
1462 gtk_label_set_xalign(GTK_LABEL(label), 0);
1463 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1464 gtk_size_group_add_widget(sg, label);
1466 editor = talkatu_editor_new();
1467 gtk_box_pack_start(GTK_BOX(hbox), editor, TRUE, TRUE, 0);
1469 dialog->message_view = talkatu_editor_get_view(TALKATU_EDITOR(editor));
1470 dialog->message_buffer = talkatu_html_buffer_new();
1471 gtk_text_view_set_buffer(GTK_TEXT_VIEW(dialog->message_view), dialog->message_buffer);
1473 /* Cancel button */
1474 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL,
1475 G_CALLBACK(substatus_editor_cancel_cb), dialog);
1477 /* OK button */
1478 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_OK,
1479 G_CALLBACK(substatus_editor_ok_cb), dialog);
1481 /* Seed the input widgets with the current values */
1483 /* Only look at the saved status if we can't find it in the parent status dialog's substatuses model */
1484 gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter,
1485 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, &parent_dialog_has_substatus, -1);
1486 if (parent_dialog_has_substatus) {
1487 gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter,
1488 STATUS_EDITOR_COLUMN_STATUS_ID, &status_id,
1489 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, &message, -1);
1490 } else if (status_editor->original_title != NULL) {
1491 PurpleSavedStatus *saved_status = NULL;
1492 PurpleSavedStatusSub *substatus = NULL;
1494 if ((saved_status = purple_savedstatus_find(status_editor->original_title)) != NULL) {
1495 if ((substatus = purple_savedstatus_get_substatus(saved_status, account)) != NULL) {
1496 message = (char *)purple_savedstatus_substatus_get_message(substatus);
1497 status_id = (char *)purple_status_type_get_id(
1498 purple_savedstatus_substatus_get_status_type(substatus));
1502 /* TODO: Else get the generic status type from our parent */
1504 if (message) {
1505 talkatu_markup_set_html(TALKATU_BUFFER(dialog->message_buffer), message, -1);
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_primitive_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_primitive_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();