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
;
367 max_text_width
= MAX(grl
->tip_width
, grl
->tip_name_width
);
368 max_width
= TOOLTIP_BORDER
+ SMALL_SPACE
+ max_text_width
+ TOOLTIP_BORDER
;
372 if (dir
== GTK_TEXT_DIR_RTL
) {
373 gtk_paint_layout(style
, grl
->tipwindow
->window
, GTK_STATE_NORMAL
, FALSE
,
374 NULL
, grl
->tipwindow
, "tooltip",
375 max_width
- (TOOLTIP_BORDER
+ SMALL_SPACE
) - PANGO_PIXELS(600000),
376 current_height
, grl
->tip_name_layout
);
378 gtk_paint_layout (style
, grl
->tipwindow
->window
, GTK_STATE_NORMAL
, FALSE
,
379 NULL
, grl
->tipwindow
, "tooltip",
380 TOOLTIP_BORDER
+ SMALL_SPACE
, current_height
, grl
->tip_name_layout
);
382 if (dir
!= GTK_TEXT_DIR_RTL
) {
383 gtk_paint_layout (style
, grl
->tipwindow
->window
, GTK_STATE_NORMAL
, FALSE
,
384 NULL
, grl
->tipwindow
, "tooltip",
385 TOOLTIP_BORDER
+ SMALL_SPACE
, current_height
+ grl
->tip_name_height
, grl
->tip_layout
);
387 gtk_paint_layout(style
, grl
->tipwindow
->window
, GTK_STATE_NORMAL
, FALSE
,
388 NULL
, grl
->tipwindow
, "tooltip",
389 max_width
- (TOOLTIP_BORDER
+ SMALL_SPACE
) - PANGO_PIXELS(600000),
390 current_height
+ grl
->tip_name_height
,
396 static gboolean
pidgin_roomlist_create_tip(PurpleRoomlist
*list
, GtkTreePath
*path
)
398 PidginRoomlist
*grl
= list
->ui_data
;
399 PurpleRoomlistRoom
*room
;
402 gchar
*name
, *tmp
, *node_name
;
403 GString
*tooltip_text
= NULL
;
406 gboolean first
= TRUE
;
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
))
413 gtk_tree_model_get_iter(GTK_TREE_MODEL(grl
->model
), &iter
, path
);
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
|| !(room
->type
& PURPLE_ROOMLIST_ROOMTYPE_ROOM
))
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
, l
= room
->fields
, k
= list
->fields
; l
&& k
; j
++, l
= l
->next
, k
= k
->next
) {
426 PurpleRoomlistField
*f
= k
->data
;
430 label
= g_markup_escape_text(f
->label
, -1);
432 case PURPLE_ROOMLIST_FIELD_BOOL
:
433 g_string_append_printf(tooltip_text
, "%s<b>%s:</b> %s", first
? "" : "\n", label
, l
->data
? "True" : "False");
435 case PURPLE_ROOMLIST_FIELD_INT
:
436 g_string_append_printf(tooltip_text
, "%s<b>%s:</b> %d", first
? "" : "\n", label
, GPOINTER_TO_INT(l
->data
));
438 case PURPLE_ROOMLIST_FIELD_STRING
:
439 tmp
= g_markup_escape_text((char *)l
->data
, -1);
440 g_string_append_printf(tooltip_text
, "%s<b>%s:</b> %s", first
? "" : "\n", label
, tmp
);
448 grl
->tip_layout
= gtk_widget_create_pango_layout(grl
->tipwindow
, NULL
);
449 grl
->tip_name_layout
= gtk_widget_create_pango_layout(grl
->tipwindow
, NULL
);
451 tmp
= g_markup_escape_text(name
, -1);
453 node_name
= g_strdup_printf("<span size='x-large' weight='bold'>%s</span>", tmp
);
456 pango_layout_set_markup(grl
->tip_layout
, tooltip_text
->str
, -1);
457 pango_layout_set_wrap(grl
->tip_layout
, PANGO_WRAP_WORD
);
458 pango_layout_set_width(grl
->tip_layout
, 600000);
460 pango_layout_get_size (grl
->tip_layout
, &grl
->tip_width
, &grl
->tip_height
);
461 grl
->tip_width
= PANGO_PIXELS(grl
->tip_width
);
462 grl
->tip_height
= PANGO_PIXELS(grl
->tip_height
);
464 pango_layout_set_markup(grl
->tip_name_layout
, node_name
, -1);
465 pango_layout_set_wrap(grl
->tip_name_layout
, PANGO_WRAP_WORD
);
466 pango_layout_set_width(grl
->tip_name_layout
, 600000);
468 pango_layout_get_size (grl
->tip_name_layout
, &grl
->tip_name_width
, &grl
->tip_name_height
);
469 grl
->tip_name_width
= PANGO_PIXELS(grl
->tip_name_width
) + SMALL_SPACE
;
470 grl
->tip_name_height
= MAX(PANGO_PIXELS(grl
->tip_name_height
), SMALL_SPACE
);
473 g_string_free(tooltip_text
, TRUE
);
479 pidgin_roomlist_create_tooltip(GtkWidget
*widget
, GtkTreePath
*path
,
480 gpointer data
, int *w
, int *h
)
482 PurpleRoomlist
*list
= data
;
483 PidginRoomlist
*grl
= list
->ui_data
;
484 grl
->tipwindow
= widget
;
485 if (!pidgin_roomlist_create_tip(data
, path
))
488 *w
= TOOLTIP_BORDER
+ SMALL_SPACE
+
489 MAX(grl
->tip_width
, grl
->tip_name_width
) + TOOLTIP_BORDER
;
491 *h
= TOOLTIP_BORDER
+ grl
->tip_height
+ grl
->tip_name_height
496 static gboolean
account_filter_func(PurpleAccount
*account
)
498 PurpleConnection
*conn
= purple_account_get_connection(account
);
499 PurplePluginProtocolInfo
*prpl_info
= NULL
;
501 if (conn
&& PURPLE_CONNECTION_IS_CONNECTED(conn
))
502 prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(conn
->prpl
);
504 return (prpl_info
&& prpl_info
->roomlist_get_list
!= NULL
);
508 pidgin_roomlist_is_showable()
511 PurpleConnection
*gc
;
513 for (c
= purple_connections_get_all(); c
!= NULL
; c
= c
->next
) {
516 if (account_filter_func(purple_connection_get_account(gc
)))
523 static PidginRoomlistDialog
*
524 pidgin_roomlist_dialog_new_with_account(PurpleAccount
*account
)
526 PidginRoomlistDialog
*dialog
;
527 GtkWidget
*window
, *vbox
, *vbox2
, *bbox
;
529 dialog
= g_new0(PidginRoomlistDialog
, 1);
530 dialog
->account
= account
;
532 /* Create the window. */
533 dialog
->window
= window
= pidgin_create_dialog(_("Room List"), PIDGIN_HIG_BORDER
, "room list", TRUE
);
535 g_signal_connect(G_OBJECT(window
), "delete_event",
536 G_CALLBACK(delete_win_cb
), dialog
);
538 /* Create the parent vbox for everything. */
539 vbox
= pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window
), FALSE
, PIDGIN_HIG_BORDER
);
541 vbox2
= gtk_vbox_new(FALSE
, PIDGIN_HIG_BORDER
);
542 gtk_container_add(GTK_CONTAINER(vbox
), vbox2
);
543 gtk_widget_show(vbox2
);
545 /* accounts dropdown list */
546 dialog
->account_widget
= pidgin_account_option_menu_new(dialog
->account
, FALSE
,
547 G_CALLBACK(dialog_select_account_cb
), account_filter_func
, dialog
);
548 if (!dialog
->account
) /* this is normally null, and we normally don't care what the first selected item is */
549 dialog
->account
= pidgin_account_option_menu_get_selected(dialog
->account_widget
);
550 pidgin_add_widget_to_vbox(GTK_BOX(vbox2
), _("_Account:"), NULL
, dialog
->account_widget
, TRUE
, NULL
);
552 /* scrolled window */
553 dialog
->sw
= gtk_scrolled_window_new(NULL
, NULL
);
554 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(dialog
->sw
),
556 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dialog
->sw
),
557 GTK_POLICY_AUTOMATIC
,
558 GTK_POLICY_AUTOMATIC
);
559 gtk_box_pack_start(GTK_BOX(vbox2
), dialog
->sw
, TRUE
, TRUE
, 0);
560 gtk_widget_set_size_request(dialog
->sw
, -1, 250);
561 gtk_widget_show(dialog
->sw
);
564 dialog
->progress
= gtk_progress_bar_new();
565 gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(dialog
->progress
), 0.1);
566 gtk_box_pack_start(GTK_BOX(vbox2
), dialog
->progress
, FALSE
, FALSE
, 0);
567 gtk_widget_show(dialog
->progress
);
570 bbox
= pidgin_dialog_get_action_area(GTK_DIALOG(window
));
571 gtk_box_set_spacing(GTK_BOX(bbox
), PIDGIN_HIG_BOX_SPACE
);
572 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox
), GTK_BUTTONBOX_END
);
575 dialog
->stop_button
= pidgin_dialog_add_button(GTK_DIALOG(window
), GTK_STOCK_STOP
,
576 G_CALLBACK(stop_button_cb
), dialog
);
577 gtk_widget_set_sensitive(dialog
->stop_button
, FALSE
);
580 dialog
->list_button
= pidgin_pixbuf_button_from_stock(_("_Get List"), GTK_STOCK_REFRESH
,
581 PIDGIN_BUTTON_HORIZONTAL
);
582 gtk_box_pack_start(GTK_BOX(bbox
), dialog
->list_button
, FALSE
, FALSE
, 0);
583 g_signal_connect(G_OBJECT(dialog
->list_button
), "clicked",
584 G_CALLBACK(list_button_cb
), dialog
);
585 gtk_widget_show(dialog
->list_button
);
588 dialog
->add_button
= pidgin_pixbuf_button_from_stock(_("_Add Chat"), GTK_STOCK_ADD
,
589 PIDGIN_BUTTON_HORIZONTAL
);
590 gtk_box_pack_start(GTK_BOX(bbox
), dialog
->add_button
, FALSE
, FALSE
, 0);
591 g_signal_connect(G_OBJECT(dialog
->add_button
), "clicked",
592 G_CALLBACK(add_room_to_blist_cb
), dialog
);
593 gtk_widget_set_sensitive(dialog
->add_button
, FALSE
);
594 gtk_widget_show(dialog
->add_button
);
597 dialog
->join_button
= pidgin_pixbuf_button_from_stock(_("_Join"), PIDGIN_STOCK_CHAT
,
598 PIDGIN_BUTTON_HORIZONTAL
);
599 gtk_box_pack_start(GTK_BOX(bbox
), dialog
->join_button
, FALSE
, FALSE
, 0);
600 g_signal_connect(G_OBJECT(dialog
->join_button
), "clicked",
601 G_CALLBACK(join_button_cb
), dialog
);
602 gtk_widget_set_sensitive(dialog
->join_button
, FALSE
);
603 gtk_widget_show(dialog
->join_button
);
606 dialog
->close_button
= pidgin_dialog_add_button(GTK_DIALOG(window
), GTK_STOCK_CLOSE
,
607 G_CALLBACK(close_button_cb
), dialog
);
609 /* show the dialog window and return the dialog */
610 gtk_widget_show(dialog
->window
);
615 void pidgin_roomlist_dialog_show_with_account(PurpleAccount
*account
)
617 PidginRoomlistDialog
*dialog
= pidgin_roomlist_dialog_new_with_account(account
);
622 list_button_cb(GTK_BUTTON(dialog
->list_button
), dialog
);
625 void pidgin_roomlist_dialog_show(void)
627 pidgin_roomlist_dialog_new_with_account(NULL
);
630 static void pidgin_roomlist_new(PurpleRoomlist
*list
)
632 PidginRoomlist
*rl
= g_new0(PidginRoomlist
, 1);
636 rl
->cats
= g_hash_table_new_full(NULL
, NULL
, NULL
, (GDestroyNotify
)gtk_tree_row_reference_free
);
638 roomlists
= g_list_append(roomlists
, list
);
641 static void int_cell_data_func(GtkTreeViewColumn
*col
, GtkCellRenderer
*renderer
,
642 GtkTreeModel
*model
, GtkTreeIter
*iter
, gpointer user_data
)
647 gtk_tree_model_get(model
, iter
, GPOINTER_TO_INT(user_data
), &myint
, -1);
650 g_snprintf(buf
, sizeof(buf
), "%d", myint
);
654 g_object_set(renderer
, "text", buf
, NULL
);
657 /* this sorts backwards on purpose, so that clicking name sorts a-z, while clicking users sorts
658 infinity-0. you can still click again to reverse it on any of them. */
659 static gint
int_sort_func(GtkTreeModel
*model
, GtkTreeIter
*a
, GtkTreeIter
*b
, gpointer user_data
)
665 gtk_tree_model_get(model
, a
, GPOINTER_TO_INT(user_data
), &c
, -1);
666 gtk_tree_model_get(model
, b
, GPOINTER_TO_INT(user_data
), &d
, -1);
677 _search_func(GtkTreeModel
*model
, gint column
, const gchar
*key
, GtkTreeIter
*iter
, gpointer search_data
)
680 gchar
*name
, *fold
, *fkey
;
682 gtk_tree_model_get(model
, iter
, column
, &name
, -1);
683 fold
= g_utf8_casefold(name
, -1);
684 fkey
= g_utf8_casefold(key
, -1);
686 result
= (g_strstr_len(fold
, strlen(fold
), fkey
) == NULL
);
695 static void pidgin_roomlist_set_fields(PurpleRoomlist
*list
, GList
*fields
)
697 PidginRoomlist
*grl
= list
->ui_data
;
698 gint columns
= NUM_OF_COLUMNS
;
702 GtkCellRenderer
*renderer
;
703 GtkTreeViewColumn
*column
;
704 GtkTreeSelection
*selection
;
708 g_return_if_fail(grl
!= NULL
);
710 columns
+= g_list_length(fields
);
711 types
= g_new(GType
, columns
);
713 types
[NAME_COLUMN
] = G_TYPE_STRING
;
714 types
[ROOM_COLUMN
] = G_TYPE_POINTER
;
716 for (j
= NUM_OF_COLUMNS
, l
= fields
; l
; l
= l
->next
, j
++) {
717 PurpleRoomlistField
*f
= l
->data
;
720 case PURPLE_ROOMLIST_FIELD_BOOL
:
721 types
[j
] = G_TYPE_BOOLEAN
;
723 case PURPLE_ROOMLIST_FIELD_INT
:
724 types
[j
] = G_TYPE_INT
;
726 case PURPLE_ROOMLIST_FIELD_STRING
:
727 types
[j
] = G_TYPE_STRING
;
732 model
= gtk_tree_store_newv(columns
, types
);
735 tree
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(model
));
736 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree
), TRUE
);
738 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tree
));
739 g_signal_connect(G_OBJECT(selection
), "changed",
740 G_CALLBACK(selection_changed_cb
), grl
);
742 g_object_unref(model
);
746 gtk_widget_show(grl
->tree
);
748 renderer
= gtk_cell_renderer_text_new();
749 column
= gtk_tree_view_column_new_with_attributes(_("Name"), renderer
,
750 "text", NAME_COLUMN
, NULL
);
751 gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column
),
752 GTK_TREE_VIEW_COLUMN_GROW_ONLY
);
753 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column
), TRUE
);
754 gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column
), NAME_COLUMN
);
755 gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column
), TRUE
);
756 gtk_tree_view_append_column(GTK_TREE_VIEW(tree
), column
);
758 for (j
= NUM_OF_COLUMNS
, l
= fields
; l
; l
= l
->next
, j
++) {
759 PurpleRoomlistField
*f
= l
->data
;
764 renderer
= gtk_cell_renderer_text_new();
765 column
= gtk_tree_view_column_new_with_attributes(f
->label
, renderer
,
767 gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column
),
768 GTK_TREE_VIEW_COLUMN_GROW_ONLY
);
769 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column
), TRUE
);
770 gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column
), j
);
771 gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column
), TRUE
);
772 if (f
->type
== PURPLE_ROOMLIST_FIELD_INT
) {
773 gtk_tree_view_column_set_cell_data_func(column
, renderer
, int_cell_data_func
,
774 GINT_TO_POINTER(j
), NULL
);
775 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model
), j
, int_sort_func
,
776 GINT_TO_POINTER(j
), NULL
);
778 gtk_tree_view_append_column(GTK_TREE_VIEW(tree
), column
);
781 g_signal_connect(G_OBJECT(tree
), "button-press-event", G_CALLBACK(room_click_cb
), list
);
782 g_signal_connect(G_OBJECT(tree
), "row-expanded", G_CALLBACK(row_expanded_cb
), list
);
783 g_signal_connect(G_OBJECT(tree
), "row-activated", G_CALLBACK(row_activated_cb
), list
);
784 #if 0 /* uncomment this when the tooltips are slightly less annoying and more well behaved */
785 g_signal_connect(G_OBJECT(tree
), "motion-notify-event", G_CALLBACK(row_motion_cb
), list
);
786 g_signal_connect(G_OBJECT(tree
), "leave-notify-event", G_CALLBACK(row_leave_cb
), list
);
788 pidgin_tooltip_setup_for_treeview(tree
, list
,
789 pidgin_roomlist_create_tooltip
,
790 pidgin_roomlist_paint_tooltip
);
792 /* Enable CTRL+F searching */
793 gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree
), NAME_COLUMN
);
794 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(tree
), _search_func
, NULL
, NULL
);
798 static gboolean
pidgin_progress_bar_pulse(gpointer data
)
800 PurpleRoomlist
*list
= data
;
801 PidginRoomlist
*rl
= list
->ui_data
;
803 if (!rl
|| !rl
->dialog
|| !rl
->dialog
->pg_needs_pulse
) {
804 if (rl
&& rl
->dialog
)
805 rl
->dialog
->pg_update_to
= 0;
806 purple_roomlist_unref(list
);
810 gtk_progress_bar_pulse(GTK_PROGRESS_BAR(rl
->dialog
->progress
));
811 rl
->dialog
->pg_needs_pulse
= FALSE
;
815 static void pidgin_roomlist_add_room(PurpleRoomlist
*list
, PurpleRoomlistRoom
*room
)
817 PidginRoomlist
*rl
= list
->ui_data
;
818 GtkTreeRowReference
*rr
, *parentrr
= NULL
;
820 GtkTreeIter iter
, parent
, child
;
823 gboolean append
= TRUE
;
826 if (room
->type
== PURPLE_ROOMLIST_ROOMTYPE_ROOM
)
830 if (rl
->dialog
->pg_update_to
== 0) {
831 purple_roomlist_ref(list
);
832 rl
->dialog
->pg_update_to
= g_timeout_add(100, pidgin_progress_bar_pulse
, list
);
833 gtk_progress_bar_pulse(GTK_PROGRESS_BAR(rl
->dialog
->progress
));
835 rl
->dialog
->pg_needs_pulse
= TRUE
;
839 parentrr
= g_hash_table_lookup(rl
->cats
, room
->parent
);
840 path
= gtk_tree_row_reference_get_path(parentrr
);
842 PurpleRoomlistRoom
*tmproom
= NULL
;
844 gtk_tree_model_get_iter(GTK_TREE_MODEL(rl
->model
), &parent
, path
);
845 gtk_tree_path_free(path
);
847 if (gtk_tree_model_iter_children(GTK_TREE_MODEL(rl
->model
), &child
, &parent
)) {
848 gtk_tree_model_get(GTK_TREE_MODEL(rl
->model
), &child
, ROOM_COLUMN
, &tmproom
, -1);
856 gtk_tree_store_append(rl
->model
, &iter
, (parentrr
? &parent
: NULL
));
860 if (room
->type
& PURPLE_ROOMLIST_ROOMTYPE_CATEGORY
)
861 gtk_tree_store_append(rl
->model
, &child
, &iter
);
863 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(rl
->model
), &iter
);
865 if (room
->type
& PURPLE_ROOMLIST_ROOMTYPE_CATEGORY
) {
866 rr
= gtk_tree_row_reference_new(GTK_TREE_MODEL(rl
->model
), path
);
867 g_hash_table_insert(rl
->cats
, room
, rr
);
870 gtk_tree_path_free(path
);
872 gtk_tree_store_set(rl
->model
, &iter
, NAME_COLUMN
, room
->name
, -1);
873 gtk_tree_store_set(rl
->model
, &iter
, ROOM_COLUMN
, room
, -1);
875 for (j
= NUM_OF_COLUMNS
, l
= room
->fields
, k
= list
->fields
; l
&& k
; j
++, l
= l
->next
, k
= k
->next
) {
876 PurpleRoomlistField
*f
= k
->data
;
879 gtk_tree_store_set(rl
->model
, &iter
, j
, l
->data
, -1);
883 static void pidgin_roomlist_in_progress(PurpleRoomlist
*list
, gboolean in_progress
)
885 PidginRoomlist
*rl
= list
->ui_data
;
887 if (!rl
|| !rl
->dialog
)
891 if (rl
->dialog
->account_widget
)
892 gtk_widget_set_sensitive(rl
->dialog
->account_widget
, FALSE
);
893 gtk_widget_set_sensitive(rl
->dialog
->stop_button
, TRUE
);
894 gtk_widget_set_sensitive(rl
->dialog
->list_button
, FALSE
);
896 rl
->dialog
->pg_needs_pulse
= FALSE
;
897 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(rl
->dialog
->progress
), 0.0);
898 if (rl
->dialog
->account_widget
)
899 gtk_widget_set_sensitive(rl
->dialog
->account_widget
, TRUE
);
900 gtk_widget_set_sensitive(rl
->dialog
->stop_button
, FALSE
);
901 gtk_widget_set_sensitive(rl
->dialog
->list_button
, TRUE
);
905 static void pidgin_roomlist_destroy(PurpleRoomlist
*list
)
907 PidginRoomlist
*rl
= list
->ui_data
;
909 roomlists
= g_list_remove(roomlists
, list
);
911 g_return_if_fail(rl
!= NULL
);
913 g_hash_table_destroy(rl
->cats
);
915 list
->ui_data
= NULL
;
918 static PurpleRoomlistUiOps ops
= {
919 pidgin_roomlist_dialog_show_with_account
,
921 pidgin_roomlist_set_fields
,
922 pidgin_roomlist_add_room
,
923 pidgin_roomlist_in_progress
,
924 pidgin_roomlist_destroy
,
932 void pidgin_roomlist_init(void)
934 purple_roomlist_set_ui_ops(&ops
);