1 /**
2  * @file gtkroomlist.c GTK+ Room List 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 #include "pidgin.h"
29 #include "gtkutils.h"
30 #include "pidginstock.h"
31 #include "pidgintooltip.h"
32 
33 #include "debug.h"
34 #include "account.h"
35 #include "connection.h"
36 #include "notify.h"
37 
38 #include "gtkroomlist.h"
39 
40 typedef struct _PidginRoomlistDialog {
41 	GtkWidget *window;
42 	GtkWidget *account_widget;
43 	GtkWidget *progress;
44 	GtkWidget *sw;
45 
46 	GtkWidget *stop_button;
47 	GtkWidget *list_button;
48 	GtkWidget *add_button;
49 	GtkWidget *join_button;
50 	GtkWidget *close_button;
51 
52 	PurpleAccount *account;
53 	PurpleRoomlist *roomlist;
54 
55 	gboolean pg_needs_pulse;
56 	guint pg_update_to;
57 } PidginRoomlistDialog;
58 
59 typedef struct _PidginRoomlist {
60 	PidginRoomlistDialog *dialog;
61 	GtkTreeStore *model;
62 	GtkWidget *tree;
63 	GHashTable *cats; /**< Meow. */
64 	gint num_rooms, total_rooms;
65 	GtkWidget *tipwindow;
66 	GdkRectangle tip_rect;
67 	PangoLayout *tip_layout;
68 	PangoLayout *tip_name_layout;
69 	int tip_height;
70 	int tip_width;
71 	int tip_name_height;
72 	int tip_name_width;
73 } PidginRoomlist;
74 
75 enum {
76 	NAME_COLUMN = 0,
77 	ROOM_COLUMN,
78 	NUM_OF_COLUMNS,
79 };
80 
81 static GList *roomlists = NULL;
82 
delete_win_cb(GtkWidget * w,GdkEventAny * e,gpointer d)83 static gint delete_win_cb(GtkWidget *w, GdkEventAny *e, gpointer d)
84 {
85 	PidginRoomlistDialog *dialog = d;
86 
87 	if (dialog->roomlist && purple_roomlist_get_in_progress(dialog->roomlist))
88 		purple_roomlist_cancel_get_list(dialog->roomlist);
89 
90 	if (dialog->pg_update_to > 0)
91 		purple_timeout_remove(dialog->pg_update_to);
92 
93 	if (dialog->roomlist) {
94 		PidginRoomlist *rl = dialog->roomlist->ui_data;
95 
96 		if (dialog->pg_update_to > 0)
97 			/* yes, that's right, unref it twice. */
98 			purple_roomlist_unref(dialog->roomlist);
99 
100 		if (rl)
101 			rl->dialog = NULL;
102 		purple_roomlist_unref(dialog->roomlist);
103 	}
104 
105 	dialog->progress = NULL;
106 	g_free(dialog);
107 
108 	return FALSE;
109 }
110 
dialog_select_account_cb(GObject * w,PurpleAccount * account,PidginRoomlistDialog * dialog)111 static void dialog_select_account_cb(GObject *w, PurpleAccount *account,
112 				     PidginRoomlistDialog *dialog)
113 {
114 	gboolean change = (account != dialog->account);
115 	dialog->account = account;
116 
117 	if (change && dialog->roomlist) {
118 		PidginRoomlist *rl = dialog->roomlist->ui_data;
119 		if (rl->tree) {
120 			gtk_widget_destroy(rl->tree);
121 			rl->tree = NULL;
122 		}
123 		purple_roomlist_unref(dialog->roomlist);
124 		dialog->roomlist = NULL;
125 	}
126 }
127 
list_button_cb(GtkButton * button,PidginRoomlistDialog * dialog)128 static void list_button_cb(GtkButton *button, PidginRoomlistDialog *dialog)
129 {
130 	PurpleConnection *gc;
131 	PidginRoomlist *rl;
132 
133 	gc = purple_account_get_connection(dialog->account);
134 	if (!gc)
135 		return;
136 
137 	if (dialog->roomlist != NULL) {
138 		rl = dialog->roomlist->ui_data;
139 		gtk_widget_destroy(rl->tree);
140 		purple_roomlist_unref(dialog->roomlist);
141 	}
142 
143 	dialog->roomlist = purple_roomlist_get_list(gc);
144 	if (!dialog->roomlist)
145 		return;
146 	purple_roomlist_ref(dialog->roomlist);
147 	rl = dialog->roomlist->ui_data;
148 	rl->dialog = dialog;
149 
150 	if (dialog->account_widget)
151 		gtk_widget_set_sensitive(dialog->account_widget, FALSE);
152 
153 	gtk_container_add(GTK_CONTAINER(dialog->sw), rl->tree);
154 
155 	/* some protocols (not bundled with libpurple) finish getting their
156 	 * room list immediately */
157 	if(purple_roomlist_get_in_progress(dialog->roomlist)) {
158 		gtk_widget_set_sensitive(dialog->stop_button, TRUE);
159 		gtk_widget_set_sensitive(dialog->list_button, FALSE);
160 	} else {
161 		gtk_widget_set_sensitive(dialog->stop_button, FALSE);
162 		gtk_widget_set_sensitive(dialog->list_button, TRUE);
163 	}
164 	gtk_widget_set_sensitive(dialog->add_button, FALSE);
165 	gtk_widget_set_sensitive(dialog->join_button, FALSE);
166 }
167 
stop_button_cb(GtkButton * button,PidginRoomlistDialog * dialog)168 static void stop_button_cb(GtkButton *button, PidginRoomlistDialog *dialog)
169 {
170 	purple_roomlist_cancel_get_list(dialog->roomlist);
171 
172 	if (dialog->account_widget)
173 		gtk_widget_set_sensitive(dialog->account_widget, TRUE);
174 
175 	gtk_widget_set_sensitive(dialog->stop_button, FALSE);
176 	gtk_widget_set_sensitive(dialog->list_button, TRUE);
177 	gtk_widget_set_sensitive(dialog->add_button, FALSE);
178 	gtk_widget_set_sensitive(dialog->join_button, FALSE);
179 }
180 
close_button_cb(GtkButton * button,PidginRoomlistDialog * dialog)181 static void close_button_cb(GtkButton *button, PidginRoomlistDialog *dialog)
182 {
183 	GtkWidget *window = dialog->window;
184 
185 	delete_win_cb(NULL, NULL, dialog);
186 	gtk_widget_destroy(window);
187 }
188 
189 struct _menu_cb_info {
190 	PurpleRoomlist *list;
191 	PurpleRoomlistRoom *room;
192 };
193 
194 static void
selection_changed_cb(GtkTreeSelection * selection,PidginRoomlist * grl)195 selection_changed_cb(GtkTreeSelection *selection, PidginRoomlist *grl) {
196 	GtkTreeIter iter;
197 	GValue val;
198 	PurpleRoomlistRoom *room;
199 	static struct _menu_cb_info *info;
200 	PidginRoomlistDialog *dialog = grl->dialog;
201 
202 	if (gtk_tree_selection_get_selected(selection, NULL, &iter)) {
203 		val.g_type = 0;
204 		gtk_tree_model_get_value(GTK_TREE_MODEL(grl->model), &iter, ROOM_COLUMN, &val);
205 		room = g_value_get_pointer(&val);
206 		if (!room || !(room->type & PURPLE_ROOMLIST_ROOMTYPE_ROOM)) {
207 			gtk_widget_set_sensitive(dialog->join_button, FALSE);
208 			gtk_widget_set_sensitive(dialog->add_button, FALSE);
209 			return;
210 		}
211 
212 		info = g_new0(struct _menu_cb_info, 1);
213 		info->list = dialog->roomlist;
214 		info->room = room;
215 
216 		g_object_set_data_full(G_OBJECT(dialog->join_button), "room-info",
217 							   info, g_free);
218 		g_object_set_data(G_OBJECT(dialog->add_button), "room-info", info);
219 
220 		gtk_widget_set_sensitive(dialog->add_button, TRUE);
221 		gtk_widget_set_sensitive(dialog->join_button, TRUE);
222 	} else {
223 		gtk_widget_set_sensitive(dialog->add_button, FALSE);
224 		gtk_widget_set_sensitive(dialog->join_button, FALSE);
225 	}
226 }
227 
do_add_room_cb(GtkWidget * w,struct _menu_cb_info * info)228 static void do_add_room_cb(GtkWidget *w, struct _menu_cb_info *info)
229 {
230 	char *name;
231 	PurpleConnection *gc = purple_account_get_connection(info->list->account);
232 	PurplePluginProtocolInfo *prpl_info = NULL;
233 
234 	if(gc != NULL)
235 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
236 
237 	if(prpl_info != NULL && prpl_info->roomlist_room_serialize)
238 		name = prpl_info->roomlist_room_serialize(info->room);
239 	else
240 		name = g_strdup(info->room->name);
241 
242 	purple_blist_request_add_chat(info->list->account, NULL, NULL, name);
243 
244 	g_free(name);
245 }
246 
add_room_to_blist_cb(GtkButton * button,PidginRoomlistDialog * dialog)247 static void add_room_to_blist_cb(GtkButton *button, PidginRoomlistDialog *dialog)
248 {
249 	PurpleRoomlist *rl = dialog->roomlist;
250 	PidginRoomlist *grl = rl->ui_data;
251 	struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "room-info");
252 
253 	if(info != NULL)
254 		do_add_room_cb(grl->tree, info);
255 }
256 
do_join_cb(GtkWidget * w,struct _menu_cb_info * info)257 static void do_join_cb(GtkWidget *w, struct _menu_cb_info *info)
258 {
259 	purple_roomlist_room_join(info->list, info->room);
260 }
261 
join_button_cb(GtkButton * button,PidginRoomlistDialog * dialog)262 static void join_button_cb(GtkButton *button, PidginRoomlistDialog *dialog)
263 {
264 	PurpleRoomlist *rl = dialog->roomlist;
265 	PidginRoomlist *grl = rl->ui_data;
266 	struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "room-info");
267 
268 	if(info != NULL)
269 		do_join_cb(grl->tree, info);
270 }
271 
row_activated_cb(GtkTreeView * tv,GtkTreePath * path,GtkTreeViewColumn * arg2,PurpleRoomlist * list)272 static void row_activated_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *arg2,
273                       PurpleRoomlist *list)
274 {
275 	PidginRoomlist *grl = list->ui_data;
276 	GtkTreeIter iter;
277 	PurpleRoomlistRoom *room;
278 	GValue val;
279 	struct _menu_cb_info info;
280 
281 	gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path);
282 	val.g_type = 0;
283 	gtk_tree_model_get_value(GTK_TREE_MODEL(grl->model), &iter, ROOM_COLUMN, &val);
284 	room = g_value_get_pointer(&val);
285 	if (!room || !(room->type & PURPLE_ROOMLIST_ROOMTYPE_ROOM))
286 		return;
287 
288 	info.list = list;
289 	info.room = room;
290 
291 	do_join_cb(GTK_WIDGET(tv), &info);
292 }
293 
room_click_cb(GtkWidget * tv,GdkEventButton * event,PurpleRoomlist * list)294 static gboolean room_click_cb(GtkWidget *tv, GdkEventButton *event, PurpleRoomlist *list)
295 {
296 	GtkTreePath *path;
297 	PidginRoomlist *grl = list->ui_data;
298 	GValue val;
299 	PurpleRoomlistRoom *room;
300 	GtkTreeIter iter;
301 	GtkWidget *menu;
302 	static struct _menu_cb_info info; /* XXX? */
303 
304 	if (event->button != 3 || event->type != GDK_BUTTON_PRESS)
305 		return FALSE;
306 
307 	/* Here we figure out which room was clicked */
308 	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL))
309 		return FALSE;
310 	gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path);
311 	gtk_tree_path_free(path);
312 	val.g_type = 0;
313 	gtk_tree_model_get_value (GTK_TREE_MODEL(grl->model), &iter, ROOM_COLUMN, &val);
314 	room = g_value_get_pointer(&val);
315 
316 	if (!room || !(room->type & PURPLE_ROOMLIST_ROOMTYPE_ROOM))
317 		return FALSE;
318 
319 	info.list = list;
320 	info.room = room;
321 
322 	menu = gtk_menu_new();
323 	pidgin_new_item_from_stock(menu, _("_Join"), PIDGIN_STOCK_CHAT,
324 		                         G_CALLBACK(do_join_cb), &info, 0, 0, NULL);
325 	pidgin_new_item_from_stock(menu, _("_Add"), GTK_STOCK_ADD,
326 		                         G_CALLBACK(do_add_room_cb), &info, 0, 0, NULL);
327 
328 	gtk_widget_show_all(menu);
329 	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, event->time);
330 
331 	return FALSE;
332 }
333 
row_expanded_cb(GtkTreeView * treeview,GtkTreeIter * arg1,GtkTreePath * arg2,gpointer user_data)334 static void row_expanded_cb(GtkTreeView *treeview, GtkTreeIter *arg1, GtkTreePath *arg2, gpointer user_data)
335 {
336 	PurpleRoomlist *list = user_data;
337 	PurpleRoomlistRoom *category;
338 	GValue val;
339 
340 	val.g_type = 0;
341 	gtk_tree_model_get_value(gtk_tree_view_get_model(treeview), arg1, ROOM_COLUMN, &val);
342 	category = g_value_get_pointer(&val);
343 
344 	if (!category->expanded_once) {
345 		purple_roomlist_expand_category(list, category);
346 		category->expanded_once = TRUE;
347 	}
348 }
349 
350 #define SMALL_SPACE 6
351 #define TOOLTIP_BORDER 12
352 
353 static gboolean
pidgin_roomlist_paint_tooltip(GtkWidget * widget,gpointer user_data)354 pidgin_roomlist_paint_tooltip(GtkWidget *widget, gpointer user_data)
355 {
356 	PurpleRoomlist *list = user_data;
357 	PidginRoomlist *grl = list->ui_data;
358 	GtkStyle *style;
359 	int current_height, max_width;
360 	int max_text_width;
361 	GtkTextDirection dir = gtk_widget_get_direction(GTK_WIDGET(grl->tree));
362 
363 	style = grl->tipwindow->style;
364 
365 	max_text_width = MAX(grl->tip_width, grl->tip_name_width);
366 	max_width = TOOLTIP_BORDER + SMALL_SPACE + max_text_width + TOOLTIP_BORDER;
367 
368 	current_height = 12;
369 
370 	if (dir == GTK_TEXT_DIR_RTL) {
371 		gtk_paint_layout(style, grl->tipwindow->window, GTK_STATE_NORMAL, FALSE,
372 				NULL, grl->tipwindow, "tooltip",
373 				max_width - (TOOLTIP_BORDER + SMALL_SPACE) - PANGO_PIXELS(600000),
374 				current_height, grl->tip_name_layout);
375 	} else {
376 		gtk_paint_layout (style, grl->tipwindow->window, GTK_STATE_NORMAL, FALSE,
377 				NULL, grl->tipwindow, "tooltip",
378 				TOOLTIP_BORDER + SMALL_SPACE, current_height, grl->tip_name_layout);
379 	}
380 	if (dir != GTK_TEXT_DIR_RTL) {
381 		gtk_paint_layout (style, grl->tipwindow->window, GTK_STATE_NORMAL, FALSE,
382 				NULL, grl->tipwindow, "tooltip",
383 				TOOLTIP_BORDER + SMALL_SPACE, current_height + grl->tip_name_height, grl->tip_layout);
384 	} else {
385 		gtk_paint_layout(style, grl->tipwindow->window, GTK_STATE_NORMAL, FALSE,
386 				NULL, grl->tipwindow, "tooltip",
387 				max_width - (TOOLTIP_BORDER + SMALL_SPACE) - PANGO_PIXELS(600000),
388 				current_height + grl->tip_name_height,
389 				grl->tip_layout);
390 	}
391 	return FALSE;
392 }
393 
pidgin_roomlist_create_tip(PurpleRoomlist * list,GtkTreePath * path)394 static gboolean pidgin_roomlist_create_tip(PurpleRoomlist *list, GtkTreePath *path)
395 {
396 	PidginRoomlist *grl = list->ui_data;
397 	PurpleRoomlistRoom *room;
398 	GtkTreeIter iter;
399 	GValue val;
400 	gchar *name, *tmp, *node_name;
401 	GString *tooltip_text = NULL;
402 	GList *l, *k;
403 	gint j;
404 	gboolean first = TRUE;
405 
406 #if 0
407 	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), grl->tip_rect.x, grl->tip_rect.y + (grl->tip_rect.height/2),
408 		&path, NULL, NULL, NULL))
409 		return FALSE;
410 #endif
411 	gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path);
412 
413 	val.g_type = 0;
414 	gtk_tree_model_get_value(GTK_TREE_MODEL(grl->model), &iter, ROOM_COLUMN, &val);
415 	room = g_value_get_pointer(&val);
416 
417 	if (!room || !(room->type & PURPLE_ROOMLIST_ROOMTYPE_ROOM))
418 		return FALSE;
419 
420 	tooltip_text = g_string_new("");
421 	gtk_tree_model_get(GTK_TREE_MODEL(grl->model), &iter, NAME_COLUMN, &name, -1);
422 
423 	for (j = NUM_OF_COLUMNS, l = room->fields, k = list->fields; l && k; j++, l = l->next, k = k->next) {
424 		PurpleRoomlistField *f = k->data;
425 		gchar *label;
426 		if (f->hidden)
427 			continue;
428 		label = g_markup_escape_text(f->label, -1);
429 		switch (f->type) {
430 			case PURPLE_ROOMLIST_FIELD_BOOL:
431 				g_string_append_printf(tooltip_text, "%s<b>%s:</b> %s", first ? "" : "\n", label, l->data ? "True" : "False");
432 				break;
433 			case PURPLE_ROOMLIST_FIELD_INT:
434 				g_string_append_printf(tooltip_text, "%s<b>%s:</b> %d", first ? "" : "\n", label, GPOINTER_TO_INT(l->data));
435 				break;
436 			case PURPLE_ROOMLIST_FIELD_STRING:
437 				tmp = g_markup_escape_text((char *)l->data, -1);
438 				g_string_append_printf(tooltip_text, "%s<b>%s:</b> %s", first ? "" : "\n", label, tmp);
439 				g_free(tmp);
440 				break;
441 		}
442 		first = FALSE;
443 		g_free(label);
444 	}
445 
446 	grl->tip_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL);
447 	grl->tip_name_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL);
448 
449 	tmp = g_markup_escape_text(name, -1);
450 	g_free(name);
451 	node_name = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>", tmp);
452 	g_free(tmp);
453 
454 	pango_layout_set_markup(grl->tip_layout, tooltip_text->str, -1);
455 	pango_layout_set_wrap(grl->tip_layout, PANGO_WRAP_WORD);
456 	pango_layout_set_width(grl->tip_layout, 600000);
457 
458 	pango_layout_get_size (grl->tip_layout, &grl->tip_width, &grl->tip_height);
459 	grl->tip_width = PANGO_PIXELS(grl->tip_width);
460 	grl->tip_height = PANGO_PIXELS(grl->tip_height);
461 
462 	pango_layout_set_markup(grl->tip_name_layout, node_name, -1);
463 	pango_layout_set_wrap(grl->tip_name_layout, PANGO_WRAP_WORD);
464 	pango_layout_set_width(grl->tip_name_layout, 600000);
465 
466 	pango_layout_get_size (grl->tip_name_layout, &grl->tip_name_width, &grl->tip_name_height);
467 	grl->tip_name_width = PANGO_PIXELS(grl->tip_name_width) + SMALL_SPACE;
468 	grl->tip_name_height = MAX(PANGO_PIXELS(grl->tip_name_height), SMALL_SPACE);
469 
470 	g_free(node_name);
471 	g_string_free(tooltip_text, TRUE);
472 
473 	return TRUE;
474 }
475 
476 static gboolean
pidgin_roomlist_create_tooltip(GtkWidget * widget,GtkTreePath * path,gpointer data,int * w,int * h)477 pidgin_roomlist_create_tooltip(GtkWidget *widget, GtkTreePath *path,
478 		gpointer data, int *w, int *h)
479 {
480 	PurpleRoomlist *list = data;
481 	PidginRoomlist *grl = list->ui_data;
482 	grl->tipwindow = widget;
483 	if (!pidgin_roomlist_create_tip(data, path))
484 		return FALSE;
485 	if (w)
486 		*w = TOOLTIP_BORDER + SMALL_SPACE +
487 			MAX(grl->tip_width, grl->tip_name_width) + TOOLTIP_BORDER;
488 	if (h)
489 		*h = TOOLTIP_BORDER + grl->tip_height + grl->tip_name_height
490 			+ TOOLTIP_BORDER;
491 	return TRUE;
492 }
493 
account_filter_func(PurpleAccount * account)494 static gboolean account_filter_func(PurpleAccount *account)
495 {
496 	PurpleConnection *conn = purple_account_get_connection(account);
497 	PurplePluginProtocolInfo *prpl_info = NULL;
498 
499 	if (conn && PURPLE_CONNECTION_IS_CONNECTED(conn))
500 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(conn->prpl);
501 
502 	return (prpl_info && prpl_info->roomlist_get_list != NULL);
503 }
504 
505 gboolean
pidgin_roomlist_is_showable()506 pidgin_roomlist_is_showable()
507 {
508 	GList *c;
509 	PurpleConnection *gc;
510 
511 	for (c = purple_connections_get_all(); c != NULL; c = c->next) {
512 		gc = c->data;
513 
514 		if (account_filter_func(purple_connection_get_account(gc)))
515 			return TRUE;
516 	}
517 
518 	return FALSE;
519 }
520 
521 static PidginRoomlistDialog *
pidgin_roomlist_dialog_new_with_account(PurpleAccount * account)522 pidgin_roomlist_dialog_new_with_account(PurpleAccount *account)
523 {
524 	PidginRoomlistDialog *dialog;
525 	GtkWidget *window, *vbox, *vbox2, *bbox;
526 
527 	dialog = g_new0(PidginRoomlistDialog, 1);
528 	dialog->account = account;
529 
530 	/* Create the window. */
531 	dialog->window = window = pidgin_create_dialog(_("Room List"), PIDGIN_HIG_BORDER, "room list", TRUE);
532 
533 	g_signal_connect(G_OBJECT(window), "delete_event",
534 					 G_CALLBACK(delete_win_cb), dialog);
535 
536 	/* Create the parent vbox for everything. */
537 	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window), FALSE, PIDGIN_HIG_BORDER);
538 
539 	vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
540 	gtk_container_add(GTK_CONTAINER(vbox), vbox2);
541 	gtk_widget_show(vbox2);
542 
543 	/* accounts dropdown list */
544 	dialog->account_widget = pidgin_account_option_menu_new(dialog->account, FALSE,
545 	                         G_CALLBACK(dialog_select_account_cb), account_filter_func, dialog);
546 	if (!dialog->account) /* this is normally null, and we normally don't care what the first selected item is */
547 		dialog->account = pidgin_account_option_menu_get_selected(dialog->account_widget);
548 	pidgin_add_widget_to_vbox(GTK_BOX(vbox2), _("_Account:"), NULL, dialog->account_widget, TRUE, NULL);
549 
550 	/* scrolled window */
551 	dialog->sw = pidgin_make_scrollable(NULL, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN, -1, 250);
552 	gtk_box_pack_start(GTK_BOX(vbox2), dialog->sw, TRUE, TRUE, 0);
553 
554 	/* progress bar */
555 	dialog->progress = gtk_progress_bar_new();
556 	gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(dialog->progress), 0.1);
557 	gtk_box_pack_start(GTK_BOX(vbox2), dialog->progress, FALSE, FALSE, 0);
558 	gtk_widget_show(dialog->progress);
559 
560 	/* button box */
561 	bbox = pidgin_dialog_get_action_area(GTK_DIALOG(window));
562 	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
563 	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
564 
565 	/* stop button */
566 	dialog->stop_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP,
567 	                 G_CALLBACK(stop_button_cb), dialog);
568 	gtk_widget_set_sensitive(dialog->stop_button, FALSE);
569 
570 	/* list button */
571 	dialog->list_button = pidgin_pixbuf_button_from_stock(_("_Get List"), GTK_STOCK_REFRESH,
572 	                                                    PIDGIN_BUTTON_HORIZONTAL);
573 	gtk_box_pack_start(GTK_BOX(bbox), dialog->list_button, FALSE, FALSE, 0);
574 	g_signal_connect(G_OBJECT(dialog->list_button), "clicked",
575 	                 G_CALLBACK(list_button_cb), dialog);
576 	gtk_widget_show(dialog->list_button);
577 
578 	/* add button */
579 	dialog->add_button = pidgin_pixbuf_button_from_stock(_("_Add Chat"), GTK_STOCK_ADD,
580 	                                                    PIDGIN_BUTTON_HORIZONTAL);
581 	gtk_box_pack_start(GTK_BOX(bbox), dialog->add_button, FALSE, FALSE, 0);
582 	g_signal_connect(G_OBJECT(dialog->add_button), "clicked",
583 	                 G_CALLBACK(add_room_to_blist_cb), dialog);
584 	gtk_widget_set_sensitive(dialog->add_button, FALSE);
585 	gtk_widget_show(dialog->add_button);
586 
587 	/* join button */
588 	dialog->join_button = pidgin_pixbuf_button_from_stock(_("_Join"), PIDGIN_STOCK_CHAT,
589 	                                                    PIDGIN_BUTTON_HORIZONTAL);
590 	gtk_box_pack_start(GTK_BOX(bbox), dialog->join_button, FALSE, FALSE, 0);
591 	g_signal_connect(G_OBJECT(dialog->join_button), "clicked",
592 					 G_CALLBACK(join_button_cb), dialog);
593 	gtk_widget_set_sensitive(dialog->join_button, FALSE);
594 	gtk_widget_show(dialog->join_button);
595 
596 	/* close button */
597 	dialog->close_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE,
598 					 G_CALLBACK(close_button_cb), dialog);
599 
600 	/* show the dialog window and return the dialog */
601 	gtk_widget_show(dialog->window);
602 
603 	return dialog;
604 }
605 
pidgin_roomlist_dialog_show_with_account(PurpleAccount * account)606 void pidgin_roomlist_dialog_show_with_account(PurpleAccount *account)
607 {
608 	PidginRoomlistDialog *dialog = pidgin_roomlist_dialog_new_with_account(account);
609 
610 	if (!dialog)
611 		return;
612 
613 	list_button_cb(GTK_BUTTON(dialog->list_button), dialog);
614 }
615 
pidgin_roomlist_dialog_show(void)616 void pidgin_roomlist_dialog_show(void)
617 {
618 	pidgin_roomlist_dialog_new_with_account(NULL);
619 }
620 
pidgin_roomlist_new(PurpleRoomlist * list)621 static void pidgin_roomlist_new(PurpleRoomlist *list)
622 {
623 	PidginRoomlist *rl = g_new0(PidginRoomlist, 1);
624 
625 	list->ui_data = rl;
626 
627 	rl->cats = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)gtk_tree_row_reference_free);
628 
629 	roomlists = g_list_append(roomlists, list);
630 }
631 
int_cell_data_func(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)632 static void int_cell_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
633                                    GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
634 {
635 	gchar buf[16];
636 	int myint;
637 
638 	gtk_tree_model_get(model, iter, GPOINTER_TO_INT(user_data), &myint, -1);
639 
640 	if (myint)
641 		g_snprintf(buf, sizeof(buf), "%d", myint);
642 	else
643 		buf[0] = '\0';
644 
645 	g_object_set(renderer, "text", buf, NULL);
646 }
647 
648 /* this sorts backwards on purpose, so that clicking name sorts a-z, while clicking users sorts
649    infinity-0. you can still click again to reverse it on any of them. */
int_sort_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)650 static gint int_sort_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
651 {
652 	int c, d;
653 
654 	c = d = 0;
655 
656 	gtk_tree_model_get(model, a, GPOINTER_TO_INT(user_data), &c, -1);
657 	gtk_tree_model_get(model, b, GPOINTER_TO_INT(user_data), &d, -1);
658 
659 	if (c == d)
660 		return 0;
661 	else if (c > d)
662 		return -1;
663 	else
664 		return 1;
665 }
666 
667 static gboolean
_search_func(GtkTreeModel * model,gint column,const gchar * key,GtkTreeIter * iter,gpointer search_data)668 _search_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data)
669 {
670 	gboolean result;
671 	gchar *name, *fold, *fkey;
672 
673 	gtk_tree_model_get(model, iter, column, &name, -1);
674 	fold = g_utf8_casefold(name, -1);
675 	fkey = g_utf8_casefold(key, -1);
676 
677 	result = (g_strstr_len(fold, strlen(fold), fkey) == NULL);
678 
679 	g_free(fold);
680 	g_free(fkey);
681 	g_free(name);
682 
683 	return result;
684 }
685 
pidgin_roomlist_set_fields(PurpleRoomlist * list,GList * fields)686 static void pidgin_roomlist_set_fields(PurpleRoomlist *list, GList *fields)
687 {
688 	PidginRoomlist *grl = list->ui_data;
689 	gint columns = NUM_OF_COLUMNS;
690 	int j;
691 	GtkTreeStore *model;
692 	GtkWidget *tree;
693 	GtkCellRenderer *renderer;
694 	GtkTreeViewColumn *column;
695 	GtkTreeSelection *selection;
696 	GList *l;
697 	GType *types;
698 
699 	g_return_if_fail(grl != NULL);
700 
701 	columns += g_list_length(fields);
702 	types = g_new(GType, columns);
703 
704 	types[NAME_COLUMN] = G_TYPE_STRING;
705 	types[ROOM_COLUMN] = G_TYPE_POINTER;
706 
707 	for (j = NUM_OF_COLUMNS, l = fields; l; l = l->next, j++) {
708 		PurpleRoomlistField *f = l->data;
709 
710 		switch (f->type) {
711 		case PURPLE_ROOMLIST_FIELD_BOOL:
712 			types[j] = G_TYPE_BOOLEAN;
713 			break;
714 		case PURPLE_ROOMLIST_FIELD_INT:
715 			types[j] = G_TYPE_INT;
716 			break;
717 		case PURPLE_ROOMLIST_FIELD_STRING:
718 			types[j] = G_TYPE_STRING;
719 			break;
720 		}
721 	}
722 
723 	model = gtk_tree_store_newv(columns, types);
724 	g_free(types);
725 
726 	tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
727 	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree), TRUE);
728 
729 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
730 	g_signal_connect(G_OBJECT(selection), "changed",
731 					 G_CALLBACK(selection_changed_cb), grl);
732 
733 	g_object_unref(model);
734 
735 	grl->model = model;
736 	grl->tree = tree;
737 	gtk_widget_show(grl->tree);
738 
739 	renderer = gtk_cell_renderer_text_new();
740 	column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer,
741 				"text", NAME_COLUMN, NULL);
742 	gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
743 	                                GTK_TREE_VIEW_COLUMN_GROW_ONLY);
744 	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
745 	gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column), NAME_COLUMN);
746 	gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE);
747 	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
748 
749 	for (j = NUM_OF_COLUMNS, l = fields; l; l = l->next, j++) {
750 		PurpleRoomlistField *f = l->data;
751 
752 		if (f->hidden)
753 			continue;
754 
755 		renderer = gtk_cell_renderer_text_new();
756 		column = gtk_tree_view_column_new_with_attributes(f->label, renderer,
757 		                                                  "text", j, NULL);
758 		gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
759 		                                GTK_TREE_VIEW_COLUMN_GROW_ONLY);
760 		gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
761 		gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column), j);
762 		gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE);
763 		if (f->type == PURPLE_ROOMLIST_FIELD_INT) {
764 			gtk_tree_view_column_set_cell_data_func(column, renderer, int_cell_data_func,
765 			                                        GINT_TO_POINTER(j), NULL);
766 			gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), j, int_sort_func,
767 			                                GINT_TO_POINTER(j), NULL);
768 		}
769 		gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
770 	}
771 
772 	g_signal_connect(G_OBJECT(tree), "button-press-event", G_CALLBACK(room_click_cb), list);
773 	g_signal_connect(G_OBJECT(tree), "row-expanded", G_CALLBACK(row_expanded_cb), list);
774 	g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(row_activated_cb), list);
775 #if 0 /* uncomment this when the tooltips are slightly less annoying and more well behaved */
776 	g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), list);
777 	g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(row_leave_cb), list);
778 #endif
779 	pidgin_tooltip_setup_for_treeview(tree, list,
780 		pidgin_roomlist_create_tooltip,
781 		pidgin_roomlist_paint_tooltip);
782 
783 	/* Enable CTRL+F searching */
784 	gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree), NAME_COLUMN);
785 	gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(tree), _search_func, NULL, NULL);
786 
787 }
788 
pidgin_progress_bar_pulse(gpointer data)789 static gboolean pidgin_progress_bar_pulse(gpointer data)
790 {
791 	PurpleRoomlist *list = data;
792 	PidginRoomlist *rl = list->ui_data;
793 
794 	if (!rl || !rl->dialog || !rl->dialog->pg_needs_pulse) {
795 		if (rl && rl->dialog)
796 			rl->dialog->pg_update_to = 0;
797 		purple_roomlist_unref(list);
798 		return FALSE;
799 	}
800 
801 	gtk_progress_bar_pulse(GTK_PROGRESS_BAR(rl->dialog->progress));
802 	rl->dialog->pg_needs_pulse = FALSE;
803 	return TRUE;
804 }
805 
pidgin_roomlist_add_room(PurpleRoomlist * list,PurpleRoomlistRoom * room)806 static void pidgin_roomlist_add_room(PurpleRoomlist *list, PurpleRoomlistRoom *room)
807 {
808 	PidginRoomlist *rl = list->ui_data;
809 	GtkTreeRowReference *rr, *parentrr = NULL;
810 	GtkTreePath *path;
811 	GtkTreeIter iter, parent, child;
812 	GList *l, *k;
813 	int j;
814 	gboolean append = TRUE;
815 
816 	rl->total_rooms++;
817 	if (room->type == PURPLE_ROOMLIST_ROOMTYPE_ROOM)
818 		rl->num_rooms++;
819 
820 	if (rl->dialog) {
821 		if (rl->dialog->pg_update_to == 0) {
822 			purple_roomlist_ref(list);
823 			rl->dialog->pg_update_to = g_timeout_add(100, pidgin_progress_bar_pulse, list);
824 			gtk_progress_bar_pulse(GTK_PROGRESS_BAR(rl->dialog->progress));
825 		} else
826 			rl->dialog->pg_needs_pulse = TRUE;
827 	}
828 
829 	if (room->parent) {
830 		parentrr = g_hash_table_lookup(rl->cats, room->parent);
831 		path = gtk_tree_row_reference_get_path(parentrr);
832 		if (path) {
833 			PurpleRoomlistRoom *tmproom = NULL;
834 
835 			gtk_tree_model_get_iter(GTK_TREE_MODEL(rl->model), &parent, path);
836 			gtk_tree_path_free(path);
837 
838 			if (gtk_tree_model_iter_children(GTK_TREE_MODEL(rl->model), &child, &parent)) {
839 				gtk_tree_model_get(GTK_TREE_MODEL(rl->model), &child, ROOM_COLUMN, &tmproom, -1);
840 				if (!tmproom)
841 					append = FALSE;
842 			}
843 		}
844 	}
845 
846 	if (append)
847 		gtk_tree_store_append(rl->model, &iter, (parentrr ? &parent : NULL));
848 	else
849 		iter = child;
850 
851 	if (room->type & PURPLE_ROOMLIST_ROOMTYPE_CATEGORY)
852 		gtk_tree_store_append(rl->model, &child, &iter);
853 
854 	path = gtk_tree_model_get_path(GTK_TREE_MODEL(rl->model), &iter);
855 
856 	if (room->type & PURPLE_ROOMLIST_ROOMTYPE_CATEGORY) {
857 		rr = gtk_tree_row_reference_new(GTK_TREE_MODEL(rl->model), path);
858 		g_hash_table_insert(rl->cats, room, rr);
859 	}
860 
861 	gtk_tree_path_free(path);
862 
863 	gtk_tree_store_set(rl->model, &iter, NAME_COLUMN, room->name, -1);
864 	gtk_tree_store_set(rl->model, &iter, ROOM_COLUMN, room, -1);
865 
866 	for (j = NUM_OF_COLUMNS, l = room->fields, k = list->fields; l && k; j++, l = l->next, k = k->next) {
867 		PurpleRoomlistField *f = k->data;
868 		if (f->hidden)
869 			continue;
870 		gtk_tree_store_set(rl->model, &iter, j, l->data, -1);
871 	}
872 }
873 
pidgin_roomlist_in_progress(PurpleRoomlist * list,gboolean in_progress)874 static void pidgin_roomlist_in_progress(PurpleRoomlist *list, gboolean in_progress)
875 {
876 	PidginRoomlist *rl = list->ui_data;
877 
878 	if (!rl || !rl->dialog)
879 		return;
880 
881 	if (in_progress) {
882 		if (rl->dialog->account_widget)
883 			gtk_widget_set_sensitive(rl->dialog->account_widget, FALSE);
884 		gtk_widget_set_sensitive(rl->dialog->stop_button, TRUE);
885 		gtk_widget_set_sensitive(rl->dialog->list_button, FALSE);
886 	} else {
887 		rl->dialog->pg_needs_pulse = FALSE;
888 		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(rl->dialog->progress), 0.0);
889 		if (rl->dialog->account_widget)
890 			gtk_widget_set_sensitive(rl->dialog->account_widget, TRUE);
891 		gtk_widget_set_sensitive(rl->dialog->stop_button, FALSE);
892 		gtk_widget_set_sensitive(rl->dialog->list_button, TRUE);
893 	}
894 }
895 
pidgin_roomlist_destroy(PurpleRoomlist * list)896 static void pidgin_roomlist_destroy(PurpleRoomlist *list)
897 {
898 	PidginRoomlist *rl = list->ui_data;
899 
900 	roomlists = g_list_remove(roomlists, list);
901 
902 	g_return_if_fail(rl != NULL);
903 
904 	g_hash_table_destroy(rl->cats);
905 	g_free(rl);
906 	list->ui_data = NULL;
907 }
908 
909 static PurpleRoomlistUiOps ops = {
910 	pidgin_roomlist_dialog_show_with_account,
911 	pidgin_roomlist_new,
912 	pidgin_roomlist_set_fields,
913 	pidgin_roomlist_add_room,
914 	pidgin_roomlist_in_progress,
915 	pidgin_roomlist_destroy,
916 	NULL,
917 	NULL,
918 	NULL,
919 	NULL
920 };
921 
922 
pidgin_roomlist_init(void)923 void pidgin_roomlist_init(void)
924 {
925 	purple_roomlist_set_ui_ops(&ops);
926 }
927