2 * @file gtkroomlist.c GTK+ Room List UI
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.
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.
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.
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
30 #include "pidginstock.h"
31 #include "pidgintooltip.h"
35 #include "connection.h"
38 #include "gtkroomlist.h"
40 typedef struct _PidginRoomlistDialog
{
42 GtkWidget
*account_widget
;
46 GtkWidget
*stop_button
;
47 GtkWidget
*list_button
;
48 GtkWidget
*add_button
;
49 GtkWidget
*join_button
;
50 GtkWidget
*close_button
;
52 PurpleAccount
*account
;
53 PurpleRoomlist
*roomlist
;
55 gboolean pg_needs_pulse
;
57 } PidginRoomlistDialog
;
59 typedef struct _PidginRoomlist
{
60 PidginRoomlistDialog
*dialog
;
63 GHashTable
*cats
; /**< Meow. */
64 gint num_rooms
, total_rooms
;
66 GdkRectangle tip_rect
;
67 PangoLayout
*tip_layout
;
68 PangoLayout
*tip_name_layout
;
81 static GList
*roomlists
= NULL
;
83 static gint
delete_win_cb(GtkWidget
*w
, GdkEventAny
*e
, gpointer d
)
85 PidginRoomlistDialog
*dialog
= d
;
87 if (dialog
->roomlist
&& purple_roomlist_get_in_progress(dialog
->roomlist
))
88 purple_roomlist_cancel_get_list(dialog
->roomlist
);
90 if (dialog
->pg_update_to
> 0)
91 purple_timeout_remove(dialog
->pg_update_to
);
93 if (dialog
->roomlist
) {
94 PidginRoomlist
*rl
= dialog
->roomlist
->ui_data
;
96 if (dialog
->pg_update_to
> 0)
97 /* yes, that's right, unref it twice. */
98 purple_roomlist_unref(dialog
->roomlist
);
102 purple_roomlist_unref(dialog
->roomlist
);
105 dialog
->progress
= NULL
;
111 static void dialog_select_account_cb(GObject
*w
, PurpleAccount
*account
,
112 PidginRoomlistDialog
*dialog
)
114 gboolean change
= (account
!= dialog
->account
);
115 dialog
->account
= account
;
117 if (change
&& dialog
->roomlist
) {
118 PidginRoomlist
*rl
= dialog
->roomlist
->ui_data
;
120 gtk_widget_destroy(rl
->tree
);
123 purple_roomlist_unref(dialog
->roomlist
);
124 dialog
->roomlist
= NULL
;
128 static void list_button_cb(GtkButton
*button
, PidginRoomlistDialog
*dialog
)
130 PurpleConnection
*gc
;
133 gc
= purple_account_get_connection(dialog
->account
);
137 if (dialog
->roomlist
!= NULL
) {
138 rl
= dialog
->roomlist
->ui_data
;
139 gtk_widget_destroy(rl
->tree
);
140 purple_roomlist_unref(dialog
->roomlist
);
143 dialog
->roomlist
= purple_roomlist_get_list(gc
);
144 if (!dialog
->roomlist
)
146 purple_roomlist_ref(dialog
->roomlist
);
147 rl
= dialog
->roomlist
->ui_data
;
150 if (dialog
->account_widget
)
151 gtk_widget_set_sensitive(dialog
->account_widget
, FALSE
);
153 gtk_container_add(GTK_CONTAINER(dialog
->sw
), rl
->tree
);
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
);
161 gtk_widget_set_sensitive(dialog
->stop_button
, FALSE
);
162 gtk_widget_set_sensitive(dialog
->list_button
, TRUE
);
164 gtk_widget_set_sensitive(dialog
->add_button
, FALSE
);
165 gtk_widget_set_sensitive(dialog
->join_button
, FALSE
);
168 static void stop_button_cb(GtkButton
*button
, PidginRoomlistDialog
*dialog
)
170 purple_roomlist_cancel_get_list(dialog
->roomlist
);
172 if (dialog
->account_widget
)
173 gtk_widget_set_sensitive(dialog
->account_widget
, TRUE
);
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
);
181 static void close_button_cb(GtkButton
*button
, PidginRoomlistDialog
*dialog
)
183 GtkWidget
*window
= dialog
->window
;
185 delete_win_cb(NULL
, NULL
, dialog
);
186 gtk_widget_destroy(window
);
189 struct _menu_cb_info
{
190 PurpleRoomlist
*list
;
191 PurpleRoomlistRoom
*room
;
195 selection_changed_cb(GtkTreeSelection
*selection
, PidginRoomlist
*grl
) {
198 PurpleRoomlistRoom
*room
;
199 static struct _menu_cb_info
*info
;
200 PidginRoomlistDialog
*dialog
= grl
->dialog
;
202 if (gtk_tree_selection_get_selected(selection
, NULL
, &iter
)) {
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
);
212 info
= g_new0(struct _menu_cb_info
, 1);
213 info
->list
= dialog
->roomlist
;
216 g_object_set_data_full(G_OBJECT(dialog
->join_button
), "room-info",
218 g_object_set_data(G_OBJECT(dialog
->add_button
), "room-info", info
);
220 gtk_widget_set_sensitive(dialog
->add_button
, TRUE
);
221 gtk_widget_set_sensitive(dialog
->join_button
, TRUE
);
223 gtk_widget_set_sensitive(dialog
->add_button
, FALSE
);
224 gtk_widget_set_sensitive(dialog
->join_button
, FALSE
);
228 static void do_add_room_cb(GtkWidget
*w
, struct _menu_cb_info
*info
)
231 PurpleConnection
*gc
= purple_account_get_connection(info
->list
->account
);
232 PurplePluginProtocolInfo
*prpl_info
= NULL
;
235 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(gc
->prpl
);
237 if(prpl_info
!= NULL
&& prpl_info
->roomlist_room_serialize
)
238 name
= prpl_info
->roomlist_room_serialize(info
->room
);
240 name
= g_strdup(info
->room
->name
);
242 purple_blist_request_add_chat(info
->list
->account
, NULL
, NULL
, name
);
247 static void add_room_to_blist_cb(GtkButton
*button
, PidginRoomlistDialog
*dialog
)
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");
254 do_add_room_cb(grl
->tree
, info
);
257 static void do_join_cb(GtkWidget
*w
, struct _menu_cb_info
*info
)
259 purple_roomlist_room_join(info
->list
, info
->room
);
262 static void join_button_cb(GtkButton
*button
, PidginRoomlistDialog
*dialog
)
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");
269 do_join_cb(grl
->tree
, info
);
272 static void row_activated_cb(GtkTreeView
*tv
, GtkTreePath
*path
, GtkTreeViewColumn
*arg2
,
273 PurpleRoomlist
*list
)
275 PidginRoomlist
*grl
= list
->ui_data
;
277 PurpleRoomlistRoom
*room
;
279 struct _menu_cb_info info
;
281 gtk_tree_model_get_iter(GTK_TREE_MODEL(grl
->model
), &iter
, path
);
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
))
291 do_join_cb(GTK_WIDGET(tv
), &info
);
294 static gboolean
room_click_cb(GtkWidget
*tv
, GdkEventButton
*event
, PurpleRoomlist
*list
)
297 PidginRoomlist
*grl
= list
->ui_data
;
299 PurpleRoomlistRoom
*room
;
302 static struct _menu_cb_info info
; /* XXX? */
304 if (event
->button
!= 3 || event
->type
!= GDK_BUTTON_PRESS
)
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
))
310 gtk_tree_model_get_iter(GTK_TREE_MODEL(grl
->model
), &iter
, path
);
311 gtk_tree_path_free(path
);
313 gtk_tree_model_get_value (GTK_TREE_MODEL(grl
->model
), &iter
, ROOM_COLUMN
, &val
);
314 room
= g_value_get_pointer(&val
);
316 if (!room
|| !(room
->type
& PURPLE_ROOMLIST_ROOMTYPE_ROOM
))
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
);
328 gtk_widget_show_all(menu
);
329 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
, 3, event
->time
);
334 static void row_expanded_cb(GtkTreeView
*treeview
, GtkTreeIter
*arg1
, GtkTreePath
*arg2
, gpointer user_data
)
336 PurpleRoomlist
*list
= user_data
;
337 PurpleRoomlistRoom
*category
;
341 gtk_tree_model_get_value(gtk_tree_view_get_model(treeview
), arg1
, ROOM_COLUMN
, &val
);
342 category
= g_value_get_pointer(&val
);
344 if (!category
->expanded_once
) {
345 purple_roomlist_expand_category(list
, category
);
346 category
->expanded_once
= TRUE
;
350 #define SMALL_SPACE 6
351 #define TOOLTIP_BORDER 12
354 pidgin_roomlist_paint_tooltip(GtkWidget
*widget
, gpointer user_data
)
356 PurpleRoomlist
*list
= user_data
;
357 PidginRoomlist
*grl
= list
->ui_data
;
359 int current_height
, max_width
;
361 GtkTextDirection dir
= gtk_widget_get_direction(GTK_WIDGET(grl
->tree
));
363 style
= grl
->tipwindow
->style
;
365 max_text_width
= MAX(grl
->tip_width
, grl
->tip_name_width
);
366 max_width
= TOOLTIP_BORDER
+ SMALL_SPACE
+ max_text_width
+ TOOLTIP_BORDER
;
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
);
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
);
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
);
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
,
394 static gboolean
pidgin_roomlist_create_tip(PurpleRoomlist
*list
, GtkTreePath
*path
)
396 PidginRoomlist
*grl
= list
->ui_data
;
397 PurpleRoomlistRoom
*room
;
400 gchar
*name
, *tmp
, *node_name
;
401 GString
*tooltip_text
= NULL
;
404 gboolean first
= TRUE
;
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
))
411 gtk_tree_model_get_iter(GTK_TREE_MODEL(grl
->model
), &iter
, path
);
414 gtk_tree_model_get_value(GTK_TREE_MODEL(grl
->model
), &iter
, ROOM_COLUMN
, &val
);
415 room
= g_value_get_pointer(&val
);
417 if (!room
|| !(room
->type
& PURPLE_ROOMLIST_ROOMTYPE_ROOM
))
420 tooltip_text
= g_string_new("");
421 gtk_tree_model_get(GTK_TREE_MODEL(grl
->model
), &iter
, NAME_COLUMN
, &name
, -1);
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
;
428 label
= g_markup_escape_text(f
->label
, -1);
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");
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
));
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
);
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
);
449 tmp
= g_markup_escape_text(name
, -1);
451 node_name
= g_strdup_printf("<span size='x-large' weight='bold'>%s</span>", tmp
);
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);
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
);
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);
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
);
471 g_string_free(tooltip_text
, TRUE
);
477 pidgin_roomlist_create_tooltip(GtkWidget
*widget
, GtkTreePath
*path
,
478 gpointer data
, int *w
, int *h
)
480 PurpleRoomlist
*list
= data
;
481 PidginRoomlist
*grl
= list
->ui_data
;
482 grl
->tipwindow
= widget
;
483 if (!pidgin_roomlist_create_tip(data
, path
))
486 *w
= TOOLTIP_BORDER
+ SMALL_SPACE
+
487 MAX(grl
->tip_width
, grl
->tip_name_width
) + TOOLTIP_BORDER
;
489 *h
= TOOLTIP_BORDER
+ grl
->tip_height
+ grl
->tip_name_height
494 static gboolean
account_filter_func(PurpleAccount
*account
)
496 PurpleConnection
*conn
= purple_account_get_connection(account
);
497 PurplePluginProtocolInfo
*prpl_info
= NULL
;
499 if (conn
&& PURPLE_CONNECTION_IS_CONNECTED(conn
))
500 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(conn
->prpl
);
502 return (prpl_info
&& prpl_info
->roomlist_get_list
!= NULL
);
506 pidgin_roomlist_is_showable()
509 PurpleConnection
*gc
;
511 for (c
= purple_connections_get_all(); c
!= NULL
; c
= c
->next
) {
514 if (account_filter_func(purple_connection_get_account(gc
)))
521 static PidginRoomlistDialog
*
522 pidgin_roomlist_dialog_new_with_account(PurpleAccount
*account
)
524 PidginRoomlistDialog
*dialog
;
525 GtkWidget
*window
, *vbox
, *vbox2
, *bbox
;
527 dialog
= g_new0(PidginRoomlistDialog
, 1);
528 dialog
->account
= account
;
530 /* Create the window. */
531 dialog
->window
= window
= pidgin_create_dialog(_("Room List"), PIDGIN_HIG_BORDER
, "room list", TRUE
);
533 g_signal_connect(G_OBJECT(window
), "delete_event",
534 G_CALLBACK(delete_win_cb
), dialog
);
536 /* Create the parent vbox for everything. */
537 vbox
= pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window
), FALSE
, PIDGIN_HIG_BORDER
);
539 vbox2
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BORDER
);
540 gtk_container_add(GTK_CONTAINER(vbox
), vbox2
);
541 gtk_widget_show(vbox2
);
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
);
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);
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
);
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
);
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
);
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
);
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
);
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
);
597 dialog
->close_button
= pidgin_dialog_add_button(GTK_DIALOG(window
), GTK_STOCK_CLOSE
,
598 G_CALLBACK(close_button_cb
), dialog
);
600 /* show the dialog window and return the dialog */
601 gtk_widget_show(dialog
->window
);
606 void pidgin_roomlist_dialog_show_with_account(PurpleAccount
*account
)
608 PidginRoomlistDialog
*dialog
= pidgin_roomlist_dialog_new_with_account(account
);
613 list_button_cb(GTK_BUTTON(dialog
->list_button
), dialog
);
616 void pidgin_roomlist_dialog_show(void)
618 pidgin_roomlist_dialog_new_with_account(NULL
);
621 static void pidgin_roomlist_new(PurpleRoomlist
*list
)
623 PidginRoomlist
*rl
= g_new0(PidginRoomlist
, 1);
627 rl
->cats
= g_hash_table_new_full(NULL
, NULL
, NULL
, (GDestroyNotify
)gtk_tree_row_reference_free
);
629 roomlists
= g_list_append(roomlists
, list
);
632 static void int_cell_data_func(GtkTreeViewColumn
*col
, GtkCellRenderer
*renderer
,
633 GtkTreeModel
*model
, GtkTreeIter
*iter
, gpointer user_data
)
638 gtk_tree_model_get(model
, iter
, GPOINTER_TO_INT(user_data
), &myint
, -1);
641 g_snprintf(buf
, sizeof(buf
), "%d", myint
);
645 g_object_set(renderer
, "text", buf
, NULL
);
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. */
650 static gint
int_sort_func(GtkTreeModel
*model
, GtkTreeIter
*a
, GtkTreeIter
*b
, gpointer user_data
)
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);
668 _search_func(GtkTreeModel
*model
, gint column
, const gchar
*key
, GtkTreeIter
*iter
, gpointer search_data
)
671 gchar
*name
, *fold
, *fkey
;
673 gtk_tree_model_get(model
, iter
, column
, &name
, -1);
674 fold
= g_utf8_casefold(name
, -1);
675 fkey
= g_utf8_casefold(key
, -1);
677 result
= (g_strstr_len(fold
, strlen(fold
), fkey
) == NULL
);
686 static void pidgin_roomlist_set_fields(PurpleRoomlist
*list
, GList
*fields
)
688 PidginRoomlist
*grl
= list
->ui_data
;
689 gint columns
= NUM_OF_COLUMNS
;
693 GtkCellRenderer
*renderer
;
694 GtkTreeViewColumn
*column
;
695 GtkTreeSelection
*selection
;
699 g_return_if_fail(grl
!= NULL
);
701 columns
+= g_list_length(fields
);
702 types
= g_new(GType
, columns
);
704 types
[NAME_COLUMN
] = G_TYPE_STRING
;
705 types
[ROOM_COLUMN
] = G_TYPE_POINTER
;
707 for (j
= NUM_OF_COLUMNS
, l
= fields
; l
; l
= l
->next
, j
++) {
708 PurpleRoomlistField
*f
= l
->data
;
711 case PURPLE_ROOMLIST_FIELD_BOOL
:
712 types
[j
] = G_TYPE_BOOLEAN
;
714 case PURPLE_ROOMLIST_FIELD_INT
:
715 types
[j
] = G_TYPE_INT
;
717 case PURPLE_ROOMLIST_FIELD_STRING
:
718 types
[j
] = G_TYPE_STRING
;
723 model
= gtk_tree_store_newv(columns
, types
);
726 tree
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(model
));
727 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree
), TRUE
);
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
);
733 g_object_unref(model
);
737 gtk_widget_show(grl
->tree
);
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
);
749 for (j
= NUM_OF_COLUMNS
, l
= fields
; l
; l
= l
->next
, j
++) {
750 PurpleRoomlistField
*f
= l
->data
;
755 renderer
= gtk_cell_renderer_text_new();
756 column
= gtk_tree_view_column_new_with_attributes(f
->label
, renderer
,
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
);
769 gtk_tree_view_append_column(GTK_TREE_VIEW(tree
), column
);
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
);
779 pidgin_tooltip_setup_for_treeview(tree
, list
,
780 pidgin_roomlist_create_tooltip
,
781 pidgin_roomlist_paint_tooltip
);
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
);
789 static gboolean
pidgin_progress_bar_pulse(gpointer data
)
791 PurpleRoomlist
*list
= data
;
792 PidginRoomlist
*rl
= list
->ui_data
;
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
);
801 gtk_progress_bar_pulse(GTK_PROGRESS_BAR(rl
->dialog
->progress
));
802 rl
->dialog
->pg_needs_pulse
= FALSE
;
806 static void pidgin_roomlist_add_room(PurpleRoomlist
*list
, PurpleRoomlistRoom
*room
)
808 PidginRoomlist
*rl
= list
->ui_data
;
809 GtkTreeRowReference
*rr
, *parentrr
= NULL
;
811 GtkTreeIter iter
, parent
, child
;
814 gboolean append
= TRUE
;
817 if (room
->type
== PURPLE_ROOMLIST_ROOMTYPE_ROOM
)
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
));
826 rl
->dialog
->pg_needs_pulse
= TRUE
;
830 parentrr
= g_hash_table_lookup(rl
->cats
, room
->parent
);
831 path
= gtk_tree_row_reference_get_path(parentrr
);
833 PurpleRoomlistRoom
*tmproom
= NULL
;
835 gtk_tree_model_get_iter(GTK_TREE_MODEL(rl
->model
), &parent
, path
);
836 gtk_tree_path_free(path
);
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);
847 gtk_tree_store_append(rl
->model
, &iter
, (parentrr
? &parent
: NULL
));
851 if (room
->type
& PURPLE_ROOMLIST_ROOMTYPE_CATEGORY
)
852 gtk_tree_store_append(rl
->model
, &child
, &iter
);
854 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(rl
->model
), &iter
);
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
);
861 gtk_tree_path_free(path
);
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);
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
;
870 gtk_tree_store_set(rl
->model
, &iter
, j
, l
->data
, -1);
874 static void pidgin_roomlist_in_progress(PurpleRoomlist
*list
, gboolean in_progress
)
876 PidginRoomlist
*rl
= list
->ui_data
;
878 if (!rl
|| !rl
->dialog
)
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
);
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
);
896 static void pidgin_roomlist_destroy(PurpleRoomlist
*list
)
898 PidginRoomlist
*rl
= list
->ui_data
;
900 roomlists
= g_list_remove(roomlists
, list
);
902 g_return_if_fail(rl
!= NULL
);
904 g_hash_table_destroy(rl
->cats
);
906 list
->ui_data
= NULL
;
909 static PurpleRoomlistUiOps ops
= {
910 pidgin_roomlist_dialog_show_with_account
,
912 pidgin_roomlist_set_fields
,
913 pidgin_roomlist_add_room
,
914 pidgin_roomlist_in_progress
,
915 pidgin_roomlist_destroy
,
923 void pidgin_roomlist_init(void)
925 purple_roomlist_set_ui_ops(&ops
);