rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / pidgin / gtkroomlist.c
blob1e2369e897e244dd995e669a03c18acc5be6faaf
1 /* pidgin
3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
22 #include "internal.h"
23 #include "pidgin.h"
24 #include "gtkutils.h"
25 #include "pidginaccountchooser.h"
26 #include "pidginstock.h"
27 #include "pidgintooltip.h"
29 #include "debug.h"
30 #include "account.h"
31 #include "connection.h"
32 #include "notify.h"
34 #include "gtk3compat.h"
35 #include "gtkroomlist.h"
37 typedef struct {
38 GtkWidget *window;
39 GtkWidget *account_widget;
40 GtkWidget *progress;
41 GtkWidget *sw;
43 GtkWidget *stop_button;
44 GtkWidget *list_button;
45 GtkWidget *add_button;
46 GtkWidget *join_button;
47 GtkWidget *close_button;
49 PurpleAccount *account;
50 PurpleRoomlist *roomlist;
52 gboolean pg_needs_pulse;
53 guint pg_update_to;
54 } PidginRoomlistDialog;
56 typedef struct {
57 PidginRoomlistDialog *dialog;
58 GtkTreeStore *model;
59 GtkWidget *tree;
60 GHashTable *cats; /* Meow. */
61 gint num_rooms, total_rooms;
62 GtkWidget *tipwindow;
63 GdkRectangle tip_rect;
64 PangoLayout *tip_layout;
65 PangoLayout *tip_name_layout;
66 int tip_height;
67 int tip_width;
68 int tip_name_height;
69 int tip_name_width;
70 } PidginRoomlist;
72 enum {
73 NAME_COLUMN = 0,
74 ROOM_COLUMN,
75 NUM_OF_COLUMNS,
78 static GList *roomlists = NULL;
80 static gint delete_win_cb(GtkWidget *w, GdkEventAny *e, gpointer d)
82 PidginRoomlistDialog *dialog = d;
84 if (dialog->roomlist && purple_roomlist_get_in_progress(dialog->roomlist))
85 purple_roomlist_cancel_get_list(dialog->roomlist);
87 if (dialog->pg_update_to > 0)
88 g_source_remove(dialog->pg_update_to);
90 if (dialog->roomlist) {
91 PidginRoomlist *rl = purple_roomlist_get_ui_data(dialog->roomlist);
93 if (dialog->pg_update_to > 0)
94 /* yes, that's right, unref it twice. */
95 g_object_unref(dialog->roomlist);
97 if (rl)
98 rl->dialog = NULL;
99 g_object_unref(dialog->roomlist);
102 dialog->progress = NULL;
103 g_free(dialog);
105 return FALSE;
108 static void
109 dialog_select_account_cb(GtkWidget *chooser, PidginRoomlistDialog *dialog)
111 PurpleAccount *account = pidgin_account_chooser_get_selected(chooser);
112 gboolean change = (account != dialog->account);
113 dialog->account = account;
115 if (change && dialog->roomlist) {
116 PidginRoomlist *rl = purple_roomlist_get_ui_data(dialog->roomlist);
117 if (rl->tree) {
118 gtk_widget_destroy(rl->tree);
119 rl->tree = NULL;
121 g_object_unref(dialog->roomlist);
122 dialog->roomlist = NULL;
126 static void list_button_cb(GtkButton *button, PidginRoomlistDialog *dialog)
128 PurpleConnection *gc;
129 PidginRoomlist *rl;
131 gc = purple_account_get_connection(dialog->account);
132 if (!gc)
133 return;
135 if (dialog->roomlist != NULL) {
136 rl = purple_roomlist_get_ui_data(dialog->roomlist);
137 gtk_widget_destroy(rl->tree);
138 g_object_unref(dialog->roomlist);
141 dialog->roomlist = purple_roomlist_get_list(gc);
142 if (!dialog->roomlist)
143 return;
144 g_object_ref(dialog->roomlist);
145 rl = purple_roomlist_get_ui_data(dialog->roomlist);
146 rl->dialog = dialog;
148 if (dialog->account_widget)
149 gtk_widget_set_sensitive(dialog->account_widget, FALSE);
151 gtk_container_add(GTK_CONTAINER(dialog->sw), rl->tree);
153 /* some protocols (not bundled with libpurple) finish getting their
154 * room list immediately */
155 if(purple_roomlist_get_in_progress(dialog->roomlist)) {
156 gtk_widget_set_sensitive(dialog->stop_button, TRUE);
157 gtk_widget_set_sensitive(dialog->list_button, FALSE);
158 } else {
159 gtk_widget_set_sensitive(dialog->stop_button, FALSE);
160 gtk_widget_set_sensitive(dialog->list_button, TRUE);
162 gtk_widget_set_sensitive(dialog->add_button, FALSE);
163 gtk_widget_set_sensitive(dialog->join_button, FALSE);
166 static void stop_button_cb(GtkButton *button, PidginRoomlistDialog *dialog)
168 purple_roomlist_cancel_get_list(dialog->roomlist);
170 if (dialog->account_widget)
171 gtk_widget_set_sensitive(dialog->account_widget, TRUE);
173 gtk_widget_set_sensitive(dialog->stop_button, FALSE);
174 gtk_widget_set_sensitive(dialog->list_button, TRUE);
175 gtk_widget_set_sensitive(dialog->add_button, FALSE);
176 gtk_widget_set_sensitive(dialog->join_button, FALSE);
179 static void close_button_cb(GtkButton *button, PidginRoomlistDialog *dialog)
181 GtkWidget *window = dialog->window;
183 delete_win_cb(NULL, NULL, dialog);
184 gtk_widget_destroy(window);
187 struct _menu_cb_info {
188 PurpleRoomlist *list;
189 PurpleRoomlistRoom *room;
192 static void
193 selection_changed_cb(GtkTreeSelection *selection, PidginRoomlist *grl) {
194 GtkTreeIter iter;
195 GValue val;
196 PurpleRoomlistRoom *room;
197 static struct _menu_cb_info *info;
198 PidginRoomlistDialog *dialog = grl->dialog;
200 if (gtk_tree_selection_get_selected(selection, NULL, &iter)) {
201 val.g_type = 0;
202 gtk_tree_model_get_value(GTK_TREE_MODEL(grl->model), &iter, ROOM_COLUMN, &val);
203 room = g_value_get_pointer(&val);
204 if (!room || !(purple_roomlist_room_get_room_type(room) & PURPLE_ROOMLIST_ROOMTYPE_ROOM)) {
205 gtk_widget_set_sensitive(dialog->join_button, FALSE);
206 gtk_widget_set_sensitive(dialog->add_button, FALSE);
207 return;
210 info = g_new0(struct _menu_cb_info, 1);
211 info->list = dialog->roomlist;
212 info->room = room;
214 g_object_set_data_full(G_OBJECT(dialog->join_button), "room-info",
215 info, g_free);
216 g_object_set_data(G_OBJECT(dialog->add_button), "room-info", info);
218 gtk_widget_set_sensitive(dialog->add_button, TRUE);
219 gtk_widget_set_sensitive(dialog->join_button, TRUE);
220 } else {
221 gtk_widget_set_sensitive(dialog->add_button, FALSE);
222 gtk_widget_set_sensitive(dialog->join_button, FALSE);
226 static void do_add_room_cb(GtkWidget *w, struct _menu_cb_info *info)
228 char *name;
229 PurpleAccount *account = purple_roomlist_get_account(info->list);
230 PurpleConnection *gc = purple_account_get_connection(account);
231 PurpleProtocol *protocol = NULL;
233 if(gc != NULL)
234 protocol = purple_connection_get_protocol(gc);
236 if(protocol != NULL && PURPLE_PROTOCOL_IMPLEMENTS(protocol, ROOMLIST, room_serialize))
237 name = purple_protocol_roomlist_iface_room_serialize(protocol, info->room);
238 else
239 name = g_strdup(purple_roomlist_room_get_name(info->room));
241 purple_blist_request_add_chat(account, NULL, NULL, name);
243 g_free(name);
246 static void add_room_to_blist_cb(GtkButton *button, PidginRoomlistDialog *dialog)
248 PurpleRoomlist *rl = dialog->roomlist;
249 PidginRoomlist *grl = purple_roomlist_get_ui_data(rl);
250 struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "room-info");
252 if(info != NULL)
253 do_add_room_cb(grl->tree, info);
256 static void do_join_cb(GtkWidget *w, struct _menu_cb_info *info)
258 purple_roomlist_room_join(info->list, info->room);
261 static void join_button_cb(GtkButton *button, PidginRoomlistDialog *dialog)
263 PurpleRoomlist *rl = dialog->roomlist;
264 PidginRoomlist *grl = purple_roomlist_get_ui_data(rl);
265 struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "room-info");
267 if(info != NULL)
268 do_join_cb(grl->tree, info);
271 static void row_activated_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *arg2,
272 PurpleRoomlist *list)
274 PidginRoomlist *grl = purple_roomlist_get_ui_data(list);
275 GtkTreeIter iter;
276 PurpleRoomlistRoom *room;
277 GValue val;
278 struct _menu_cb_info info;
280 gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path);
281 val.g_type = 0;
282 gtk_tree_model_get_value(GTK_TREE_MODEL(grl->model), &iter, ROOM_COLUMN, &val);
283 room = g_value_get_pointer(&val);
284 if (!room || !(purple_roomlist_room_get_room_type(room) & PURPLE_ROOMLIST_ROOMTYPE_ROOM))
285 return;
287 info.list = list;
288 info.room = room;
290 do_join_cb(GTK_WIDGET(tv), &info);
293 static gboolean room_click_cb(GtkWidget *tv, GdkEventButton *event, PurpleRoomlist *list)
295 GtkTreePath *path;
296 PidginRoomlist *grl = purple_roomlist_get_ui_data(list);
297 GValue val;
298 PurpleRoomlistRoom *room;
299 GtkTreeIter iter;
300 GtkWidget *menu;
301 static struct _menu_cb_info info; /* XXX? */
303 if (!gdk_event_triggers_context_menu((GdkEvent *)event))
304 return FALSE;
306 /* Here we figure out which room was clicked */
307 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL))
308 return FALSE;
309 gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path);
310 gtk_tree_path_free(path);
311 val.g_type = 0;
312 gtk_tree_model_get_value (GTK_TREE_MODEL(grl->model), &iter, ROOM_COLUMN, &val);
313 room = g_value_get_pointer(&val);
315 if (!room || !(purple_roomlist_room_get_room_type(room) & PURPLE_ROOMLIST_ROOMTYPE_ROOM))
316 return FALSE;
318 info.list = list;
319 info.room = room;
321 menu = gtk_menu_new();
322 pidgin_new_menu_item(menu, _("_Join"), PIDGIN_STOCK_CHAT,
323 G_CALLBACK(do_join_cb), &info);
324 pidgin_new_menu_item(menu, _("_Add"), GTK_STOCK_ADD,
325 G_CALLBACK(do_add_room_cb), &info);
327 gtk_widget_show_all(menu);
328 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
330 return FALSE;
333 static void row_expanded_cb(GtkTreeView *treeview, GtkTreeIter *arg1, GtkTreePath *arg2, gpointer user_data)
335 PurpleRoomlist *list = user_data;
336 PurpleRoomlistRoom *category;
337 GValue val;
339 val.g_type = 0;
340 gtk_tree_model_get_value(gtk_tree_view_get_model(treeview), arg1, ROOM_COLUMN, &val);
341 category = g_value_get_pointer(&val);
343 if (!purple_roomlist_room_get_expanded_once(category)) {
344 purple_roomlist_expand_category(list, category);
345 purple_roomlist_room_set_expanded_once(category, TRUE);
349 #define SMALL_SPACE 6
350 #define TOOLTIP_BORDER 12
352 static gboolean
353 pidgin_roomlist_paint_tooltip(GtkWidget *widget, cairo_t *cr, gpointer user_data)
355 PurpleRoomlist *list = user_data;
356 PidginRoomlist *grl = purple_roomlist_get_ui_data(list);
357 int current_height, max_width;
358 int max_text_width;
359 GtkTextDirection dir = gtk_widget_get_direction(GTK_WIDGET(grl->tree));
360 GtkStyleContext *context;
362 context = gtk_widget_get_style_context(grl->tipwindow);
363 gtk_style_context_add_class(context, GTK_STYLE_CLASS_TOOLTIP);
365 max_text_width = MAX(grl->tip_width, grl->tip_name_width);
366 max_width = TOOLTIP_BORDER + SMALL_SPACE + max_text_width + TOOLTIP_BORDER;
368 current_height = 12;
370 if (dir == GTK_TEXT_DIR_RTL) {
371 gtk_render_layout(context, cr,
372 max_width - (TOOLTIP_BORDER + SMALL_SPACE) - PANGO_PIXELS(600000),
373 current_height,
374 grl->tip_name_layout);
375 } else {
376 gtk_render_layout(context, cr,
377 TOOLTIP_BORDER + SMALL_SPACE,
378 current_height,
379 grl->tip_name_layout);
381 if (dir != GTK_TEXT_DIR_RTL) {
382 gtk_render_layout(context, cr,
383 TOOLTIP_BORDER + SMALL_SPACE,
384 current_height + grl->tip_name_height,
385 grl->tip_layout);
386 } else {
387 gtk_render_layout(context, cr,
388 max_width - (TOOLTIP_BORDER + SMALL_SPACE) - PANGO_PIXELS(600000),
389 current_height + grl->tip_name_height,
390 grl->tip_layout);
393 return FALSE;
396 static gboolean pidgin_roomlist_create_tip(PurpleRoomlist *list, GtkTreePath *path)
398 PidginRoomlist *grl = purple_roomlist_get_ui_data(list);
399 PurpleRoomlistRoom *room;
400 GtkTreeIter iter;
401 GValue val;
402 gchar *name, *tmp, *node_name;
403 GString *tooltip_text = NULL;
404 GList *l, *k;
405 gint j;
406 gboolean first = TRUE;
408 #if 0
409 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),
410 &path, NULL, NULL, NULL))
411 return FALSE;
412 #endif
413 gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path);
415 val.g_type = 0;
416 gtk_tree_model_get_value(GTK_TREE_MODEL(grl->model), &iter, ROOM_COLUMN, &val);
417 room = g_value_get_pointer(&val);
419 if (!room || !(purple_roomlist_room_get_room_type(room) & PURPLE_ROOMLIST_ROOMTYPE_ROOM))
420 return FALSE;
422 tooltip_text = g_string_new("");
423 gtk_tree_model_get(GTK_TREE_MODEL(grl->model), &iter, NAME_COLUMN, &name, -1);
425 for (j = NUM_OF_COLUMNS,
426 l = purple_roomlist_room_get_fields(room),
427 k = purple_roomlist_get_fields(list);
428 l && k; j++, l = l->next, k = k->next)
430 PurpleRoomlistField *f = k->data;
431 gchar *label;
432 if (purple_roomlist_field_get_hidden(f))
433 continue;
434 label = g_markup_escape_text(purple_roomlist_field_get_label(f), -1);
435 switch (purple_roomlist_field_get_field_type(f)) {
436 case PURPLE_ROOMLIST_FIELD_BOOL:
437 g_string_append_printf(tooltip_text, "%s<b>%s:</b> %s", first ? "" : "\n", label, l->data ? "True" : "False");
438 break;
439 case PURPLE_ROOMLIST_FIELD_INT:
440 g_string_append_printf(tooltip_text, "%s<b>%s:</b> %d", first ? "" : "\n", label, GPOINTER_TO_INT(l->data));
441 break;
442 case PURPLE_ROOMLIST_FIELD_STRING:
443 tmp = g_markup_escape_text((char *)l->data, -1);
444 g_string_append_printf(tooltip_text, "%s<b>%s:</b> %s", first ? "" : "\n", label, tmp);
445 g_free(tmp);
446 break;
448 first = FALSE;
449 g_free(label);
452 grl->tip_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL);
453 grl->tip_name_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL);
455 tmp = g_markup_escape_text(name, -1);
456 g_free(name);
457 node_name = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>", tmp);
458 g_free(tmp);
460 pango_layout_set_markup(grl->tip_layout, tooltip_text->str, -1);
461 pango_layout_set_wrap(grl->tip_layout, PANGO_WRAP_WORD);
462 pango_layout_set_width(grl->tip_layout, 600000);
464 pango_layout_get_size (grl->tip_layout, &grl->tip_width, &grl->tip_height);
465 grl->tip_width = PANGO_PIXELS(grl->tip_width);
466 grl->tip_height = PANGO_PIXELS(grl->tip_height);
468 pango_layout_set_markup(grl->tip_name_layout, node_name, -1);
469 pango_layout_set_wrap(grl->tip_name_layout, PANGO_WRAP_WORD);
470 pango_layout_set_width(grl->tip_name_layout, 600000);
472 pango_layout_get_size (grl->tip_name_layout, &grl->tip_name_width, &grl->tip_name_height);
473 grl->tip_name_width = PANGO_PIXELS(grl->tip_name_width) + SMALL_SPACE;
474 grl->tip_name_height = MAX(PANGO_PIXELS(grl->tip_name_height), SMALL_SPACE);
476 g_free(node_name);
477 g_string_free(tooltip_text, TRUE);
479 return TRUE;
482 static gboolean
483 pidgin_roomlist_create_tooltip(GtkWidget *widget, GtkTreePath *path,
484 gpointer data, int *w, int *h)
486 PurpleRoomlist *list = data;
487 PidginRoomlist *grl = purple_roomlist_get_ui_data(list);
488 grl->tipwindow = widget;
489 if (!pidgin_roomlist_create_tip(data, path))
490 return FALSE;
491 if (w)
492 *w = TOOLTIP_BORDER + SMALL_SPACE +
493 MAX(grl->tip_width, grl->tip_name_width) + TOOLTIP_BORDER;
494 if (h)
495 *h = TOOLTIP_BORDER + grl->tip_height + grl->tip_name_height
496 + TOOLTIP_BORDER;
497 return TRUE;
500 static gboolean account_filter_func(PurpleAccount *account)
502 PurpleConnection *conn = purple_account_get_connection(account);
503 PurpleProtocol *protocol = NULL;
505 if (conn && PURPLE_CONNECTION_IS_CONNECTED(conn))
506 protocol = purple_connection_get_protocol(conn);
508 return (protocol && PURPLE_PROTOCOL_IMPLEMENTS(protocol, ROOMLIST, get_list));
511 gboolean
512 pidgin_roomlist_is_showable()
514 GList *c;
515 PurpleConnection *gc;
517 for (c = purple_connections_get_all(); c != NULL; c = c->next) {
518 gc = c->data;
520 if (account_filter_func(purple_connection_get_account(gc)))
521 return TRUE;
524 return FALSE;
527 static PidginRoomlistDialog *
528 pidgin_roomlist_dialog_new_with_account(PurpleAccount *account)
530 PidginRoomlistDialog *dialog;
531 GtkWidget *window, *vbox, *vbox2, *bbox;
533 dialog = g_new0(PidginRoomlistDialog, 1);
534 dialog->account = account;
536 /* Create the window. */
537 dialog->window = window = pidgin_create_dialog(_("Room List"), 0, "room list", TRUE);
539 g_signal_connect(G_OBJECT(window), "delete_event",
540 G_CALLBACK(delete_win_cb), dialog);
542 /* Create the parent vbox for everything. */
543 vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window), FALSE, PIDGIN_HIG_BORDER);
545 vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, PIDGIN_HIG_BORDER);
546 gtk_container_add(GTK_CONTAINER(vbox), vbox2);
547 gtk_widget_show(vbox2);
549 /* accounts dropdown list */
550 dialog->account_widget =
551 pidgin_account_chooser_new(dialog->account, FALSE);
552 pidgin_account_chooser_set_filter_func(
553 PIDGIN_ACCOUNT_CHOOSER(dialog->account_widget),
554 account_filter_func);
555 g_signal_connect(dialog->account_widget, "changed",
556 G_CALLBACK(dialog_select_account_cb), dialog);
557 if (!dialog->account) /* this is normally null, and we normally don't care what the first selected item is */
558 dialog->account = pidgin_account_chooser_get_selected(
559 dialog->account_widget);
560 pidgin_add_widget_to_vbox(GTK_BOX(vbox2), _("_Account:"), NULL, dialog->account_widget, TRUE, NULL);
562 /* scrolled window */
563 dialog->sw = pidgin_make_scrollable(NULL, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN, -1, 250);
564 gtk_box_pack_start(GTK_BOX(vbox2), dialog->sw, TRUE, TRUE, 0);
566 /* progress bar */
567 dialog->progress = gtk_progress_bar_new();
568 gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(dialog->progress), 0.1);
569 gtk_box_pack_start(GTK_BOX(vbox2), dialog->progress, FALSE, FALSE, 0);
570 gtk_widget_show(dialog->progress);
572 /* button box */
573 bbox = pidgin_dialog_get_action_area(GTK_DIALOG(window));
574 gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
575 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
577 /* stop button */
578 dialog->stop_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP,
579 G_CALLBACK(stop_button_cb), dialog);
580 gtk_widget_set_sensitive(dialog->stop_button, FALSE);
582 /* list button */
583 dialog->list_button = pidgin_pixbuf_button_from_stock(_("_Get List"), GTK_STOCK_REFRESH,
584 PIDGIN_BUTTON_HORIZONTAL);
585 gtk_box_pack_start(GTK_BOX(bbox), dialog->list_button, FALSE, FALSE, 0);
586 g_signal_connect(G_OBJECT(dialog->list_button), "clicked",
587 G_CALLBACK(list_button_cb), dialog);
588 gtk_widget_show(dialog->list_button);
590 /* add button */
591 dialog->add_button = pidgin_pixbuf_button_from_stock(_("_Add Chat"), GTK_STOCK_ADD,
592 PIDGIN_BUTTON_HORIZONTAL);
593 gtk_box_pack_start(GTK_BOX(bbox), dialog->add_button, FALSE, FALSE, 0);
594 g_signal_connect(G_OBJECT(dialog->add_button), "clicked",
595 G_CALLBACK(add_room_to_blist_cb), dialog);
596 gtk_widget_set_sensitive(dialog->add_button, FALSE);
597 gtk_widget_show(dialog->add_button);
599 /* join button */
600 dialog->join_button = pidgin_pixbuf_button_from_stock(_("_Join"), PIDGIN_STOCK_CHAT,
601 PIDGIN_BUTTON_HORIZONTAL);
602 gtk_box_pack_start(GTK_BOX(bbox), dialog->join_button, FALSE, FALSE, 0);
603 g_signal_connect(G_OBJECT(dialog->join_button), "clicked",
604 G_CALLBACK(join_button_cb), dialog);
605 gtk_widget_set_sensitive(dialog->join_button, FALSE);
606 gtk_widget_show(dialog->join_button);
608 /* close button */
609 dialog->close_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE,
610 G_CALLBACK(close_button_cb), dialog);
612 /* show the dialog window and return the dialog */
613 gtk_widget_show(dialog->window);
615 return dialog;
618 void pidgin_roomlist_dialog_show_with_account(PurpleAccount *account)
620 PidginRoomlistDialog *dialog = pidgin_roomlist_dialog_new_with_account(account);
622 if (!dialog)
623 return;
625 list_button_cb(GTK_BUTTON(dialog->list_button), dialog);
628 void pidgin_roomlist_dialog_show(void)
630 pidgin_roomlist_dialog_new_with_account(NULL);
633 static void pidgin_roomlist_new(PurpleRoomlist *list)
635 PidginRoomlist *rl = g_new0(PidginRoomlist, 1);
637 purple_roomlist_set_ui_data(list, rl);
639 rl->cats = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)gtk_tree_row_reference_free);
641 roomlists = g_list_append(roomlists, list);
644 static void int_cell_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
645 GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
647 gchar buf[16];
648 int myint;
650 gtk_tree_model_get(model, iter, GPOINTER_TO_INT(user_data), &myint, -1);
652 if (myint)
653 g_snprintf(buf, sizeof(buf), "%d", myint);
654 else
655 buf[0] = '\0';
657 g_object_set(renderer, "text", buf, NULL);
660 /* this sorts backwards on purpose, so that clicking name sorts a-z, while clicking users sorts
661 infinity-0. you can still click again to reverse it on any of them. */
662 static gint int_sort_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
664 int c, d;
666 c = d = 0;
668 gtk_tree_model_get(model, a, GPOINTER_TO_INT(user_data), &c, -1);
669 gtk_tree_model_get(model, b, GPOINTER_TO_INT(user_data), &d, -1);
671 if (c == d)
672 return 0;
673 else if (c > d)
674 return -1;
675 else
676 return 1;
679 static gboolean
680 _search_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data)
682 gboolean result;
683 gchar *name, *fold, *fkey;
685 gtk_tree_model_get(model, iter, column, &name, -1);
686 fold = g_utf8_casefold(name, -1);
687 fkey = g_utf8_casefold(key, -1);
689 result = (g_strstr_len(fold, strlen(fold), fkey) == NULL);
691 g_free(fold);
692 g_free(fkey);
693 g_free(name);
695 return result;
698 static void pidgin_roomlist_set_fields(PurpleRoomlist *list, GList *fields)
700 PidginRoomlist *grl = purple_roomlist_get_ui_data(list);
701 gint columns = NUM_OF_COLUMNS;
702 int j;
703 GtkTreeStore *model;
704 GtkWidget *tree;
705 GtkCellRenderer *renderer;
706 GtkTreeViewColumn *column;
707 GtkTreeSelection *selection;
708 GList *l;
709 GType *types;
711 g_return_if_fail(grl != NULL);
713 columns += g_list_length(fields);
714 types = g_new(GType, columns);
716 types[NAME_COLUMN] = G_TYPE_STRING;
717 types[ROOM_COLUMN] = G_TYPE_POINTER;
719 for (j = NUM_OF_COLUMNS, l = fields; l; l = l->next, j++) {
720 PurpleRoomlistField *f = l->data;
722 switch (purple_roomlist_field_get_field_type(f)) {
723 case PURPLE_ROOMLIST_FIELD_BOOL:
724 types[j] = G_TYPE_BOOLEAN;
725 break;
726 case PURPLE_ROOMLIST_FIELD_INT:
727 types[j] = G_TYPE_INT;
728 break;
729 case PURPLE_ROOMLIST_FIELD_STRING:
730 types[j] = G_TYPE_STRING;
731 break;
735 model = gtk_tree_store_newv(columns, types);
736 g_free(types);
738 tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
740 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
741 g_signal_connect(G_OBJECT(selection), "changed",
742 G_CALLBACK(selection_changed_cb), grl);
744 g_object_unref(model);
746 grl->model = model;
747 grl->tree = tree;
748 gtk_widget_show(grl->tree);
750 renderer = gtk_cell_renderer_text_new();
751 column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer,
752 "text", NAME_COLUMN, NULL);
753 gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
754 GTK_TREE_VIEW_COLUMN_GROW_ONLY);
755 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
756 gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column), NAME_COLUMN);
757 gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE);
758 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
760 for (j = NUM_OF_COLUMNS, l = fields; l; l = l->next, j++) {
761 PurpleRoomlistField *f = l->data;
763 if (purple_roomlist_field_get_hidden(f))
764 continue;
766 renderer = gtk_cell_renderer_text_new();
767 column = gtk_tree_view_column_new_with_attributes(
768 purple_roomlist_field_get_label(f), renderer,
769 "text", j, NULL);
770 gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
771 GTK_TREE_VIEW_COLUMN_GROW_ONLY);
772 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
773 gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column), j);
774 gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE);
775 if (purple_roomlist_field_get_field_type(f) == PURPLE_ROOMLIST_FIELD_INT) {
776 gtk_tree_view_column_set_cell_data_func(column, renderer, int_cell_data_func,
777 GINT_TO_POINTER(j), NULL);
778 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), j, int_sort_func,
779 GINT_TO_POINTER(j), NULL);
781 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
784 g_signal_connect(G_OBJECT(tree), "button-press-event", G_CALLBACK(room_click_cb), list);
785 g_signal_connect(G_OBJECT(tree), "row-expanded", G_CALLBACK(row_expanded_cb), list);
786 g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(row_activated_cb), list);
787 #if 0 /* uncomment this when the tooltips are slightly less annoying and more well behaved */
788 g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), list);
789 g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(row_leave_cb), list);
790 #endif
791 pidgin_tooltip_setup_for_treeview(tree, list,
792 pidgin_roomlist_create_tooltip,
793 pidgin_roomlist_paint_tooltip);
795 /* Enable CTRL+F searching */
796 gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree), NAME_COLUMN);
797 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(tree), _search_func, NULL, NULL);
801 static gboolean pidgin_progress_bar_pulse(gpointer data)
803 PurpleRoomlist *list = data;
804 PidginRoomlist *rl = purple_roomlist_get_ui_data(list);
806 if (!rl || !rl->dialog || !rl->dialog->pg_needs_pulse) {
807 if (rl && rl->dialog)
808 rl->dialog->pg_update_to = 0;
809 g_object_unref(list);
810 return FALSE;
813 gtk_progress_bar_pulse(GTK_PROGRESS_BAR(rl->dialog->progress));
814 rl->dialog->pg_needs_pulse = FALSE;
815 return TRUE;
818 static void pidgin_roomlist_add_room(PurpleRoomlist *list, PurpleRoomlistRoom *room)
820 PidginRoomlist *rl = purple_roomlist_get_ui_data(list);
821 GtkTreeRowReference *rr, *parentrr = NULL;
822 GtkTreePath *path;
823 GtkTreeIter iter, parent, child;
824 GList *l, *k;
825 int j;
826 gboolean append = TRUE;
828 rl->total_rooms++;
829 if (purple_roomlist_room_get_room_type(room) == PURPLE_ROOMLIST_ROOMTYPE_ROOM)
830 rl->num_rooms++;
832 if (rl->dialog) {
833 if (rl->dialog->pg_update_to == 0) {
834 g_object_ref(list);
835 rl->dialog->pg_update_to = g_timeout_add(100, pidgin_progress_bar_pulse, list);
836 gtk_progress_bar_pulse(GTK_PROGRESS_BAR(rl->dialog->progress));
837 } else
838 rl->dialog->pg_needs_pulse = TRUE;
841 if (purple_roomlist_room_get_parent(room)) {
842 parentrr = g_hash_table_lookup(rl->cats, purple_roomlist_room_get_parent(room));
843 path = gtk_tree_row_reference_get_path(parentrr);
844 if (path) {
845 PurpleRoomlistRoom *tmproom = NULL;
847 gtk_tree_model_get_iter(GTK_TREE_MODEL(rl->model), &parent, path);
848 gtk_tree_path_free(path);
850 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(rl->model), &child, &parent)) {
851 gtk_tree_model_get(GTK_TREE_MODEL(rl->model), &child, ROOM_COLUMN, &tmproom, -1);
852 if (!tmproom)
853 append = FALSE;
858 if (append)
859 gtk_tree_store_append(rl->model, &iter, (parentrr ? &parent : NULL));
860 else
861 iter = child;
863 if (purple_roomlist_room_get_room_type(room) & PURPLE_ROOMLIST_ROOMTYPE_CATEGORY)
864 gtk_tree_store_append(rl->model, &child, &iter);
866 path = gtk_tree_model_get_path(GTK_TREE_MODEL(rl->model), &iter);
868 if (purple_roomlist_room_get_room_type(room) & PURPLE_ROOMLIST_ROOMTYPE_CATEGORY) {
869 rr = gtk_tree_row_reference_new(GTK_TREE_MODEL(rl->model), path);
870 g_hash_table_insert(rl->cats, room, rr);
873 gtk_tree_path_free(path);
875 gtk_tree_store_set(rl->model, &iter, NAME_COLUMN, purple_roomlist_room_get_name(room), -1);
876 gtk_tree_store_set(rl->model, &iter, ROOM_COLUMN, room, -1);
878 for (j = NUM_OF_COLUMNS,
879 l = purple_roomlist_room_get_fields(room),
880 k = purple_roomlist_get_fields(list);
881 l && k; j++, l = l->next, k = k->next)
883 PurpleRoomlistField *f = k->data;
884 if (purple_roomlist_field_get_hidden(f))
885 continue;
886 gtk_tree_store_set(rl->model, &iter, j, l->data, -1);
890 static void pidgin_roomlist_in_progress(PurpleRoomlist *list, gboolean in_progress)
892 PidginRoomlist *rl = purple_roomlist_get_ui_data(list);
894 if (!rl || !rl->dialog)
895 return;
897 if (in_progress) {
898 if (rl->dialog->account_widget)
899 gtk_widget_set_sensitive(rl->dialog->account_widget, FALSE);
900 gtk_widget_set_sensitive(rl->dialog->stop_button, TRUE);
901 gtk_widget_set_sensitive(rl->dialog->list_button, FALSE);
902 } else {
903 rl->dialog->pg_needs_pulse = FALSE;
904 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(rl->dialog->progress), 0.0);
905 if (rl->dialog->account_widget)
906 gtk_widget_set_sensitive(rl->dialog->account_widget, TRUE);
907 gtk_widget_set_sensitive(rl->dialog->stop_button, FALSE);
908 gtk_widget_set_sensitive(rl->dialog->list_button, TRUE);
912 static void pidgin_roomlist_destroy(PurpleRoomlist *list)
914 PidginRoomlist *rl = purple_roomlist_get_ui_data(list);
916 roomlists = g_list_remove(roomlists, list);
918 g_return_if_fail(rl != NULL);
920 g_hash_table_destroy(rl->cats);
921 g_free(rl);
922 purple_roomlist_set_ui_data(list, NULL);
925 static PurpleRoomlistUiOps ops = {
926 pidgin_roomlist_dialog_show_with_account,
927 pidgin_roomlist_new,
928 pidgin_roomlist_set_fields,
929 pidgin_roomlist_add_room,
930 pidgin_roomlist_in_progress,
931 pidgin_roomlist_destroy,
932 NULL,
933 NULL,
934 NULL,
935 NULL
939 void pidgin_roomlist_init(void)
941 purple_roomlist_set_ui_ops(&ops);