2 * @file gtkdisco.c GTK+ Service Discovery 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
32 #include "pidginaccountchooser.h"
33 #include "pidgintooltip.h"
35 #include "gtk3compat.h"
37 #include "xmppdisco.h"
39 GList
*dialogs
= NULL
;
50 pidgin_disco_list_destroy(PidginDiscoList
*list
)
52 g_hash_table_destroy(list
->services
);
53 if (list
->dialog
&& list
->dialog
->discolist
== list
)
54 list
->dialog
->discolist
= NULL
;
56 g_free((gchar
*)list
->server
);
60 PidginDiscoList
*pidgin_disco_list_ref(PidginDiscoList
*list
)
62 g_return_val_if_fail(list
!= NULL
, NULL
);
65 purple_debug_misc("xmppdisco", "reffing list, ref count now %d\n", list
->ref
);
70 void pidgin_disco_list_unref(PidginDiscoList
*list
)
72 g_return_if_fail(list
!= NULL
);
76 purple_debug_misc("xmppdisco", "unreffing list, ref count now %d\n", list
->ref
);
78 pidgin_disco_list_destroy(list
);
81 void pidgin_disco_list_set_in_progress(PidginDiscoList
*list
, gboolean in_progress
)
83 PidginDiscoDialog
*dialog
= list
->dialog
;
88 list
->in_progress
= in_progress
;
91 gtk_widget_set_sensitive(dialog
->account_chooser
, FALSE
);
92 gtk_widget_set_sensitive(dialog
->stop_button
, TRUE
);
93 gtk_widget_set_sensitive(dialog
->browse_button
, FALSE
);
95 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dialog
->progress
), 0.0);
97 gtk_widget_set_sensitive(dialog
->account_chooser
, TRUE
);
99 gtk_widget_set_sensitive(dialog
->stop_button
, FALSE
);
100 gtk_widget_set_sensitive(dialog
->browse_button
, TRUE
);
102 gtk_widget_set_sensitive(dialog->register_button, FALSE);
103 gtk_widget_set_sensitive(dialog->add_button, FALSE);
109 pidgin_disco_load_icon(XmppDiscoService
*service
, const char *size
)
111 GdkPixbuf
*pixbuf
= NULL
;
112 char *filename
= NULL
;
115 g_return_val_if_fail(service
!= NULL
, NULL
);
116 g_return_val_if_fail(size
!= NULL
, NULL
);
118 tmp_size
= g_strdup_printf("%sx%s", size
, size
);
120 if (service
->type
== XMPP_DISCO_SERVICE_TYPE_GATEWAY
&& service
->gateway_type
) {
121 char *tmp
= g_strconcat("im-", service
->gateway_type
,
124 filename
= g_build_filename(PURPLE_DATADIR
,
125 "pidgin", "icons", "hicolor", tmp_size
, "apps",
129 } else if (service
->type
== XMPP_DISCO_SERVICE_TYPE_USER
) {
130 filename
= g_build_filename(PURPLE_DATADIR
,
131 "pixmaps", "pidgin", "status", size
, "person.png", NULL
);
133 } else if (service
->type
== XMPP_DISCO_SERVICE_TYPE_CHAT
) {
134 filename
= g_build_filename(PURPLE_DATADIR
,
135 "pidgin", "icons", "hicolor", tmp_size
, "status",
142 pixbuf
= gdk_pixbuf_new_from_file(filename
, NULL
);
150 dialog_select_account_cb(GtkWidget
*chooser
, PidginDiscoDialog
*dialog
)
152 PurpleAccount
*account
= pidgin_account_chooser_get_selected(chooser
);
153 gboolean change
= (account
!= dialog
->account
);
154 dialog
->account
= account
;
155 gtk_widget_set_sensitive(dialog
->browse_button
, account
!= NULL
);
158 g_clear_pointer(&dialog
->discolist
, pidgin_disco_list_unref
);
162 static void register_button_cb(GtkWidget
*unused
, PidginDiscoDialog
*dialog
)
164 xmpp_disco_service_register(dialog
->selected
);
167 static void discolist_cancel_cb(PidginDiscoList
*pdl
, const char *server
)
169 pdl
->dialog
->prompt_handle
= NULL
;
171 pidgin_disco_list_set_in_progress(pdl
, FALSE
);
172 pidgin_disco_list_unref(pdl
);
175 static void discolist_ok_cb(PidginDiscoList
*pdl
, const char *server
)
177 pdl
->dialog
->prompt_handle
= NULL
;
178 gtk_widget_set_sensitive(pdl
->dialog
->browse_button
, TRUE
);
180 if (!server
|| !*server
) {
181 purple_notify_error(my_plugin
, _("Invalid Server"), _("Invalid Server"),
182 NULL
, purple_request_cpar_from_connection(pdl
->pc
));
184 pidgin_disco_list_set_in_progress(pdl
, FALSE
);
185 pidgin_disco_list_unref(pdl
);
189 pdl
->server
= g_strdup(server
);
190 pidgin_disco_list_set_in_progress(pdl
, TRUE
);
191 xmpp_disco_start(pdl
);
194 static void browse_button_cb(GtkWidget
*button
, PidginDiscoDialog
*dialog
)
196 PurpleConnection
*pc
;
197 PidginDiscoList
*pdl
;
198 const char *username
;
199 const char *at
, *slash
;
202 pc
= purple_account_get_connection(dialog
->account
);
206 gtk_widget_set_sensitive(dialog
->browse_button
, FALSE
);
207 gtk_widget_set_sensitive(dialog
->add_button
, FALSE
);
208 gtk_widget_set_sensitive(dialog
->register_button
, FALSE
);
210 g_clear_pointer(&dialog
->discolist
, pidgin_disco_list_unref
);
211 gtk_tree_store_clear(dialog
->model
);
213 pdl
= dialog
->discolist
= g_new0(PidginDiscoList
, 1);
214 pdl
->services
= g_hash_table_new_full(NULL
, NULL
, NULL
,
215 (GDestroyNotify
)gtk_tree_row_reference_free
);
217 /* We keep a copy... */
218 pidgin_disco_list_ref(pdl
);
220 pdl
->dialog
= dialog
;
222 gtk_widget_set_sensitive(dialog
->account_chooser
, FALSE
);
224 username
= purple_account_get_username(dialog
->account
);
225 at
= strchr(username
, '@');
226 slash
= strchr(username
, '/');
228 server
= g_strdup_printf("%s", at
+ 1);
229 } else if (at
&& slash
&& at
+ 1 < slash
) {
230 server
= g_strdup_printf("%.*s", (int)(slash
- (at
+ 1)), at
+ 1);
234 /* This shouldn't ever happen since the account is connected */
235 server
= g_strdup("jabber.org");
237 /* Translators: The string "Enter an XMPP Server" is asking the user to
238 type the name of an XMPP server which will then be queried */
239 dialog
->prompt_handle
= purple_request_input(my_plugin
, _("Server name request"), _("Enter an XMPP Server"),
240 _("Select an XMPP server to query"),
241 server
, FALSE
, FALSE
, NULL
,
242 _("Find Services"), PURPLE_CALLBACK(discolist_ok_cb
),
243 _("Cancel"), PURPLE_CALLBACK(discolist_cancel_cb
),
244 purple_request_cpar_from_connection(pc
), pdl
);
249 static void add_to_blist_cb(GtkWidget
*unused
, PidginDiscoDialog
*dialog
)
251 XmppDiscoService
*service
= dialog
->selected
;
252 PurpleAccount
*account
;
255 g_return_if_fail(service
!= NULL
);
257 account
= purple_connection_get_account(service
->list
->pc
);
260 if (service
->type
== XMPP_DISCO_SERVICE_TYPE_CHAT
)
261 purple_blist_request_add_chat(account
, NULL
, NULL
, jid
);
263 purple_blist_request_add_buddy(account
, jid
, NULL
, NULL
);
267 service_click_cb(GtkTreeView
*tree
, GdkEventButton
*event
, gpointer user_data
)
269 PidginDiscoDialog
*dialog
= user_data
;
270 XmppDiscoService
*service
;
277 if (!gdk_event_triggers_context_menu((GdkEvent
*)event
))
280 /* Figure out what was clicked */
281 if (!gtk_tree_view_get_path_at_pos(tree
, event
->x
, event
->y
, &path
,
284 gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog
->model
), &iter
, path
);
285 gtk_tree_path_free(path
);
287 gtk_tree_model_get_value(GTK_TREE_MODEL(dialog
->model
), &iter
,
288 SERVICE_COLUMN
, &val
);
289 service
= g_value_get_pointer(&val
);
294 menu
= gtk_menu_new();
296 if (service
->flags
& XMPP_DISCO_ADD
) {
297 pidgin_new_menu_item(menu
, _("Add to Buddy List"), NULL
,
298 G_CALLBACK(add_to_blist_cb
), dialog
);
301 if (service
->flags
& XMPP_DISCO_REGISTER
) {
302 GtkWidget
*item
= pidgin_new_menu_item(menu
, _("Register"),
304 g_signal_connect(G_OBJECT(item
), "activate",
305 G_CALLBACK(register_button_cb
), dialog
);
308 gtk_widget_show_all(menu
);
309 gtk_menu_popup_at_pointer(GTK_MENU(menu
), (GdkEvent
*)event
);
314 selection_changed_cb(GtkTreeSelection
*selection
, PidginDiscoDialog
*dialog
)
319 if (gtk_tree_selection_get_selected(selection
, NULL
, &iter
)) {
321 gtk_tree_model_get_value(GTK_TREE_MODEL(dialog
->model
), &iter
,
322 SERVICE_COLUMN
, &val
);
323 dialog
->selected
= g_value_get_pointer(&val
);
324 if (!dialog
->selected
) {
325 gtk_widget_set_sensitive(dialog
->add_button
, FALSE
);
326 gtk_widget_set_sensitive(dialog
->register_button
, FALSE
);
330 gtk_widget_set_sensitive(dialog
->add_button
, dialog
->selected
->flags
& XMPP_DISCO_ADD
);
331 gtk_widget_set_sensitive(dialog
->register_button
, dialog
->selected
->flags
& XMPP_DISCO_REGISTER
);
333 gtk_widget_set_sensitive(dialog
->add_button
, FALSE
);
334 gtk_widget_set_sensitive(dialog
->register_button
, FALSE
);
339 row_expanded_cb(GtkTreeView
*tree
, GtkTreeIter
*arg1
, GtkTreePath
*rg2
,
342 PidginDiscoDialog
*dialog
= user_data
;
343 XmppDiscoService
*service
;
347 gtk_tree_model_get_value(GTK_TREE_MODEL(dialog
->model
), arg1
,
348 SERVICE_COLUMN
, &val
);
349 service
= g_value_get_pointer(&val
);
350 xmpp_disco_service_expand(service
);
354 row_activated_cb(GtkTreeView
*tree_view
,
356 GtkTreeViewColumn
*column
,
359 PidginDiscoDialog
*dialog
= user_data
;
361 XmppDiscoService
*service
;
364 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog
->model
), &iter
,
370 gtk_tree_model_get_value(GTK_TREE_MODEL(dialog
->model
), &iter
,
371 SERVICE_COLUMN
, &val
);
372 service
= g_value_get_pointer(&val
);
374 if (service
->flags
& XMPP_DISCO_BROWSE
) {
375 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(dialog
->tree
),
377 gtk_tree_view_collapse_row(GTK_TREE_VIEW(dialog
->tree
),
380 gtk_tree_view_expand_row(GTK_TREE_VIEW(dialog
->tree
),
383 } else if (service
->flags
& XMPP_DISCO_REGISTER
) {
384 register_button_cb(NULL
, dialog
);
385 } else if (service
->flags
& XMPP_DISCO_ADD
) {
386 add_to_blist_cb(NULL
, dialog
);
391 destroy_win_cb(GtkWidget
*window
, G_GNUC_UNUSED gpointer data
)
393 PidginDiscoDialog
*dialog
= PIDGIN_DISCO_DIALOG(window
);
394 PidginDiscoList
*list
= dialog
->discolist
;
396 if (dialog
->prompt_handle
)
397 purple_request_close(PURPLE_REQUEST_INPUT
, dialog
->prompt_handle
);
402 if (list
->in_progress
)
403 list
->in_progress
= FALSE
;
405 pidgin_disco_list_unref(list
);
408 dialogs
= g_list_remove(dialogs
, dialog
);
411 static void stop_button_cb(GtkButton
*button
, PidginDiscoDialog
*dialog
)
413 pidgin_disco_list_set_in_progress(dialog
->discolist
, FALSE
);
416 static void close_button_cb(GtkButton
*button
, PidginDiscoDialog
*dialog
)
418 gtk_widget_destroy(GTK_WIDGET(dialog
));
421 static gboolean
account_filter_func(PurpleAccount
*account
)
423 return purple_strequal(purple_account_get_protocol_id(account
), XMPP_PROTOCOL_ID
);
427 disco_paint_tooltip(GtkWidget
*tipwindow
, cairo_t
*cr
, gpointer data
)
429 PangoLayout
*layout
= g_object_get_data(G_OBJECT(tipwindow
), "tooltip-plugin");
430 GtkStyleContext
*context
= gtk_widget_get_style_context(tipwindow
);
431 gtk_style_context_add_class(context
, GTK_STYLE_CLASS_TOOLTIP
);
432 gtk_render_layout(context
, cr
, 6, 6, layout
);
437 disco_create_tooltip(GtkWidget
*tipwindow
, GtkTreePath
*path
,
438 gpointer data
, int *w
, int *h
)
440 PidginDiscoDialog
*dialog
= data
;
444 XmppDiscoService
*service
;
446 const char *type
= NULL
;
447 char *markup
, *jid
, *name
, *desc
= NULL
;
449 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog
->model
), &iter
,
455 gtk_tree_model_get_value(GTK_TREE_MODEL(dialog
->model
), &iter
,
456 SERVICE_COLUMN
, &val
);
457 service
= g_value_get_pointer(&val
);
461 switch (service
->type
) {
462 case XMPP_DISCO_SERVICE_TYPE_UNSET
:
466 case XMPP_DISCO_SERVICE_TYPE_GATEWAY
:
470 case XMPP_DISCO_SERVICE_TYPE_DIRECTORY
:
471 type
= _("Directory");
474 case XMPP_DISCO_SERVICE_TYPE_CHAT
:
478 case XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION
:
479 type
= _("PubSub Collection");
482 case XMPP_DISCO_SERVICE_TYPE_PUBSUB_LEAF
:
483 type
= _("PubSub Leaf");
486 case XMPP_DISCO_SERVICE_TYPE_OTHER
:
491 markup
= g_strdup_printf("<span size='x-large' weight='bold'>%s</span>\n<b>%s:</b> %s%s%s",
492 name
= g_markup_escape_text(service
->name
, -1),
494 jid
= g_markup_escape_text(service
->jid
, -1),
495 service
->description
? _("\n<b>Description:</b> ") : "",
496 service
->description
? desc
= g_markup_escape_text(service
->description
, -1) : "");
498 layout
= gtk_widget_create_pango_layout(tipwindow
, NULL
);
499 pango_layout_set_markup(layout
, markup
, -1);
500 pango_layout_set_wrap(layout
, PANGO_WRAP_WORD
);
501 pango_layout_set_width(layout
, 500000);
502 pango_layout_get_size(layout
, &width
, &height
);
503 g_object_set_data_full(G_OBJECT(tipwindow
), "tooltip-plugin", layout
, g_object_unref
);
506 *w
= PANGO_PIXELS(width
) + 12;
508 *h
= PANGO_PIXELS(height
) + 12;
518 void pidgin_disco_signed_off_cb(PurpleConnection
*pc
)
522 for (node
= dialogs
; node
; node
= node
->next
) {
523 PidginDiscoDialog
*dialog
= node
->data
;
524 PidginDiscoList
*list
= dialog
->discolist
;
526 if (list
&& list
->pc
== pc
) {
527 if (list
->in_progress
)
528 pidgin_disco_list_set_in_progress(list
, FALSE
);
530 gtk_tree_store_clear(dialog
->model
);
532 pidgin_disco_list_unref(list
);
533 dialog
->discolist
= NULL
;
535 gtk_widget_set_sensitive(
536 dialog
->browse_button
,
537 pidgin_account_chooser_get_selected(
538 dialog
->account_chooser
) != NULL
);
540 gtk_widget_set_sensitive(dialog
->register_button
, FALSE
);
541 gtk_widget_set_sensitive(dialog
->add_button
, FALSE
);
546 void pidgin_disco_dialogs_destroy_all(void)
549 GtkWidget
*dialog
= dialogs
->data
;
551 gtk_widget_destroy(dialog
);
552 /* destroy_win_cb removes the dialog from the list */
556 /******************************************************************************
557 * GObject implementation
558 *****************************************************************************/
560 G_DEFINE_DYNAMIC_TYPE(PidginDiscoDialog
, pidgin_disco_dialog
, GTK_TYPE_DIALOG
)
563 pidgin_disco_dialog_class_init(PidginDiscoDialogClass
*klass
)
565 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS(klass
);
567 gtk_widget_class_set_template_from_resource(
568 widget_class
, "/im/pidgin/Pidgin/Plugin/XMPPDisco/disco.ui");
570 gtk_widget_class_bind_template_child(widget_class
, PidginDiscoDialog
,
572 gtk_widget_class_bind_template_child(widget_class
, PidginDiscoDialog
,
574 gtk_widget_class_bind_template_child(widget_class
, PidginDiscoDialog
,
576 gtk_widget_class_bind_template_child(widget_class
, PidginDiscoDialog
,
578 gtk_widget_class_bind_template_child(widget_class
, PidginDiscoDialog
,
580 gtk_widget_class_bind_template_child(widget_class
, PidginDiscoDialog
,
582 gtk_widget_class_bind_template_child(widget_class
, PidginDiscoDialog
,
584 gtk_widget_class_bind_template_child(widget_class
, PidginDiscoDialog
,
587 gtk_widget_class_bind_template_callback(widget_class
, destroy_win_cb
);
588 gtk_widget_class_bind_template_callback(widget_class
, stop_button_cb
);
589 gtk_widget_class_bind_template_callback(widget_class
, browse_button_cb
);
590 gtk_widget_class_bind_template_callback(widget_class
,
592 gtk_widget_class_bind_template_callback(widget_class
, add_to_blist_cb
);
593 gtk_widget_class_bind_template_callback(widget_class
, close_button_cb
);
594 gtk_widget_class_bind_template_callback(widget_class
,
595 dialog_select_account_cb
);
596 gtk_widget_class_bind_template_callback(widget_class
, row_activated_cb
);
597 gtk_widget_class_bind_template_callback(widget_class
, row_expanded_cb
);
598 gtk_widget_class_bind_template_callback(widget_class
, service_click_cb
);
599 gtk_widget_class_bind_template_callback(widget_class
,
600 selection_changed_cb
);
604 pidgin_disco_dialog_class_finalize(PidginDiscoDialogClass
*klass
)
609 pidgin_disco_dialog_init(PidginDiscoDialog
*dialog
)
611 dialogs
= g_list_prepend(dialogs
, dialog
);
613 gtk_widget_init_template(GTK_WIDGET(dialog
));
615 /* accounts dropdown list */
616 pidgin_account_chooser_set_filter_func(
617 PIDGIN_ACCOUNT_CHOOSER(dialog
->account_chooser
),
618 account_filter_func
);
620 pidgin_account_chooser_get_selected(dialog
->account_chooser
);
623 gtk_widget_set_sensitive(dialog
->browse_button
, dialog
->account
!= NULL
);
625 pidgin_tooltip_setup_for_treeview(GTK_WIDGET(dialog
->tree
), dialog
,
626 disco_create_tooltip
,
627 disco_paint_tooltip
);
630 /******************************************************************************
632 *****************************************************************************/
635 pidgin_disco_dialog_register(PurplePlugin
*plugin
)
637 pidgin_disco_dialog_register_type(G_TYPE_MODULE(plugin
));
641 pidgin_disco_dialog_new(void)
643 PidginDiscoDialog
*dialog
=
644 g_object_new(PIDGIN_TYPE_DISCO_DIALOG
, NULL
);
645 gtk_widget_show_all(GTK_WIDGET(dialog
));
649 void pidgin_disco_add_service(PidginDiscoList
*pdl
, XmppDiscoService
*service
, XmppDiscoService
*parent
)
651 PidginDiscoDialog
*dialog
;
652 GtkTreeIter iter
, parent_iter
, child
;
653 GdkPixbuf
*pixbuf
= NULL
;
654 gboolean append
= TRUE
;
656 dialog
= pdl
->dialog
;
657 g_return_if_fail(dialog
!= NULL
);
660 purple_debug_info("xmppdisco", "Adding service \"%s\"\n", service
->name
);
662 purple_debug_info("xmppdisco", "Service \"%s\" has no childrens\n", parent
->name
);
664 gtk_progress_bar_pulse(GTK_PROGRESS_BAR(dialog
->progress
));
667 GtkTreeRowReference
*rr
;
670 rr
= g_hash_table_lookup(pdl
->services
, parent
);
671 path
= gtk_tree_row_reference_get_path(rr
);
673 gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog
->model
),
675 gtk_tree_path_free(path
);
677 if (gtk_tree_model_iter_children(
678 GTK_TREE_MODEL(dialog
->model
), &child
,
680 PidginDiscoList
*tmp
;
682 GTK_TREE_MODEL(dialog
->model
), &child
,
683 SERVICE_COLUMN
, &tmp
, -1);
690 if (service
== NULL
) {
691 if (parent
!= NULL
&& !append
)
692 gtk_tree_store_remove(dialog
->model
, &child
);
697 gtk_tree_store_append(dialog
->model
, &iter
,
698 (parent
? &parent_iter
: NULL
));
703 if (service
->flags
& XMPP_DISCO_BROWSE
) {
704 GtkTreeRowReference
*rr
;
707 gtk_tree_store_append(dialog
->model
, &child
, &iter
);
709 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(dialog
->model
),
711 rr
= gtk_tree_row_reference_new(GTK_TREE_MODEL(dialog
->model
),
713 g_hash_table_insert(pdl
->services
, service
, rr
);
714 gtk_tree_path_free(path
);
717 pixbuf
= pidgin_disco_load_icon(service
, "16");
719 gtk_tree_store_set(dialog
->model
, &iter
, PIXBUF_COLUMN
, pixbuf
,
720 NAME_COLUMN
, service
->name
, DESCRIPTION_COLUMN
,
721 service
->description
, SERVICE_COLUMN
, service
, -1);
724 g_object_unref(pixbuf
);