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