mark PurpleImageClass as private
[pidgin-git.git] / pidgin / gtksavedstatuses.c
blobe28371378feed1765160888b6fd7e87ab81f01a1
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_foreach(list, (GFunc)gtk_tree_path_free, NULL);
213 g_list_free(list);
216 static void
217 status_window_add_cb(GtkButton *button, gpointer user_data)
219 pidgin_status_editor_show(FALSE, NULL);
222 static void
223 status_window_modify_foreach(GtkTreeModel *model, GtkTreePath *path,
224 GtkTreeIter *iter, gpointer user_data)
226 gchar *title;
227 PurpleSavedStatus *saved_status;
229 gtk_tree_model_get(model, iter, STATUS_WINDOW_COLUMN_TITLE, &title, -1);
230 saved_status = purple_savedstatus_find(title);
231 g_free(title);
232 pidgin_status_editor_show(TRUE, saved_status);
235 static void
236 status_window_modify_cb(GtkButton *button, gpointer user_data)
238 StatusWindow *dialog = user_data;
239 GtkTreeSelection *selection;
241 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
243 gtk_tree_selection_selected_foreach(selection, status_window_modify_foreach, user_data);
246 static void
247 status_window_delete_cancel_cb(gpointer data)
249 GList *sel_titles = data;
250 g_list_foreach(sel_titles, (GFunc) g_free, NULL);
251 g_list_free(sel_titles);
254 static void
255 status_window_delete_confirm_cb(gpointer data)
257 GtkTreeIter iter;
258 GList *sel_titles = data, *l;
259 char *title;
261 for (l = sel_titles; l != NULL; l = l->next) {
262 title = l->data;
263 if (purple_savedstatus_find(title) != purple_savedstatus_get_current()) {
264 if (status_window_find_savedstatus(&iter, title))
265 gtk_list_store_remove(status_window->model, &iter);
266 purple_savedstatus_delete(title);
268 g_free(title);
270 g_list_free(sel_titles);
273 static void
274 status_window_delete_cb(GtkButton *button, gpointer user_data)
276 StatusWindow *dialog = user_data;
277 GtkTreeIter iter;
278 GtkTreeSelection *selection;
279 GList *sel_paths, *l, *sel_titles = NULL;
280 GtkTreeModel *model = GTK_TREE_MODEL(dialog->model);
281 char *title;
282 gpointer handle;
284 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
285 sel_paths = gtk_tree_selection_get_selected_rows(selection, NULL);
287 /* This is ugly because we're not allowed to modify the model from within
288 * gtk_tree_selection_selected_foreach() and the GtkTreePaths can become invalid
289 * when something is removed from the model. The selection can also change while
290 * the request dialog is displayed, so we need to capture the selected rows at this time. */
292 for (l = sel_paths; l != NULL; l = l->next) {
293 if (gtk_tree_model_get_iter(model, &iter, l->data)) {
294 gtk_tree_model_get(model, &iter, STATUS_WINDOW_COLUMN_TITLE, &title, -1);
295 sel_titles = g_list_prepend(sel_titles, title);
297 gtk_tree_path_free(l->data);
299 g_list_free(sel_paths);
301 g_return_if_fail(sel_titles != NULL);
302 if (!sel_titles->next) {
303 title = g_strdup_printf(_("Are you sure you want to delete %s?"),
304 (const gchar *)sel_titles->data);
305 handle = purple_savedstatus_find(sel_titles->data);
306 } else {
307 title = g_strdup(_("Are you sure you want to delete the selected saved statuses?"));
308 handle = dialog;
311 purple_request_action(handle, NULL, title, NULL, 0,
312 NULL,
313 sel_titles, 2,
314 _("Delete"), status_window_delete_confirm_cb,
315 _("Cancel"), status_window_delete_cancel_cb);
317 g_free(title);
320 static void
321 status_window_close_cb(GtkButton *button, gpointer user_data)
323 pidgin_status_window_hide();
326 static void
327 status_selected_cb(GtkTreeSelection *sel, gpointer user_data)
329 StatusWindow *dialog = user_data;
330 GList *sel_paths, *tmp;
331 gboolean can_use = TRUE, can_delete = TRUE;
332 int num_selected;
333 GtkTreeModel *model = GTK_TREE_MODEL(dialog->model);
335 sel_paths = gtk_tree_selection_get_selected_rows(sel, NULL);
337 for (tmp = sel_paths, num_selected = 0; tmp; tmp = tmp->next, num_selected++) {
338 GtkTreeIter iter;
339 char *title;
341 if (gtk_tree_model_get_iter(model, &iter, tmp->data)) {
342 gtk_tree_model_get(model, &iter,
343 STATUS_WINDOW_COLUMN_TITLE, &title, -1);
344 if (purple_savedstatus_find(title) == purple_savedstatus_get_current()) {
345 can_use = can_delete = FALSE;
348 g_free(title);
351 gtk_tree_path_free(tmp->data);
354 gtk_widget_set_sensitive(dialog->use_button, (num_selected == 1) && can_use);
355 gtk_widget_set_sensitive(dialog->modify_button, (num_selected > 0));
356 gtk_widget_set_sensitive(dialog->delete_button, num_selected > 0 && can_delete);
358 g_list_free(sel_paths);
361 static const gchar *
362 get_stock_icon_from_primitive(PurpleStatusPrimitive type)
364 return pidgin_stock_id_from_status_primitive(type);
367 static void
368 add_status_to_saved_status_list(GtkListStore *model, PurpleSavedStatus *saved_status)
370 GtkTreeIter iter;
371 const char *title;
372 const char *type;
373 const gchar *icon;
374 char *message;
376 if (purple_savedstatus_is_transient(saved_status))
377 return;
379 title = purple_savedstatus_get_title(saved_status);
380 type = purple_primitive_get_name_from_type(purple_savedstatus_get_primitive_type(saved_status));
381 message = purple_markup_strip_html(purple_savedstatus_get_message(saved_status));
382 icon = get_stock_icon_from_primitive(purple_savedstatus_get_primitive_type(saved_status));
384 gtk_list_store_append(model, &iter);
385 gtk_list_store_set(model, &iter,
386 STATUS_WINDOW_COLUMN_ICON, icon,
387 STATUS_WINDOW_COLUMN_TITLE, title,
388 STATUS_WINDOW_COLUMN_TYPE, type,
389 STATUS_WINDOW_COLUMN_MESSAGE, message,
390 -1);
391 g_free(message);
394 static void
395 populate_saved_status_list(StatusWindow *dialog)
397 GList *saved_statuses;
399 gtk_list_store_clear(dialog->model);
401 for (saved_statuses = purple_savedstatuses_get_all(); saved_statuses != NULL;
402 saved_statuses = g_list_next(saved_statuses))
404 add_status_to_saved_status_list(dialog->model, saved_statuses->data);
408 static gboolean
409 search_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data)
411 gboolean result;
412 char *haystack;
414 gtk_tree_model_get(model, iter, column, &haystack, -1);
416 result = (purple_strcasestr(haystack, key) == NULL);
418 g_free(haystack);
420 return result;
423 static void
424 savedstatus_activated_cb(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, StatusWindow *dialog)
426 status_window_use_cb(NULL, dialog);
427 status_window_close_cb(NULL, dialog);
430 static void
431 saved_status_updated_cb(PurpleSavedStatus *status, StatusWindow *sw)
433 populate_saved_status_list(sw);
436 static GtkWidget *
437 create_saved_status_list(StatusWindow *dialog)
439 GtkWidget *treeview;
440 GtkTreeSelection *sel;
441 GtkTreeViewColumn *column;
442 GtkCellRenderer *renderer;
444 /* Create the list model */
445 dialog->model = gtk_list_store_new(STATUS_WINDOW_NUM_COLUMNS,
446 G_TYPE_STRING,
447 G_TYPE_STRING,
448 G_TYPE_STRING,
449 G_TYPE_POINTER,
450 G_TYPE_STRING);
452 /* Create the treeview */
453 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
454 dialog->treeview = treeview;
455 g_signal_connect(G_OBJECT(treeview), "row-activated",
456 G_CALLBACK(savedstatus_activated_cb), dialog);
458 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
459 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
460 g_signal_connect(G_OBJECT(sel), "changed",
461 G_CALLBACK(status_selected_cb), dialog);
463 /* Add columns */
464 column = gtk_tree_view_column_new();
465 gtk_tree_view_column_set_title(column, _("Title"));
466 gtk_tree_view_column_set_resizable(column, TRUE);
467 gtk_tree_view_column_set_min_width(column, 100);
468 gtk_tree_view_column_set_sort_column_id(column,
469 STATUS_WINDOW_COLUMN_TITLE);
470 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
471 renderer = gtk_cell_renderer_text_new();
472 gtk_tree_view_column_pack_start(column, renderer, TRUE);
473 gtk_tree_view_column_add_attribute(column, renderer, "text",
474 STATUS_WINDOW_COLUMN_TITLE);
475 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
477 column = gtk_tree_view_column_new();
478 gtk_tree_view_column_set_title(column, _("Type"));
479 gtk_tree_view_column_set_resizable(column, TRUE);
480 gtk_tree_view_column_set_sort_column_id(column,
481 STATUS_WINDOW_COLUMN_TYPE);
482 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
483 renderer = gtk_cell_renderer_pixbuf_new();
484 gtk_tree_view_column_pack_start(column, renderer, TRUE);
485 gtk_tree_view_column_add_attribute(column, renderer, "stock-id",
486 STATUS_WINDOW_COLUMN_ICON);
487 renderer = gtk_cell_renderer_text_new();
488 gtk_tree_view_column_pack_start(column, renderer, TRUE);
489 gtk_tree_view_column_add_attribute(column, renderer, "text",
490 STATUS_WINDOW_COLUMN_TYPE);
492 column = gtk_tree_view_column_new();
493 gtk_tree_view_column_set_title(column, _("Message"));
494 gtk_tree_view_column_set_resizable(column, TRUE);
495 gtk_tree_view_column_set_sort_column_id(column,
496 STATUS_WINDOW_COLUMN_MESSAGE);
497 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
498 renderer = gtk_cell_renderer_text_new();
499 gtk_tree_view_column_pack_start(column, renderer, TRUE);
500 gtk_tree_view_column_add_attribute(column, renderer, "text",
501 STATUS_WINDOW_COLUMN_MESSAGE);
502 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
504 /* Enable CTRL+F searching */
505 gtk_tree_view_set_search_column(GTK_TREE_VIEW(treeview), STATUS_WINDOW_COLUMN_TITLE);
506 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(treeview), search_func, NULL, NULL);
508 /* Sort the title column by default */
509 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dialog->model),
510 STATUS_WINDOW_COLUMN_TITLE,
511 GTK_SORT_ASCENDING);
513 /* Populate list */
514 populate_saved_status_list(dialog);
516 gtk_widget_show_all(treeview);
518 return pidgin_make_scrollable(treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1);
521 static gboolean
522 configure_cb(GtkWidget *widget, GdkEventConfigure *event, StatusWindow *dialog)
524 if (gtk_widget_get_visible(widget))
526 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/status/dialog/width", event->width);
527 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/status/dialog/height", event->height);
530 return FALSE;
533 static void
534 current_status_changed(PurpleSavedStatus *old, PurpleSavedStatus *new_status,
535 StatusWindow *dialog)
537 status_selected_cb(gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview)), dialog);
540 void
541 pidgin_status_window_show(void)
543 StatusWindow *dialog;
544 GtkWidget *bbox;
545 GtkWidget *button;
546 GtkWidget *list;
547 GtkWidget *vbox;
548 GtkWidget *win;
549 int width, height;
551 if (status_window != NULL)
553 gtk_window_present(GTK_WINDOW(status_window->window));
554 return;
557 status_window = dialog = g_new0(StatusWindow, 1);
559 width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/width");
560 height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/height");
562 dialog->window = win = pidgin_create_dialog(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE);
563 gtk_window_set_default_size(GTK_WINDOW(win), width, height);
565 g_signal_connect(G_OBJECT(win), "delete_event",
566 G_CALLBACK(status_window_destroy_cb), dialog);
567 g_signal_connect(G_OBJECT(win), "configure_event",
568 G_CALLBACK(configure_cb), dialog);
570 /* Setup the vbox */
571 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
573 /* List of saved status states */
574 list = create_saved_status_list(dialog);
575 gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0);
577 /* Button box. */
578 bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win));
580 /* Use button */
581 button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE,
582 PIDGIN_BUTTON_HORIZONTAL);
583 dialog->use_button = button;
584 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
585 gtk_widget_set_sensitive(button, FALSE);
587 g_signal_connect(G_OBJECT(button), "clicked",
588 G_CALLBACK(status_window_use_cb), dialog);
590 /* Add button */
591 pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_ADD,
592 G_CALLBACK(status_window_add_cb), dialog);
594 /* Modify button */
595 button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY,
596 G_CALLBACK(status_window_modify_cb), dialog);
597 dialog->modify_button = button;
598 gtk_widget_set_sensitive(button, FALSE);
600 /* Delete button */
601 button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE,
602 G_CALLBACK(status_window_delete_cb), dialog);
603 dialog->delete_button = button;
604 gtk_widget_set_sensitive(button, FALSE);
606 /* Close button */
607 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE,
608 G_CALLBACK(status_window_close_cb), dialog);
610 purple_signal_connect(purple_savedstatuses_get_handle(),
611 "savedstatus-changed", status_window,
612 PURPLE_CALLBACK(current_status_changed), dialog);
613 purple_signal_connect(purple_savedstatuses_get_handle(),
614 "savedstatus-added", status_window,
615 PURPLE_CALLBACK(saved_status_updated_cb), dialog);
616 purple_signal_connect(purple_savedstatuses_get_handle(),
617 "savedstatus-deleted", status_window,
618 PURPLE_CALLBACK(saved_status_updated_cb), dialog);
619 purple_signal_connect(purple_savedstatuses_get_handle(),
620 "savedstatus-modified", status_window,
621 PURPLE_CALLBACK(saved_status_updated_cb), dialog);
623 gtk_widget_show_all(win);
626 void
627 pidgin_status_window_hide(void)
629 if (status_window == NULL)
630 return;
632 if (status_window->window != NULL)
633 gtk_widget_destroy(status_window->window);
635 purple_request_close_with_handle(status_window);
636 purple_notify_close_with_handle(status_window);
637 purple_signals_disconnect_by_handle(status_window);
638 g_object_unref(G_OBJECT(status_window->model));
639 g_free(status_window);
640 status_window = NULL;
644 /**************************************************************************
645 * Status editor
646 **************************************************************************/
648 static void substatus_editor_cancel_cb(GtkButton *button, gpointer user_data);
650 static void
651 status_editor_remove_dialog(StatusEditor *dialog)
653 GtkTreeModel *model;
654 GtkTreeIter iter;
656 /* Remove the reference to this dialog from our parent's list store */
657 if (status_window_find_savedstatus(&iter, dialog->original_title))
659 gtk_list_store_set(status_window->model, &iter,
660 STATUS_WINDOW_COLUMN_WINDOW, NULL,
661 -1);
664 /* Close any substatus editors that may be open */
665 model = GTK_TREE_MODEL(dialog->model);
666 if (gtk_tree_model_get_iter_first(model, &iter))
668 do {
669 SubStatusEditor *substatus_dialog;
671 gtk_tree_model_get(model, &iter,
672 STATUS_EDITOR_COLUMN_WINDOW, &substatus_dialog,
673 -1);
674 if (substatus_dialog != NULL)
676 gtk_list_store_set(dialog->model, &iter,
677 STATUS_EDITOR_COLUMN_WINDOW, NULL,
678 -1);
679 substatus_editor_cancel_cb(NULL, substatus_dialog);
681 } while (gtk_tree_model_iter_next(model, &iter));
686 static void
687 status_editor_destroy_cb(GtkWidget *widget, gpointer user_data)
689 StatusEditor *dialog = user_data;
691 status_editor_remove_dialog(dialog);
692 g_free(dialog->original_title);
693 g_object_unref(G_OBJECT(dialog->model));
694 g_free(dialog);
697 static void
698 status_editor_cancel_cb(GtkButton *button, gpointer user_data)
700 StatusEditor *dialog = user_data;
701 gtk_widget_destroy(dialog->window);
704 static void
705 status_editor_ok_cb(GtkButton *button, gpointer user_data)
707 StatusEditor *dialog = user_data;
708 const char *title;
709 PurpleStatusPrimitive type;
710 char *message, *unformatted;
711 PurpleSavedStatus *saved_status = NULL;
712 GtkTreeModel *model;
713 GtkTreeIter iter;
715 title = gtk_entry_get_text(dialog->title);
718 * If we're saving this status, and the title is already taken
719 * then show an error dialog and don't do anything.
721 if (((button == dialog->saveanduse_button) || (button == dialog->save_button)) &&
722 (purple_savedstatus_find(title) != NULL) &&
723 ((dialog->original_title == NULL) || (!purple_strequal(title, dialog->original_title))))
725 purple_notify_error(status_window, NULL, _("Title already in use. You must "
726 "choose a unique title."), NULL, NULL);
727 return;
730 type = gtk_combo_box_get_active(dialog->type) + (PURPLE_STATUS_UNSET + 1);
731 message = talkatu_markup_get_html(dialog->message_buffer, NULL);
732 unformatted = purple_markup_strip_html(message);
735 * If we're editing an old status, then lookup the old status.
736 * Note: It is possible that it has been deleted or renamed
737 * or something, and no longer exists.
739 if (dialog->original_title != NULL)
741 GtkTreeIter iter;
743 saved_status = purple_savedstatus_find(dialog->original_title);
745 if (status_window_find_savedstatus(&iter, dialog->original_title))
746 gtk_list_store_remove(status_window->model, &iter);
749 if (saved_status == NULL)
751 /* This is a new status */
752 if ((button == dialog->saveanduse_button)
753 || (button == dialog->save_button))
754 saved_status = purple_savedstatus_new(title, type);
755 else
756 saved_status = purple_savedstatus_new(NULL, type);
758 else
760 /* Modify the old status */
761 if (!purple_strequal(title, dialog->original_title))
762 purple_savedstatus_set_title(saved_status, title);
763 purple_savedstatus_set_primitive_type(saved_status, type);
766 if (*unformatted == '\0')
767 purple_savedstatus_set_message(saved_status, NULL);
768 else
769 purple_savedstatus_set_message(saved_status, message);
771 /* Set any substatuses */
772 model = GTK_TREE_MODEL(dialog->model);
773 if (gtk_tree_model_get_iter_first(model, &iter))
775 do {
776 PurpleAccount *account;
777 gboolean enabled;
778 char *id;
779 char *message;
780 PurpleStatusType *type;
782 gtk_tree_model_get(model, &iter,
783 STATUS_EDITOR_COLUMN_ACCOUNT, &account,
784 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, &enabled,
785 STATUS_EDITOR_COLUMN_STATUS_ID, &id,
786 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, &message,
787 -1);
788 if (enabled)
790 type = purple_account_get_status_type(account, id);
791 purple_savedstatus_set_substatus(saved_status, account, type, message);
793 else
795 purple_savedstatus_unset_substatus(saved_status, account);
797 g_free(id);
798 g_free(message);
799 } while (gtk_tree_model_iter_next(model, &iter));
802 g_free(message);
803 g_free(unformatted);
805 /* If they clicked on "Save and Use" or "Use," then activate the status */
806 if (button != dialog->save_button)
807 purple_savedstatus_activate(saved_status);
809 gtk_widget_destroy(dialog->window);
812 static void
813 editor_title_changed_cb(GtkWidget *widget, gpointer user_data)
815 StatusEditor *dialog = user_data;
816 const gchar *text;
818 text = gtk_entry_get_text(dialog->title);
820 gtk_widget_set_sensitive(GTK_WIDGET(dialog->saveanduse_button), (*text != '\0'));
821 gtk_widget_set_sensitive(GTK_WIDGET(dialog->save_button), (*text != '\0'));
824 static GtkWidget *
825 create_status_type_menu(PurpleStatusPrimitive type)
827 int i;
828 GtkWidget *dropdown;
829 GtkListStore *store;
830 GtkTreeIter iter;
831 GtkCellRenderer *renderer;
833 store = gtk_list_store_new(STATUS_NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
835 for (i = PURPLE_STATUS_UNSET + 1; i < PURPLE_STATUS_NUM_PRIMITIVES; i++)
837 /* Someone should fix this for 3.0.0. The independent boolean
838 * should probably be set on the status type, not the status.
839 * I guess that would prevent third party plugins from creating
840 * independent statuses?
842 if (i == PURPLE_STATUS_MOBILE ||
843 i == PURPLE_STATUS_MOOD ||
844 i == PURPLE_STATUS_TUNE)
846 * Special-case these. They're intended to be independent
847 * status types, so don't show them in the list.
849 continue;
851 gtk_list_store_append(store, &iter);
852 gtk_list_store_set(store, &iter,
853 STATUS_COLUMN_ICON, get_stock_icon_from_primitive(i),
854 STATUS_COLUMN_STATUS_ID, purple_primitive_get_id_from_type(i),
855 STATUS_COLUMN_STATUS_NAME, purple_primitive_get_name_from_type(i),
856 -1);
859 dropdown = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
861 renderer = gtk_cell_renderer_pixbuf_new();
862 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(dropdown), renderer, FALSE);
863 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(dropdown), renderer,
864 "stock-id", STATUS_COLUMN_ICON,
865 NULL);
867 renderer = gtk_cell_renderer_text_new();
868 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(dropdown), renderer, TRUE);
869 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(dropdown), renderer,
870 "text", STATUS_COLUMN_STATUS_NAME,
871 NULL);
873 gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown),
874 type - (PURPLE_STATUS_UNSET + 1));
876 return dropdown;
879 static void edit_substatus(StatusEditor *status_editor, PurpleAccount *account);
881 static void
882 edit_substatus_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col, gpointer user_data)
884 StatusEditor *dialog = user_data;
885 GtkTreeIter iter;
886 PurpleAccount *account;
888 gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path);
889 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
890 STATUS_EDITOR_COLUMN_ACCOUNT, &account,
891 -1);
893 edit_substatus(dialog, account);
896 static void
897 status_editor_substatus_cb(GtkCellRendererToggle *renderer, gchar *path_str, gpointer data)
899 StatusEditor *dialog = (StatusEditor *)data;
900 GtkTreeIter iter;
901 gboolean enabled;
902 PurpleAccount *account;
904 gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(dialog->model), &iter, path_str);
905 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
906 STATUS_EDITOR_COLUMN_ACCOUNT, &account,
907 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, &enabled,
908 -1);
910 enabled = !enabled;
912 if (enabled)
914 edit_substatus(dialog, account);
916 else
918 /* Remove the substatus */
919 gtk_list_store_set(dialog->model, &iter,
920 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, enabled,
921 STATUS_EDITOR_COLUMN_STATUS_ID, NULL,
922 STATUS_EDITOR_COLUMN_STATUS_NAME, NULL,
923 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, NULL,
924 STATUS_EDITOR_COLUMN_STATUS_ICON, NULL,
925 -1);
929 static void
930 status_editor_add_columns(StatusEditor *dialog)
932 GtkCellRenderer *renderer;
933 GtkTreeViewColumn *column;
935 /* Enable Different status column */
936 renderer = gtk_cell_renderer_toggle_new();
937 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(dialog->treeview),
938 -1, _("Different"),
939 renderer,
940 "active", STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS,
941 NULL);
942 g_signal_connect(G_OBJECT(renderer), "toggled",
943 G_CALLBACK(status_editor_substatus_cb), dialog);
945 /* Username column */
946 column = gtk_tree_view_column_new();
947 gtk_tree_view_column_set_resizable(column, TRUE);
948 gtk_tree_view_column_set_title(column, _("Username"));
949 gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
950 gtk_tree_view_column_set_resizable(column, TRUE);
952 /* Icon */
953 renderer = gtk_cell_renderer_pixbuf_new();
954 gtk_tree_view_column_pack_start(column, renderer, FALSE);
955 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf",
956 STATUS_EDITOR_COLUMN_ICON);
958 /* Username */
959 renderer = gtk_cell_renderer_text_new();
960 gtk_tree_view_column_pack_start(column, renderer, TRUE);
961 gtk_tree_view_column_add_attribute(column, renderer, "text",
962 STATUS_EDITOR_COLUMN_USERNAME);
964 /* Status column */
965 column = gtk_tree_view_column_new();
966 gtk_tree_view_column_set_resizable(column, TRUE);
967 gtk_tree_view_column_set_title(column, _("Status"));
968 gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
969 gtk_tree_view_column_set_resizable(column, TRUE);
970 renderer = gtk_cell_renderer_pixbuf_new();
971 gtk_tree_view_column_pack_start(column, renderer, FALSE);
972 gtk_tree_view_column_add_attribute(column, renderer, "stock-id",
973 STATUS_EDITOR_COLUMN_STATUS_ICON);
974 renderer = gtk_cell_renderer_text_new();
975 gtk_tree_view_column_pack_start(column, renderer, TRUE);
976 gtk_tree_view_column_add_attribute(column, renderer, "text",
977 STATUS_EDITOR_COLUMN_STATUS_NAME);
979 /* Message column */
980 column = gtk_tree_view_column_new();
981 gtk_tree_view_column_set_resizable(column, TRUE);
982 gtk_tree_view_column_set_title(column, _("Message"));
983 gtk_tree_view_insert_column(GTK_TREE_VIEW(dialog->treeview), column, -1);
984 gtk_tree_view_column_set_resizable(column, TRUE);
985 renderer = gtk_cell_renderer_text_new();
986 gtk_tree_view_column_pack_start(column, renderer, TRUE);
987 gtk_tree_view_column_add_attribute(column, renderer, "text",
988 STATUS_EDITOR_COLUMN_STATUS_MESSAGE);
990 g_signal_connect(G_OBJECT(dialog->treeview), "row-activated",
991 G_CALLBACK(edit_substatus_cb), dialog);
994 static void
995 status_editor_set_account(GtkListStore *store, PurpleAccount *account,
996 GtkTreeIter *iter, PurpleSavedStatusSub *substatus)
998 GdkPixbuf *pixbuf;
999 const char *id = NULL, *name = NULL, *message = NULL;
1000 PurpleStatusPrimitive prim = PURPLE_STATUS_UNSET;
1002 pixbuf = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_MEDIUM);
1003 if ((pixbuf != NULL) && !purple_account_is_connected(account))
1005 gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
1008 if (substatus != NULL)
1010 const PurpleStatusType *type;
1012 type = purple_savedstatus_substatus_get_status_type(substatus);
1013 id = purple_status_type_get_id(type);
1014 name = purple_status_type_get_name(type);
1015 prim = purple_status_type_get_primitive(type);
1016 if (purple_status_type_get_attr(type, "message"))
1017 message = purple_savedstatus_substatus_get_message(substatus);
1020 gtk_list_store_set(store, iter,
1021 STATUS_EDITOR_COLUMN_ACCOUNT, account,
1022 STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, (substatus != NULL),
1023 STATUS_EDITOR_COLUMN_ICON, pixbuf,
1024 STATUS_EDITOR_COLUMN_USERNAME, purple_account_get_username(account),
1025 STATUS_EDITOR_COLUMN_STATUS_ID, id,
1026 STATUS_EDITOR_COLUMN_STATUS_NAME, name,
1027 STATUS_EDITOR_COLUMN_STATUS_MESSAGE, message,
1028 STATUS_EDITOR_COLUMN_STATUS_ICON, get_stock_icon_from_primitive(prim),
1029 -1);
1031 if (pixbuf != NULL)
1032 g_object_unref(G_OBJECT(pixbuf));
1035 static void
1036 status_editor_add_account(StatusEditor *dialog, PurpleAccount *account,
1037 PurpleSavedStatusSub *substatus)
1039 GtkTreeIter iter;
1041 gtk_list_store_append(dialog->model, &iter);
1043 status_editor_set_account(dialog->model, account, &iter, substatus);
1046 static void
1047 status_editor_populate_list(StatusEditor *dialog, PurpleSavedStatus *saved_status)
1049 GList *iter;
1050 PurpleSavedStatusSub *substatus;
1052 gtk_list_store_clear(dialog->model);
1054 for (iter = purple_accounts_get_all(); iter != NULL; iter = iter->next)
1056 PurpleAccount *account = (PurpleAccount *)iter->data;
1058 if (saved_status != NULL)
1059 substatus = purple_savedstatus_get_substatus(saved_status, account);
1060 else
1061 substatus = NULL;
1063 status_editor_add_account(dialog, account, substatus);
1067 void
1068 pidgin_status_editor_show(gboolean edit, PurpleSavedStatus *saved_status)
1070 GtkTreeIter iter;
1071 StatusEditor *dialog;
1072 GtkSizeGroup *sg;
1073 GtkWidget *bbox;
1074 GtkWidget *button;
1075 GtkWidget *dbox;
1076 GtkWidget *expander;
1077 GtkWidget *dropdown;
1078 GtkWidget *entry;
1079 GtkWidget *editor;
1080 GtkWidget *hbox;
1081 GtkWidget *vbox;
1082 GtkWidget *win;
1083 GList *focus_chain = NULL;
1085 if (edit)
1087 g_return_if_fail(saved_status != NULL);
1088 g_return_if_fail(!purple_savedstatus_is_transient(saved_status));
1091 /* Find a possible window for this saved status and present it */
1092 if (edit && status_window_find_savedstatus(&iter, purple_savedstatus_get_title(saved_status)))
1094 gtk_tree_model_get(GTK_TREE_MODEL(status_window->model), &iter,
1095 STATUS_WINDOW_COLUMN_WINDOW, &dialog,
1096 -1);
1097 if (dialog != NULL)
1099 gtk_window_present(GTK_WINDOW(dialog->window));
1100 return;
1104 dialog = g_new0(StatusEditor, 1);
1105 if (edit && status_window_find_savedstatus(&iter, purple_savedstatus_get_title(saved_status)))
1107 gtk_list_store_set(status_window->model, &iter,
1108 STATUS_WINDOW_COLUMN_WINDOW, dialog,
1109 -1);
1112 if (edit)
1113 dialog->original_title = g_strdup(purple_savedstatus_get_title(saved_status));
1115 dialog->window = win = pidgin_create_dialog(_("Status"), PIDGIN_HIG_BORDER, "status", TRUE);
1117 g_signal_connect(G_OBJECT(win), "destroy",
1118 G_CALLBACK(status_editor_destroy_cb), dialog);
1120 /* Setup the vbox */
1121 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
1123 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1125 /* Title */
1126 entry = gtk_entry_new();
1127 dialog->title = GTK_ENTRY(entry);
1128 if ((saved_status != NULL)
1129 && !purple_savedstatus_is_transient(saved_status)
1130 && (purple_savedstatus_get_title(saved_status) != NULL))
1131 gtk_entry_set_text(GTK_ENTRY(entry), purple_savedstatus_get_title(saved_status));
1132 g_signal_connect(G_OBJECT(entry), "changed",
1133 G_CALLBACK(editor_title_changed_cb), dialog);
1134 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Title:"), sg, entry, TRUE, NULL);
1136 /* Status type */
1137 if (saved_status != NULL)
1138 dropdown = create_status_type_menu(purple_savedstatus_get_primitive_type(saved_status));
1139 else
1140 dropdown = create_status_type_menu(PURPLE_STATUS_AWAY);
1141 dialog->type = GTK_COMBO_BOX(dropdown);
1142 pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Status:"), sg, dropdown, TRUE, NULL);
1144 /* Status message */
1145 editor = talkatu_editor_new();
1147 dialog->message_view = talkatu_editor_get_view(TALKATU_EDITOR(editor));
1148 hbox = pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("_Message:"), sg, editor, TRUE, NULL);
1150 dialog->message_buffer = talkatu_html_buffer_new();
1151 gtk_text_view_set_buffer(GTK_TEXT_VIEW(dialog->message_view), dialog->message_buffer);
1153 gtk_container_child_set(GTK_CONTAINER(vbox), hbox, "expand", TRUE, "fill", TRUE, NULL);
1154 focus_chain = g_list_prepend(focus_chain, dialog->message_view);
1155 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), focus_chain);
1156 g_list_free(focus_chain);
1158 if ((saved_status != NULL) && (purple_savedstatus_get_message(saved_status) != NULL)) {
1159 talkatu_markup_set_html(
1160 TALKATU_BUFFER(dialog->message_buffer),
1161 purple_savedstatus_get_message(saved_status),
1166 /* Different status message expander */
1167 expander = gtk_expander_new_with_mnemonic(_("Use a _different status for some accounts"));
1168 gtk_box_pack_start(GTK_BOX(vbox), expander, FALSE, FALSE, 0);
1170 /* Setup the box that the expander will cover */
1171 dbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, PIDGIN_HIG_CAT_SPACE);
1172 gtk_container_add(GTK_CONTAINER(expander), dbox);
1174 /* Create the list model */
1175 dialog->model = gtk_list_store_new(STATUS_EDITOR_NUM_COLUMNS,
1176 G_TYPE_POINTER,
1177 G_TYPE_POINTER,
1178 G_TYPE_BOOLEAN,
1179 GDK_TYPE_PIXBUF,
1180 G_TYPE_STRING,
1181 G_TYPE_STRING,
1182 G_TYPE_STRING,
1183 G_TYPE_STRING,
1184 G_TYPE_STRING);
1186 /* Create the treeview */
1187 dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
1188 gtk_widget_set_size_request(dialog->treeview, -1, 150);
1189 gtk_box_pack_start(GTK_BOX(dbox),
1190 pidgin_make_scrollable(dialog->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1191 TRUE, TRUE, 0);
1193 /* Add columns */
1194 status_editor_add_columns(dialog);
1196 /* Populate list */
1197 status_editor_populate_list(dialog, saved_status);
1199 /* Expand the treeview if we have substatuses */
1200 gtk_expander_set_expanded(GTK_EXPANDER(expander),
1201 (saved_status != NULL) && purple_savedstatus_has_substatuses(saved_status));
1203 /* Button box */
1204 bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win));
1205 gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
1206 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
1208 /* Cancel button */
1209 pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL,
1210 G_CALLBACK(status_editor_cancel_cb), dialog);
1212 /* Use button */
1213 button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE,
1214 PIDGIN_BUTTON_HORIZONTAL);
1215 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1216 g_signal_connect(G_OBJECT(button), "clicked",
1217 G_CALLBACK(status_editor_ok_cb), dialog);
1219 /* Save and Use button */
1220 button = pidgin_pixbuf_button_from_stock(_("Sa_ve and Use"), GTK_STOCK_OK,
1221 PIDGIN_BUTTON_HORIZONTAL);
1222 dialog->saveanduse_button = GTK_BUTTON(button);
1223 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1224 if (dialog->original_title == NULL)
1225 gtk_widget_set_sensitive(button, FALSE);
1226 g_signal_connect(G_OBJECT(button), "clicked",
1227 G_CALLBACK(status_editor_ok_cb), dialog);
1229 /* Save button */
1230 button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_SAVE,
1231 G_CALLBACK(status_editor_ok_cb), dialog);
1232 if (dialog->original_title == NULL)
1233 gtk_widget_set_sensitive(button, FALSE);
1234 dialog->save_button = GTK_BUTTON(button);
1236 gtk_widget_show_all(win);
1237 g_object_unref(sg);
1241 /**************************************************************************
1242 * SubStatus editor
1243 **************************************************************************/
1245 static void
1246 substatus_selection_changed_cb(GtkComboBox *box, gpointer user_data)
1248 SubStatusEditor *select = user_data;
1249 GtkTreeIter iter;
1250 char *id;
1251 PurpleStatusType *type;
1253 if (!gtk_combo_box_get_active_iter(box, &iter))
1254 return;
1255 gtk_tree_model_get(GTK_TREE_MODEL(select->model), &iter,
1256 STATUS_COLUMN_STATUS_ID, &id,
1257 -1);
1258 type = purple_account_get_status_type(select->account, id);
1259 g_free(id);
1261 if (purple_status_type_get_attr(type, "message") == NULL)
1263 gtk_widget_set_sensitive(select->message_view, FALSE);
1265 else
1267 gtk_widget_set_sensitive(select->message_view, TRUE);
1271 static gboolean
1272 status_editor_find_account_in_treemodel(GtkTreeIter *iter,
1273 StatusEditor *status_editor,
1274 PurpleAccount *account)
1276 GtkTreeModel *model;
1277 PurpleAccount *cur;
1279 g_return_val_if_fail(status_editor != NULL, FALSE);
1280 g_return_val_if_fail(account != NULL, FALSE);
1282 model = GTK_TREE_MODEL(status_editor->model);
1284 if (!gtk_tree_model_get_iter_first(model, iter))
1285 return FALSE;
1287 do {
1288 gtk_tree_model_get(model, iter, STATUS_EDITOR_COLUMN_ACCOUNT, &cur, -1);
1289 if (cur == account)
1290 return TRUE;
1291 } while (gtk_tree_model_iter_next(model, iter));
1293 return FALSE;
1296 static void
1297 substatus_editor_remove_dialog(SubStatusEditor *dialog)
1299 GtkTreeIter iter;
1301 if (status_editor_find_account_in_treemodel(&iter, dialog->status_editor, dialog->account))
1303 /* Remove the reference to this dialog from our parent's list store */
1304 gtk_list_store_set(dialog->status_editor->model, &iter,
1305 STATUS_EDITOR_COLUMN_WINDOW, NULL,
1306 -1);
1310 static void
1311 substatus_editor_destroy_cb(GtkWidget *widget, gpointer user_data)
1313 SubStatusEditor *dialog = user_data;
1315 substatus_editor_remove_dialog(dialog);
1316 g_free(dialog);
1319 static void
1320 substatus_editor_cancel_cb(GtkButton *button, gpointer user_data)
1322 SubStatusEditor *dialog = user_data;
1323 gtk_widget_destroy(dialog->window);
1327 static void
1328 substatus_editor_ok_cb(GtkButton *button, gpointer user_data)
1330 SubStatusEditor *dialog = user_data;
1331 StatusEditor *status_editor;
1332 GtkTreeIter iter;
1333 PurpleStatusType *type;
1334 char *id = NULL;
1335 char *message = NULL;
1336 const char *name = NULL, *stock = NULL;
1338 if (!gtk_combo_box_get_active_iter(dialog->box, &iter))
1340 gtk_widget_destroy(dialog->window);
1341 return;
1344 gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), &iter,
1345 STATUS_COLUMN_STATUS_ID, &id,
1346 -1);
1347 type = purple_account_get_status_type(dialog->account, id);
1348 if (purple_status_type_get_attr(type, "message") != NULL) {
1349 message = talkatu_markup_get_html(dialog->message_buffer, NULL);
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 *editor;
1382 GtkWidget *label;
1383 GtkWidget *vbox;
1384 GtkWidget *win;
1385 GtkTreeIter iter;
1386 GtkCellRenderer *rend;
1387 char *status_id = NULL;
1388 char *message = NULL;
1389 gboolean parent_dialog_has_substatus = FALSE;
1390 GList *list;
1391 gboolean select = FALSE;
1393 g_return_if_fail(status_editor != NULL);
1394 g_return_if_fail(account != NULL);
1396 status_editor_find_account_in_treemodel(&iter, status_editor, account);
1397 gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter,
1398 STATUS_EDITOR_COLUMN_WINDOW, &dialog,
1399 -1);
1400 if (dialog != NULL)
1402 gtk_window_present(GTK_WINDOW(dialog->window));
1403 return;
1406 dialog = g_new0(SubStatusEditor, 1);
1407 gtk_list_store_set(status_editor->model, &iter,
1408 STATUS_EDITOR_COLUMN_WINDOW, dialog,
1409 -1);
1410 dialog->status_editor = status_editor;
1411 dialog->account = account;
1413 tmp = g_strdup_printf(_("Status for %s"), purple_account_get_username(account));
1414 dialog->window = win = pidgin_create_dialog(tmp, PIDGIN_HIG_BORDER, "substatus", TRUE);
1415 g_free(tmp);
1417 g_signal_connect(G_OBJECT(win), "destroy",
1418 G_CALLBACK(substatus_editor_destroy_cb), dialog);
1420 /* Setup the vbox */
1421 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
1423 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1425 /* Status type */
1426 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BOX_SPACE);
1427 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1429 label = gtk_label_new_with_mnemonic(_("_Status:"));
1430 gtk_label_set_xalign(GTK_LABEL(label), 0);
1431 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1432 gtk_size_group_add_widget(sg, label);
1434 dialog->model = gtk_list_store_new(STATUS_NUM_COLUMNS,
1435 G_TYPE_STRING,
1436 G_TYPE_STRING,
1437 G_TYPE_STRING);
1438 combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(dialog->model));
1439 dialog->box = GTK_COMBO_BOX(combo);
1441 rend = GTK_CELL_RENDERER(gtk_cell_renderer_pixbuf_new());
1442 g_object_set(G_OBJECT(rend),
1443 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL),
1444 NULL);
1445 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), rend, FALSE);
1446 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), rend,
1447 "stock-id", STATUS_COLUMN_ICON, NULL);
1449 rend = GTK_CELL_RENDERER(gtk_cell_renderer_text_new());
1450 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), rend, TRUE);
1451 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), rend,
1452 "text", STATUS_COLUMN_STATUS_NAME, NULL);
1454 g_signal_connect(G_OBJECT(combo), "changed",
1455 G_CALLBACK(substatus_selection_changed_cb), dialog);
1457 gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 0);
1459 /* Status mesage */
1460 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PIDGIN_HIG_BOX_SPACE);
1461 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1463 label = gtk_label_new_with_mnemonic(_("_Message:"));
1464 gtk_label_set_xalign(GTK_LABEL(label), 0);
1465 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1466 gtk_size_group_add_widget(sg, label);
1468 editor = talkatu_editor_new();
1469 gtk_box_pack_start(GTK_BOX(hbox), editor, TRUE, TRUE, 0);
1471 dialog->message_view = talkatu_editor_get_view(TALKATU_EDITOR(editor));
1472 dialog->message_buffer = talkatu_html_buffer_new();
1473 gtk_text_view_set_buffer(GTK_TEXT_VIEW(dialog->message_view), dialog->message_buffer);
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(
1500 purple_savedstatus_substatus_get_status_type(substatus));
1504 /* TODO: Else get the generic status type from our parent */
1506 if (message) {
1507 talkatu_markup_set_html(TALKATU_BUFFER(dialog->message_buffer), message, -1);
1510 for (list = purple_account_get_status_types(account); list; list = list->next)
1512 PurpleStatusType *status_type;
1513 const char *id, *name;
1514 PurpleStatusPrimitive prim;
1516 status_type = list->data;
1519 * Only allow users to select statuses that are flagged as
1520 * "user settable" and that aren't independent.
1522 if (!purple_status_type_is_user_settable(status_type) ||
1523 purple_status_type_is_independent(status_type))
1524 continue;
1526 id = purple_status_type_get_id(status_type);
1527 prim = purple_status_type_get_primitive(status_type);
1528 name = purple_status_type_get_name(status_type);
1530 gtk_list_store_append(dialog->model, &iter);
1531 gtk_list_store_set(dialog->model, &iter,
1532 STATUS_COLUMN_ICON, pidgin_stock_id_from_status_primitive(prim),
1533 STATUS_COLUMN_STATUS_ID, id,
1534 STATUS_COLUMN_STATUS_NAME, name,
1535 -1);
1536 if ((status_id != NULL) && purple_strequal(status_id, id))
1538 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter);
1539 select = TRUE;
1543 if (!select)
1544 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1546 if (parent_dialog_has_substatus) {
1547 /* These two were gotten from the parent tree model, so they need to be freed */
1548 g_free(status_id);
1549 g_free(message);
1552 gtk_widget_show_all(win);
1553 g_object_unref(sg);
1557 /**************************************************************************
1558 * Utilities *
1559 **************************************************************************/
1561 enum {
1562 SS_MENU_ENTRY_TYPE_PRIMITIVE,
1563 SS_MENU_ENTRY_TYPE_SAVEDSTATUS
1566 enum {
1567 /* _SSMenuEntryType */
1568 SS_MENU_TYPE_COLUMN,
1571 * This is a GdkPixbuf (the other columns are strings).
1572 * This column is visible.
1574 SS_MENU_ICON_COLUMN,
1576 /* The text displayed on the status box. This column is visible. */
1577 SS_MENU_TEXT_COLUMN,
1580 * This value depends on SS_MENU_TYPE_COLUMN. For _SAVEDSTATUS types,
1581 * this is the creation time. For _PRIMITIVE types,
1582 * this is the PurpleStatusPrimitive.
1584 SS_MENU_DATA_COLUMN,
1587 * This is the emblem to use for this status
1589 SS_MENU_EMBLEM_COLUMN,
1592 * And whether or not that emblem is visible
1594 SS_MENU_EMBLEM_VISIBLE_COLUMN,
1596 SS_MENU_NUM_COLUMNS
1599 static void
1600 status_menu_cb(GtkComboBox *widget, void(*callback)(PurpleSavedStatus*))
1602 GtkTreeIter iter;
1603 int type;
1604 gpointer data;
1605 PurpleSavedStatus *status = NULL;
1607 if (!gtk_combo_box_get_active_iter(widget, &iter))
1608 return;
1610 gtk_tree_model_get(gtk_combo_box_get_model(widget), &iter,
1611 SS_MENU_TYPE_COLUMN, &type,
1612 SS_MENU_DATA_COLUMN, &data,
1613 -1);
1615 if (type == SS_MENU_ENTRY_TYPE_PRIMITIVE)
1617 PurpleStatusPrimitive primitive = GPOINTER_TO_INT(data);
1618 status = purple_savedstatus_find_transient_by_type_and_message(primitive, NULL);
1619 if (status == NULL)
1620 status = purple_savedstatus_new(NULL, primitive);
1622 else if (type == SS_MENU_ENTRY_TYPE_SAVEDSTATUS)
1623 status = purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data));
1625 callback(status);
1628 static gint
1629 saved_status_sort_alphabetically_func(gconstpointer a, gconstpointer b)
1631 const PurpleSavedStatus *saved_status_a = a;
1632 const PurpleSavedStatus *saved_status_b = b;
1633 return g_utf8_collate(purple_savedstatus_get_title(saved_status_a),
1634 purple_savedstatus_get_title(saved_status_b));
1637 static gboolean pidgin_status_menu_add_primitive(GtkListStore *model, GtkWidget *w, PurpleStatusPrimitive primitive,
1638 PurpleSavedStatus *current_status)
1640 GtkTreeIter iter;
1641 gboolean currently_selected = FALSE;
1643 gtk_list_store_append(model, &iter);
1644 gtk_list_store_set(model, &iter,
1645 SS_MENU_TYPE_COLUMN, SS_MENU_ENTRY_TYPE_PRIMITIVE,
1646 SS_MENU_ICON_COLUMN, pidgin_stock_id_from_status_primitive(primitive),
1647 SS_MENU_TEXT_COLUMN, purple_primitive_get_name_from_type(primitive),
1648 SS_MENU_DATA_COLUMN, GINT_TO_POINTER(primitive),
1649 SS_MENU_EMBLEM_VISIBLE_COLUMN, FALSE,
1650 -1);
1652 if (purple_savedstatus_is_transient(current_status)
1653 && !purple_savedstatus_has_substatuses(current_status)
1654 && purple_savedstatus_get_primitive_type(current_status) == primitive)
1655 currently_selected = TRUE;
1657 return currently_selected;
1660 static void
1661 pidgin_status_menu_update_iter(GtkWidget *combobox, GtkListStore *store, GtkTreeIter *iter,
1662 PurpleSavedStatus *status)
1664 PurpleStatusPrimitive primitive;
1666 if (store == NULL)
1667 store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox)));
1669 primitive = purple_savedstatus_get_primitive_type(status);
1670 gtk_list_store_set(store, iter,
1671 SS_MENU_TYPE_COLUMN, SS_MENU_ENTRY_TYPE_SAVEDSTATUS,
1672 SS_MENU_ICON_COLUMN, pidgin_stock_id_from_status_primitive(primitive),
1673 SS_MENU_TEXT_COLUMN, purple_savedstatus_get_title(status),
1674 SS_MENU_DATA_COLUMN, GINT_TO_POINTER(purple_savedstatus_get_creation_time(status)),
1675 SS_MENU_EMBLEM_COLUMN, GTK_STOCK_SAVE,
1676 SS_MENU_EMBLEM_VISIBLE_COLUMN, TRUE,
1677 -1);
1680 static gboolean
1681 pidgin_status_menu_find_iter(GtkListStore *store, GtkTreeIter *iter, PurpleSavedStatus *find)
1683 int type;
1684 gpointer data;
1685 time_t creation_time = purple_savedstatus_get_creation_time(find);
1686 GtkTreeModel *model = GTK_TREE_MODEL(store);
1688 if (!gtk_tree_model_get_iter_first(model, iter))
1689 return FALSE;
1691 do {
1692 gtk_tree_model_get(model, iter,
1693 SS_MENU_TYPE_COLUMN, &type,
1694 SS_MENU_DATA_COLUMN, &data,
1695 -1);
1696 if (type == SS_MENU_ENTRY_TYPE_PRIMITIVE)
1697 continue;
1698 if (GPOINTER_TO_INT(data) == creation_time)
1699 return TRUE;
1700 } while (gtk_tree_model_iter_next(model, iter));
1702 return FALSE;
1705 static void
1706 savedstatus_added_cb(PurpleSavedStatus *status, GtkWidget *combobox)
1708 GtkListStore *store;
1709 GtkTreeIter iter;
1711 if (purple_savedstatus_is_transient(status))
1712 return;
1714 store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox)));
1715 gtk_list_store_append(store, &iter);
1716 pidgin_status_menu_update_iter(combobox, store, &iter, status);
1719 static void
1720 savedstatus_deleted_cb(PurpleSavedStatus *status, GtkWidget *combobox)
1722 GtkListStore *store;
1723 GtkTreeIter iter;
1725 if (purple_savedstatus_is_transient(status))
1726 return;
1728 store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox)));
1729 if (pidgin_status_menu_find_iter(store, &iter, status))
1730 gtk_list_store_remove(store, &iter);
1733 static void
1734 savedstatus_modified_cb(PurpleSavedStatus *status, GtkWidget *combobox)
1736 GtkListStore *store;
1737 GtkTreeIter iter;
1739 if (purple_savedstatus_is_transient(status))
1740 return;
1742 store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combobox)));
1743 if (pidgin_status_menu_find_iter(store, &iter, status))
1744 pidgin_status_menu_update_iter(combobox, store, &iter, status);
1747 GtkWidget *pidgin_status_menu(PurpleSavedStatus *current_status, GCallback callback)
1749 GtkWidget *combobox;
1750 GtkListStore *model;
1751 GList *sorted, *cur;
1752 int i = 0;
1753 int index = -1;
1754 GtkTreeIter iter;
1755 GtkCellRenderer *text_rend;
1756 GtkCellRenderer *icon_rend;
1757 GtkCellRenderer *emblem_rend;
1759 model = gtk_list_store_new(SS_MENU_NUM_COLUMNS, G_TYPE_INT, G_TYPE_STRING,
1760 G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
1762 combobox = gtk_combo_box_new();
1764 if (pidgin_status_menu_add_primitive(model, combobox, PURPLE_STATUS_AVAILABLE, current_status))
1765 index = i;
1766 i++;
1768 if (pidgin_status_menu_add_primitive(model, combobox, PURPLE_STATUS_AWAY, current_status))
1769 index = i;
1770 i++;
1772 if (pidgin_status_menu_add_primitive(model, combobox, PURPLE_STATUS_INVISIBLE, current_status))
1773 index = i;
1774 i++;
1776 if (pidgin_status_menu_add_primitive(model, combobox, PURPLE_STATUS_OFFLINE, current_status))
1777 index = i;
1778 i++;
1780 sorted = g_list_copy((GList *)purple_savedstatuses_get_all());
1781 sorted = g_list_sort(sorted, saved_status_sort_alphabetically_func);
1782 for (cur = sorted; cur; cur = cur->next)
1784 PurpleSavedStatus *status = (PurpleSavedStatus *) cur->data;
1785 if (!purple_savedstatus_is_transient(status))
1787 gtk_list_store_append(model, &iter);
1789 pidgin_status_menu_update_iter(combobox, model, &iter, status);
1791 if (status == current_status)
1792 index = i;
1793 i++;
1796 g_list_free(sorted);
1798 gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(model));
1800 text_rend = gtk_cell_renderer_text_new();
1801 icon_rend = gtk_cell_renderer_pixbuf_new();
1802 emblem_rend = gtk_cell_renderer_pixbuf_new();
1803 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), icon_rend, FALSE);
1804 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), text_rend, TRUE);
1805 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), emblem_rend, FALSE);
1806 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox), icon_rend, "stock-id", SS_MENU_ICON_COLUMN, NULL);
1807 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox), text_rend, "markup", SS_MENU_TEXT_COLUMN, NULL);
1808 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combobox), emblem_rend,
1809 "stock-id", SS_MENU_EMBLEM_COLUMN, "visible", SS_MENU_EMBLEM_VISIBLE_COLUMN, NULL);
1810 g_object_set(G_OBJECT(icon_rend),
1811 "stock-size", gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL),
1812 NULL);
1813 g_object_set(text_rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1815 gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), index);
1816 g_signal_connect(G_OBJECT(combobox), "changed", G_CALLBACK(status_menu_cb), callback);
1818 /* Make sure the list is updated dynamically when a substatus is changed/deleted
1819 * or a new one is added. */
1820 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-added",
1821 combobox, G_CALLBACK(savedstatus_added_cb), combobox);
1822 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-deleted",
1823 combobox, G_CALLBACK(savedstatus_deleted_cb), combobox);
1824 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-modified",
1825 combobox, G_CALLBACK(savedstatus_modified_cb), combobox);
1826 g_signal_connect(G_OBJECT(combobox), "destroy",
1827 G_CALLBACK(purple_signals_disconnect_by_handle), NULL);
1829 return combobox;
1833 /**************************************************************************
1834 * GTK+ saved status glue
1835 **************************************************************************/
1837 void *
1838 pidgin_status_get_handle(void)
1840 static int handle;
1842 return &handle;
1845 void
1846 pidgin_status_init(void)
1848 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/status");
1849 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/status/dialog");
1850 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/status/dialog/width", 550);
1851 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/status/dialog/height", 250);
1854 void
1855 pidgin_status_uninit(void)
1857 pidgin_status_window_hide();