Migrate xmpp-caps.xml to XDG cache dir
[pidgin-git.git] / pidgin / gtkplugin.c
blob16168e24fb20f46a56508e059f879660eda660f3
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
21 #include "internal.h"
22 #include "pidgin.h"
23 #include "gtkplugin.h"
24 #include "gtkpluginpref.h"
25 #include "gtkutils.h"
26 #include "debug.h"
27 #include "prefs.h"
28 #include "request.h"
29 #include "pidgintooltip.h"
31 #include <string.h>
33 #include "gtk3compat.h"
35 #define PIDGIN_PLUGIN_INFO_GET_PRIVATE(obj) \
36 (G_TYPE_INSTANCE_GET_PRIVATE((obj), PIDGIN_TYPE_PLUGIN_INFO, PidginPluginInfoPrivate))
38 #define PIDGIN_RESPONSE_CONFIGURE 98121
40 typedef struct
42 PidginPluginConfigFrameCb config_frame_cb;
43 } PidginPluginInfoPrivate;
45 enum
47 PROP_0,
48 PROP_GTK_CONFIG_FRAME_CB,
49 PROP_LAST
52 typedef struct
54 enum
56 PIDGIN_PLUGIN_UI_DATA_TYPE_FRAME,
57 PIDGIN_PLUGIN_UI_DATA_TYPE_REQUEST
58 } type;
60 union
62 struct
64 GtkWidget *dialog;
65 PurplePluginPrefFrame *pref_frame;
66 } frame;
68 gpointer request_handle;
69 } u;
70 } PidginPluginUiData;
72 static void plugin_toggled_stage_two(PurplePlugin *plug, GtkTreeModel *model,
73 GtkTreeIter *iter, GError *error, gboolean unload);
75 static GtkWidget *expander = NULL;
76 static GtkWidget *plugin_dialog = NULL;
78 static GtkLabel *plugin_name = NULL;
79 static GtkTextBuffer *plugin_desc = NULL;
80 static GtkLabel *plugin_error = NULL;
81 static GtkLabel *plugin_authors = NULL;
82 static GtkLabel *plugin_website = NULL;
83 static gchar *plugin_website_uri = NULL;
84 static GtkLabel *plugin_filename = NULL;
86 static GtkWidget *pref_button = NULL;
88 /* Set method for GObject properties */
89 static void
90 pidgin_plugin_info_set_property(GObject *obj, guint param_id, const GValue *value,
91 GParamSpec *pspec)
93 PidginPluginInfoPrivate *priv = PIDGIN_PLUGIN_INFO_GET_PRIVATE(obj);
95 switch (param_id) {
96 case PROP_GTK_CONFIG_FRAME_CB:
97 priv->config_frame_cb = g_value_get_pointer(value);
98 break;
99 default:
100 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
101 break;
105 /* Get method for GObject properties */
106 static void
107 pidgin_plugin_info_get_property(GObject *obj, guint param_id, GValue *value,
108 GParamSpec *pspec)
110 PidginPluginInfoPrivate *priv = PIDGIN_PLUGIN_INFO_GET_PRIVATE(obj);
112 switch (param_id) {
113 case PROP_GTK_CONFIG_FRAME_CB:
114 g_value_set_pointer(value, priv->config_frame_cb);
115 break;
116 default:
117 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
118 break;
122 /* Class initializer function */
123 static void pidgin_plugin_info_class_init(PidginPluginInfoClass *klass)
125 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
127 g_type_class_add_private(klass, sizeof(PidginPluginInfoPrivate));
129 /* Setup properties */
130 obj_class->get_property = pidgin_plugin_info_get_property;
131 obj_class->set_property = pidgin_plugin_info_set_property;
133 g_object_class_install_property(obj_class, PROP_GTK_CONFIG_FRAME_CB,
134 g_param_spec_pointer("gtk-config-frame-cb",
135 "GTK configuration frame callback",
136 "Callback that returns a GTK configuration frame",
137 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
138 G_PARAM_STATIC_STRINGS));
141 GType
142 pidgin_plugin_info_get_type(void)
144 static GType type = 0;
146 if (G_UNLIKELY(type == 0)) {
147 static const GTypeInfo info = {
148 .class_size = sizeof(PidginPluginInfoClass),
149 .class_init = (GClassInitFunc)pidgin_plugin_info_class_init,
150 .instance_size = sizeof(PidginPluginInfo),
153 type = g_type_register_static(PURPLE_TYPE_PLUGIN_INFO,
154 "PidginPluginInfo", &info, 0);
157 return type;
160 PidginPluginInfo *
161 pidgin_plugin_info_new(const char *first_property, ...)
163 GObject *info;
164 va_list var_args;
166 /* at least ID is required */
167 if (!first_property)
168 return NULL;
170 va_start(var_args, first_property);
171 info = g_object_new_valist(PIDGIN_TYPE_PLUGIN_INFO, first_property,
172 var_args);
173 va_end(var_args);
175 g_object_set(info, "ui-requirement", PIDGIN_UI, NULL);
177 return PIDGIN_PLUGIN_INFO(info);
180 static gboolean
181 pidgin_plugin_has_prefs(PurplePlugin *plugin)
183 PurplePluginInfo *info = purple_plugin_get_info(plugin);
184 PidginPluginInfoPrivate *priv = NULL;
185 gboolean ret;
187 g_return_val_if_fail(plugin != NULL, FALSE);
189 if (!purple_plugin_is_loaded(plugin))
190 return FALSE;
192 if (PIDGIN_IS_PLUGIN_INFO(info))
193 priv = PIDGIN_PLUGIN_INFO_GET_PRIVATE(info);
195 ret = ((priv && priv->config_frame_cb) ||
196 purple_plugin_info_get_pref_frame_cb(info) ||
197 purple_plugin_info_get_pref_request_cb(info));
199 return ret;
202 static GtkWidget *
203 pidgin_plugin_get_config_frame(PurplePlugin *plugin,
204 PurplePluginPrefFrame **purple_pref_frame)
206 GtkWidget *config = NULL;
207 PurplePluginInfo *info;
208 PurplePluginPrefFrameCb pref_frame_cb = NULL;
210 g_return_val_if_fail(PURPLE_IS_PLUGIN(plugin), NULL);
212 info = purple_plugin_get_info(plugin);
213 if(!PURPLE_IS_PLUGIN_INFO(info))
214 return NULL;
216 pref_frame_cb = purple_plugin_info_get_pref_frame_cb(info);
217 if(pref_frame_cb) {
218 PurplePluginPrefFrame *frame = pref_frame_cb(plugin);
220 if(frame) {
221 config = pidgin_plugin_pref_create_frame(frame);
223 *purple_pref_frame = frame;
227 return config;
230 static void
231 pref_dialog_close(PurplePlugin *plugin)
233 PurplePluginInfo *info;
234 PidginPluginUiData *ui_data;
236 g_return_if_fail(plugin != NULL);
238 info = purple_plugin_get_info(plugin);
240 ui_data = purple_plugin_info_get_ui_data(info);
241 if (ui_data == NULL)
242 return;
244 if (ui_data->type == PIDGIN_PLUGIN_UI_DATA_TYPE_REQUEST) {
245 purple_request_close(PURPLE_REQUEST_FIELDS,
246 ui_data->u.request_handle);
247 return;
250 g_return_if_fail(ui_data->type == PIDGIN_PLUGIN_UI_DATA_TYPE_FRAME);
252 gtk_widget_destroy(ui_data->u.frame.dialog);
254 if (ui_data->u.frame.pref_frame)
255 purple_plugin_pref_frame_destroy(ui_data->u.frame.pref_frame);
257 g_free(ui_data);
258 purple_plugin_info_set_ui_data(info, NULL);
262 static void
263 pref_dialog_response_cb(GtkWidget *dialog, int response, PurplePlugin *plugin)
265 if (response == GTK_RESPONSE_CLOSE ||
266 response == GTK_RESPONSE_DELETE_EVENT)
268 pref_dialog_close(plugin);
272 static void
273 pidgin_plugin_open_config(PurplePlugin *plugin, GtkWindow *parent)
275 PurplePluginInfo *info;
276 PidginPluginInfoPrivate *priv = NULL;
277 PidginPluginUiData *ui_data;
278 PurplePluginPrefFrameCb pref_frame_cb;
279 PurplePluginPrefRequestCb pref_request_cb;
280 PidginPluginConfigFrameCb get_pidgin_frame = NULL;
281 gint prefs_count;
283 g_return_if_fail(plugin != NULL);
285 info = purple_plugin_get_info(plugin);
287 if (!pidgin_plugin_has_prefs(plugin)) {
288 purple_debug_warning("gtkplugin", "Plugin has no prefs");
289 return;
292 if (purple_plugin_info_get_ui_data(info))
293 return;
295 if (PIDGIN_IS_PLUGIN_INFO(info))
296 priv = PIDGIN_PLUGIN_INFO_GET_PRIVATE(info);
298 pref_frame_cb = purple_plugin_info_get_pref_frame_cb(info);
299 pref_request_cb = purple_plugin_info_get_pref_request_cb(info);
301 if (priv)
302 get_pidgin_frame = priv->config_frame_cb;
304 prefs_count = 0;
305 if (pref_frame_cb)
306 prefs_count++;
307 if (pref_request_cb)
308 prefs_count++;
309 if (get_pidgin_frame)
310 prefs_count++;
312 if (prefs_count > 1) {
313 purple_debug_warning("gtkplugin", "Plugin %s contains more than"
314 " one prefs callback, some will be ignored.",
315 purple_plugin_info_get_name(info));
317 g_return_if_fail(prefs_count > 0);
319 ui_data = g_new0(PidginPluginUiData, 1);
320 purple_plugin_info_set_ui_data(info, ui_data);
322 /* Priority: pidgin frame > purple request > purple frame
323 * Purple frame could be replaced with purple request some day.
325 if (pref_request_cb && !get_pidgin_frame) {
326 ui_data->type = PIDGIN_PLUGIN_UI_DATA_TYPE_REQUEST;
327 ui_data->u.request_handle = pref_request_cb(plugin);
328 purple_request_add_close_notify(ui_data->u.request_handle,
329 purple_callback_set_zero, &info->ui_data);
330 purple_request_add_close_notify(ui_data->u.request_handle,
331 g_free, ui_data);
332 } else {
333 GtkWidget *box, *dialog;
335 ui_data->type = PIDGIN_PLUGIN_UI_DATA_TYPE_FRAME;
337 box = pidgin_plugin_get_config_frame(plugin,
338 &ui_data->u.frame.pref_frame);
339 if (box == NULL) {
340 purple_debug_error("gtkplugin",
341 "Failed to display prefs frame");
342 g_free(ui_data);
343 purple_plugin_info_set_ui_data(info, NULL);
344 return;
346 gtk_widget_set_vexpand(box, TRUE);
348 ui_data->u.frame.dialog = dialog = gtk_dialog_new_with_buttons(
349 PIDGIN_ALERT_TITLE, parent,
350 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE,
351 GTK_RESPONSE_CLOSE, NULL);
353 g_signal_connect(G_OBJECT(dialog), "response",
354 G_CALLBACK(pref_dialog_response_cb), plugin);
356 gtk_container_add(GTK_CONTAINER(
357 gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
358 pidgin_make_scrollable(box, GTK_POLICY_NEVER,
359 GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN, 400, 400));
361 gtk_window_set_role(GTK_WINDOW(dialog), "plugin_config");
362 gtk_window_set_title(GTK_WINDOW(dialog),
363 _(purple_plugin_info_get_name(info)));
364 gtk_widget_show_all(dialog);
368 void
369 pidgin_plugins_save(void)
371 purple_plugins_save_loaded(PIDGIN_PREFS_ROOT "/plugins/loaded");
374 static void
375 update_plugin_list(void *data)
377 GtkListStore *ls = GTK_LIST_STORE(data);
378 GtkTreeIter iter;
379 GList *plugins, *l;
380 PurplePlugin *plug;
381 PurplePluginInfo *info;
383 gtk_list_store_clear(ls);
384 purple_plugins_refresh();
386 plugins = purple_plugins_find_all();
388 for (l = plugins; l != NULL; l = l->next)
390 char *name;
391 char *version;
392 char *summary;
393 char *desc;
394 plug = PURPLE_PLUGIN(l->data);
395 info = purple_plugin_get_info(plug);
397 if (purple_plugin_is_internal(plug))
398 continue;
400 gtk_list_store_append (ls, &iter);
402 if (purple_plugin_info_get_name(info)) {
403 name = g_markup_escape_text(_(purple_plugin_info_get_name(info)), -1);
404 } else {
405 char *tmp = g_path_get_basename(purple_plugin_get_filename(plug));
406 name = g_markup_escape_text(tmp, -1);
407 g_free(tmp);
409 version = g_markup_escape_text(purple_plugin_info_get_version(info), -1);
410 summary = g_markup_escape_text(purple_plugin_info_get_summary(info), -1);
412 desc = g_strdup_printf("<b>%s</b> %s\n%s", name,
413 version,
414 summary);
415 g_free(name);
416 g_free(version);
417 g_free(summary);
419 gtk_list_store_set(ls, &iter,
420 0, purple_plugin_is_loaded(plug),
421 1, desc,
422 2, plug,
423 3, purple_plugin_info_get_error(info),
424 -1);
425 g_free(desc);
428 g_list_free(plugins);
431 static gboolean
432 check_if_loaded(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
434 PurplePlugin *plugin;
435 gtk_tree_model_get(model, iter, 2, &plugin, -1);
436 gtk_list_store_set(GTK_LIST_STORE(model), iter,
437 0, purple_plugin_is_loaded(plugin),
438 -1);
439 return FALSE;
442 static void
443 update_loaded_plugins(GtkTreeModel *model)
445 gtk_tree_model_foreach(model, check_if_loaded, NULL);
448 static void plugin_loading_common(PurplePlugin *plugin, GtkTreeView *view, gboolean loaded)
450 GtkTreeIter iter;
451 GtkTreeModel *model = gtk_tree_view_get_model(view);
453 if (gtk_tree_model_get_iter_first(model, &iter)) {
454 do {
455 PurplePlugin *plug;
456 GtkTreeSelection *sel;
458 gtk_tree_model_get(model, &iter, 2, &plug, -1);
460 if (plug != plugin)
461 continue;
463 gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, loaded, -1);
465 /* If the loaded/unloaded plugin is the selected row,
466 * update the pref_button. */
467 sel = gtk_tree_view_get_selection(view);
468 if (gtk_tree_selection_get_selected(sel, &model, &iter))
470 gtk_tree_model_get(model, &iter, 2, &plug, -1);
471 if (plug == plugin)
473 gtk_widget_set_sensitive(pref_button,
474 pidgin_plugin_has_prefs(plug));
478 break;
479 } while (gtk_tree_model_iter_next(model, &iter));
483 static void plugin_load_cb(PurplePlugin *plugin, gpointer data)
485 GtkTreeView *view = (GtkTreeView *)data;
486 plugin_loading_common(plugin, view, TRUE);
489 static void plugin_unload_cb(PurplePlugin *plugin, gpointer data)
491 GtkTreeView *view = (GtkTreeView *)data;
492 plugin_loading_common(plugin, view, FALSE);
495 static void plugin_unload_confirm_cb(gpointer *data)
497 PurplePlugin *plugin = (PurplePlugin *)data[0];
498 GtkTreeModel *model = (GtkTreeModel *)data[1];
499 GtkTreeIter *iter = (GtkTreeIter *)data[2];
501 plugin_toggled_stage_two(plugin, model, iter, NULL, TRUE);
503 g_free(data);
506 static void plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer data)
508 GtkTreeModel *model = (GtkTreeModel *)data;
509 GtkTreeIter *iter = g_new(GtkTreeIter, 1);
510 GtkTreePath *path = gtk_tree_path_new_from_string(pth);
511 PurplePlugin *plug;
512 GError *error = NULL;
514 gtk_tree_model_get_iter(model, iter, path);
515 gtk_tree_path_free(path);
516 gtk_tree_model_get(model, iter, 2, &plug, -1);
518 if (!purple_plugin_is_loaded(plug))
520 pidgin_set_cursor(plugin_dialog, GDK_WATCH);
522 purple_plugin_load(plug, &error);
523 plugin_toggled_stage_two(plug, model, iter, error, FALSE);
525 pidgin_clear_cursor(plugin_dialog);
527 else
529 pref_dialog_close(plug);
531 if (purple_plugin_get_dependent_plugins(plug) != NULL)
533 GString *tmp = g_string_new(_("The following plugins will be unloaded."));
534 GSList *l;
535 gpointer *cb_data;
537 for (l = purple_plugin_get_dependent_plugins(plug); l != NULL ; l = l->next)
539 const char *dep_name = (const char *)l->data;
540 PurplePlugin *dep_plugin = purple_plugins_find_plugin(dep_name);
541 PurplePluginInfo *dep_info;
543 g_return_if_fail(dep_plugin != NULL);
545 dep_info = purple_plugin_get_info(dep_plugin);
546 g_string_append_printf(tmp, "\n\t%s\n", purple_plugin_info_get_name(dep_info));
549 cb_data = g_new(gpointer, 3);
550 cb_data[0] = plug;
551 cb_data[1] = model;
552 cb_data[2] = iter;
554 purple_request_action(plugin_dialog, NULL,
555 _("Multiple plugins will be unloaded."),
556 tmp->str, 0, NULL, cb_data, 2,
557 _("Unload Plugins"),
558 G_CALLBACK(plugin_unload_confirm_cb),
559 _("Cancel"), g_free);
560 g_string_free(tmp, TRUE);
562 else
563 plugin_toggled_stage_two(plug, model, iter, NULL, TRUE);
567 static void plugin_toggled_stage_two(PurplePlugin *plug, GtkTreeModel *model, GtkTreeIter *iter, GError *error, gboolean unload)
569 PurplePluginInfo *info = purple_plugin_get_info(plug);
571 if (unload)
573 pidgin_set_cursor(plugin_dialog, GDK_WATCH);
575 if (!purple_plugin_unload(plug, &error))
577 const char *primary = _("Could not unload plugin");
578 const char *reload = _("The plugin could not be unloaded now, but will be disabled at the next startup.");
580 char *tmp = g_strdup_printf("%s\n\n%s", reload, error->message);
581 purple_notify_warning(NULL, NULL, primary, tmp, NULL);
582 g_free(tmp);
584 purple_plugin_disable(plug);
587 pidgin_clear_cursor(plugin_dialog);
589 else if (!unload && error)
591 purple_notify_warning(NULL, NULL, _("Could not load plugin"), error->message, NULL);
594 gtk_widget_set_sensitive(pref_button, pidgin_plugin_has_prefs(plug));
596 if (error != NULL)
598 gchar *name = g_markup_escape_text(purple_plugin_info_get_name(info), -1);
600 gchar *disp_error = g_markup_escape_text(error->message, -1);
601 gchar *text;
603 text = g_strdup_printf(
604 "<b>%s</b> %s\n<span weight=\"bold\" color=\"red\"%s</span>",
605 purple_plugin_info_get_name(info),
606 purple_plugin_info_get_version(info), disp_error);
607 gtk_list_store_set(GTK_LIST_STORE (model), iter,
608 1, text,
609 -1);
610 g_free(text);
612 text = g_strdup_printf(
613 "<span weight=\"bold\" color=\"red\">%s</span>",
614 disp_error);
615 gtk_label_set_markup(plugin_error, text);
616 g_free(text);
618 g_free(disp_error);
619 g_free(name);
621 g_error_free(error);
624 if ((unload && purple_plugin_get_dependent_plugins(plug)) ||
625 (!unload && purple_plugin_info_get_dependencies(info)))
626 update_loaded_plugins(model);
627 else
628 gtk_list_store_set(GTK_LIST_STORE (model), iter,
629 0, purple_plugin_is_loaded(plug),
630 -1);
631 g_free(iter);
633 pidgin_plugins_save();
636 static gboolean ensure_plugin_visible(void *data)
638 GtkTreeSelection *sel = GTK_TREE_SELECTION(data);
639 GtkTreeView *tv = gtk_tree_selection_get_tree_view(sel);
640 GtkTreeModel *model = gtk_tree_view_get_model(tv);
641 GtkTreePath *path;
642 GtkTreeIter iter;
643 if (!gtk_tree_selection_get_selected (sel, &model, &iter))
644 return FALSE;
645 path = gtk_tree_model_get_path(model, &iter);
646 gtk_tree_view_scroll_to_cell(gtk_tree_selection_get_tree_view(sel), path, NULL, FALSE, 0, 0);
647 gtk_tree_path_free(path);
648 return FALSE;
651 static void prefs_plugin_sel (GtkTreeSelection *sel, GtkTreeModel *model)
653 gchar *buf, *tmp, *name, *version;
654 gchar *authors = NULL;
655 const gchar * const *authorlist;
656 GtkTreeIter iter;
657 GValue val;
658 PurplePlugin *plug;
659 PurplePluginInfo *info;
661 if (!gtk_tree_selection_get_selected (sel, &model, &iter))
663 gtk_widget_set_sensitive(pref_button, FALSE);
665 /* Collapse and disable the expander widget */
666 gtk_expander_set_expanded(GTK_EXPANDER(expander), FALSE);
667 gtk_widget_set_sensitive(expander, FALSE);
669 return;
672 gtk_widget_set_sensitive(expander, TRUE);
674 val.g_type = 0;
675 gtk_tree_model_get_value (model, &iter, 2, &val);
676 plug = g_value_get_pointer(&val);
677 info = purple_plugin_get_info(plug);
679 name = g_markup_escape_text(purple_plugin_info_get_name(info), -1);
680 version = g_markup_escape_text(purple_plugin_info_get_version(info), -1);
681 buf = g_strdup_printf(
682 "<span size=\"larger\" weight=\"bold\">%s</span> "
683 "<span size=\"smaller\">%s</span>",
684 name, version);
685 gtk_label_set_markup(plugin_name, buf);
686 g_free(name);
687 g_free(version);
688 g_free(buf);
690 gtk_text_buffer_set_text(plugin_desc, purple_plugin_info_get_description(info), -1);
692 authorlist = purple_plugin_info_get_authors(info);
693 if (authorlist)
694 authors = g_strjoinv(",\n", (gchar **)authorlist);
695 gtk_label_set_text(plugin_authors, authors);
696 g_free(authors);
698 gtk_label_set_text(plugin_filename, purple_plugin_get_filename(plug));
700 g_free(plugin_website_uri);
701 plugin_website_uri = g_strdup(purple_plugin_info_get_website(info));
703 if (plugin_website_uri)
705 tmp = g_markup_escape_text(plugin_website_uri, -1);
706 buf = g_strdup_printf("<span underline=\"single\" "
707 "foreground=\"blue\">%s</span>", tmp);
708 gtk_label_set_markup(plugin_website, buf);
709 g_free(tmp);
710 g_free(buf);
712 else
714 gtk_label_set_text(plugin_website, NULL);
717 if (purple_plugin_info_get_error(info) == NULL)
719 gtk_label_set_text(plugin_error, NULL);
721 else
723 tmp = g_markup_escape_text(purple_plugin_info_get_error(info), -1);
724 buf = g_strdup_printf(
725 _("<span foreground=\"red\" weight=\"bold\">"
726 "Error: %s\n"
727 "Check the plugin website for an update."
728 "</span>"),
729 tmp);
730 gtk_label_set_markup(plugin_error, buf);
731 g_free(buf);
732 g_free(tmp);
735 gtk_widget_set_sensitive(pref_button, pidgin_plugin_has_prefs(plug));
737 /* Make sure the selected plugin is still visible */
738 g_idle_add(ensure_plugin_visible, sel);
740 g_value_unset(&val);
743 static void plugin_dialog_response_cb(GtkWidget *d, int response, GtkTreeSelection *sel)
745 PurplePlugin *plugin;
746 GtkTreeModel *model;
747 GValue val;
748 GtkTreeIter iter;
749 GList *list, *it;
751 g_return_if_fail(d != NULL);
753 switch (response) {
754 case GTK_RESPONSE_CLOSE:
755 case GTK_RESPONSE_DELETE_EVENT:
756 purple_request_close_with_handle(plugin_dialog);
757 purple_signals_disconnect_by_handle(plugin_dialog);
758 list = purple_plugins_find_all();
759 for (it = list; it; it = g_list_next(it))
760 pref_dialog_close(it->data);
761 g_list_free(list);
762 gtk_widget_destroy(d);
763 plugin_dialog = NULL;
764 break;
765 case PIDGIN_RESPONSE_CONFIGURE:
766 if (! gtk_tree_selection_get_selected (sel, &model, &iter))
767 return;
768 val.g_type = 0;
769 gtk_tree_model_get_value(model, &iter, 2, &val);
770 plugin = g_value_get_pointer(&val);
771 g_value_unset(&val);
772 if (plugin == NULL)
773 break;
775 pidgin_plugin_open_config(plugin, GTK_WINDOW(d));
777 break;
781 static void
782 show_plugin_prefs_cb(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, GtkWidget *dialog)
784 GtkTreeSelection *sel;
785 GtkTreeIter iter;
786 PurplePlugin *plugin;
787 GtkTreeModel *model;
789 sel = gtk_tree_view_get_selection(view);
791 if (!gtk_tree_selection_get_selected(sel, &model, &iter))
792 return;
794 gtk_tree_model_get(model, &iter, 2, &plugin, -1);
796 if (!purple_plugin_is_loaded(plugin))
797 return;
799 /* Now show the pref-dialog for the plugin */
800 plugin_dialog_response_cb(dialog, PIDGIN_RESPONSE_CONFIGURE, sel);
803 static gboolean
804 pidgin_plugins_paint_tooltip(GtkWidget *tipwindow, cairo_t *cr, gpointer data)
806 PangoLayout *layout = g_object_get_data(G_OBJECT(tipwindow), "tooltip-plugin");
807 GtkStyleContext *context = gtk_widget_get_style_context(tipwindow);
808 gtk_style_context_add_class(context, GTK_STYLE_CLASS_TOOLTIP);
809 gtk_render_layout(context, cr, 6, 6, layout);
810 return TRUE;
813 static gboolean
814 pidgin_plugins_create_tooltip(GtkWidget *tipwindow, GtkTreePath *path,
815 gpointer data, int *w, int *h)
817 GtkTreeIter iter;
818 GtkTreeView *treeview = GTK_TREE_VIEW(data);
819 PurplePlugin *plugin = NULL;
820 PurplePluginInfo *info;
821 GtkTreeModel *model = gtk_tree_view_get_model(treeview);
822 PangoLayout *layout;
823 int width, height;
824 const char * const *authorlist;
825 char *markup, *name, *desc;
826 char *authors = NULL, *pauthors = NULL;
828 if (!gtk_tree_model_get_iter(model, &iter, path))
829 return FALSE;
831 gtk_tree_model_get(model, &iter, 2, &plugin, -1);
832 info = purple_plugin_get_info(plugin);
833 authorlist = purple_plugin_info_get_authors(info);
835 if (authorlist)
836 authors = g_strjoinv(", ", (gchar **)authorlist);
837 if (authors)
838 pauthors = g_markup_escape_text(authors, -1);
840 markup = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>\n<b>%s:</b> %s\n<b>%s:</b> %s",
841 name = g_markup_escape_text(purple_plugin_info_get_name(info), -1),
842 _("Description"), desc = g_markup_escape_text(purple_plugin_info_get_description(info), -1),
843 (g_strv_length((gchar **)authorlist) > 1 ? _("Authors") : _("Author")), pauthors);
845 layout = gtk_widget_create_pango_layout(tipwindow, NULL);
846 pango_layout_set_markup(layout, markup, -1);
847 pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
848 pango_layout_set_width(layout, 600000);
849 pango_layout_get_size(layout, &width, &height);
850 g_object_set_data_full(G_OBJECT(tipwindow), "tooltip-plugin", layout, g_object_unref);
852 if (w)
853 *w = PANGO_PIXELS(width) + 12;
854 if (h)
855 *h = PANGO_PIXELS(height) + 12;
857 g_free(markup);
858 g_free(name);
859 g_free(desc);
860 g_free(pauthors);
861 g_free(authors);
863 return TRUE;
866 static gboolean
867 website_button_motion_cb(GtkWidget *button, GdkEventCrossing *event,
868 gpointer unused)
870 if (plugin_website_uri) {
871 pidgin_set_cursor(button, GDK_HAND2);
872 return TRUE;
874 return FALSE;
877 static gboolean
878 website_button_clicked_cb(GtkButton *button, GdkEventButton *event,
879 gpointer unused)
881 if (plugin_website_uri) {
882 purple_notify_uri(NULL, plugin_website_uri);
883 return TRUE;
885 return FALSE;
888 static GtkWidget *
889 create_details()
891 GtkBox *vbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 3));
892 GtkSizeGroup *sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
893 GtkWidget *label, *view, *website_button;
895 plugin_name = GTK_LABEL(gtk_label_new(NULL));
896 gtk_label_set_xalign(plugin_name, 0);
897 gtk_label_set_yalign(plugin_name, 0);
898 gtk_label_set_line_wrap(plugin_name, FALSE);
899 gtk_label_set_selectable(plugin_name, TRUE);
900 gtk_box_pack_start(vbox, GTK_WIDGET(plugin_name), FALSE, FALSE, 0);
902 view = gtk_text_view_new();
903 plugin_desc = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
904 g_object_set(view, "wrap-mode", GTK_WRAP_WORD,
905 "editable", FALSE,
906 "left-margin", PIDGIN_HIG_CAT_SPACE,
907 "right-margin", PIDGIN_HIG_CAT_SPACE,
908 NULL);
909 gtk_box_pack_start(vbox, view, TRUE, TRUE, 0);
911 plugin_error = GTK_LABEL(gtk_label_new(NULL));
912 gtk_label_set_xalign(plugin_error, 0);
913 gtk_label_set_yalign(plugin_error, 0);
914 gtk_label_set_line_wrap(plugin_error, FALSE);
915 gtk_label_set_selectable(plugin_error, TRUE);
916 gtk_box_pack_start(vbox, GTK_WIDGET(plugin_error), FALSE, FALSE, 0);
918 plugin_authors = GTK_LABEL(gtk_label_new(NULL));
919 gtk_label_set_line_wrap(plugin_authors, FALSE);
920 gtk_label_set_xalign(plugin_authors, 0);
921 gtk_label_set_yalign(plugin_authors, 0);
922 gtk_label_set_selectable(plugin_authors, TRUE);
923 pidgin_add_widget_to_vbox(vbox, "", sg,
924 GTK_WIDGET(plugin_authors), TRUE, &label);
925 gtk_label_set_markup(GTK_LABEL(label), _("<b>Written by:</b>"));
926 gtk_label_set_xalign(GTK_LABEL(label), 0);
927 gtk_label_set_yalign(GTK_LABEL(label), 0);
929 website_button = gtk_event_box_new();
930 gtk_event_box_set_visible_window(GTK_EVENT_BOX(website_button), FALSE);
932 plugin_website = GTK_LABEL(gtk_label_new(NULL));
933 g_object_set(G_OBJECT(plugin_website),
934 "ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL);
935 gtk_label_set_xalign(plugin_website, 0);
936 gtk_label_set_yalign(plugin_website, 0);
937 gtk_container_add(GTK_CONTAINER(website_button),
938 GTK_WIDGET(plugin_website));
939 g_signal_connect(website_button, "button-release-event",
940 G_CALLBACK(website_button_clicked_cb), NULL);
941 g_signal_connect(website_button, "enter-notify-event",
942 G_CALLBACK(website_button_motion_cb), NULL);
943 g_signal_connect(website_button, "leave-notify-event",
944 G_CALLBACK(pidgin_clear_cursor), NULL);
946 pidgin_add_widget_to_vbox(vbox, "", sg, website_button, TRUE, &label);
947 gtk_label_set_markup(GTK_LABEL(label), _("<b>Web site:</b>"));
948 gtk_label_set_xalign(GTK_LABEL(label), 0);
950 plugin_filename = GTK_LABEL(gtk_label_new(NULL));
951 gtk_label_set_line_wrap(plugin_filename, FALSE);
952 gtk_label_set_xalign(plugin_filename, 0);
953 gtk_label_set_yalign(plugin_filename, 0);
954 gtk_label_set_selectable(plugin_filename, TRUE);
955 pidgin_add_widget_to_vbox(vbox, "", sg,
956 GTK_WIDGET(plugin_filename), TRUE, &label);
957 gtk_label_set_markup(GTK_LABEL(label), _("<b>Filename:</b>"));
958 gtk_label_set_xalign(GTK_LABEL(label), 0);
959 gtk_label_set_yalign(GTK_LABEL(label), 0);
961 g_object_unref(sg);
963 return GTK_WIDGET(vbox);
967 void pidgin_plugin_dialog_show()
969 GtkWidget *event_view;
970 GtkListStore *ls;
971 GtkCellRenderer *rend, *rendt;
972 GtkTreeViewColumn *col;
973 GtkTreeSelection *sel;
975 if (plugin_dialog != NULL) {
976 gtk_window_present(GTK_WINDOW(plugin_dialog));
977 return;
980 plugin_dialog = gtk_dialog_new();
981 gtk_window_set_title(GTK_WINDOW(plugin_dialog), _("Plugins"));
982 pref_button = gtk_dialog_add_button(GTK_DIALOG(plugin_dialog),
983 _("Configure Pl_ugin"), PIDGIN_RESPONSE_CONFIGURE);
984 gtk_dialog_add_button(GTK_DIALOG(plugin_dialog),
985 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
986 gtk_widget_set_sensitive(pref_button, FALSE);
987 gtk_window_set_role(GTK_WINDOW(plugin_dialog), "plugins");
989 ls = gtk_list_store_new(4, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_BOOLEAN);
990 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls),
991 1, GTK_SORT_ASCENDING);
993 update_plugin_list(ls);
995 event_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls));
997 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(event_view), TRUE);
999 g_signal_connect(G_OBJECT(event_view), "row-activated",
1000 G_CALLBACK(show_plugin_prefs_cb), plugin_dialog);
1002 purple_signal_connect(purple_plugins_get_handle(), "plugin-load", plugin_dialog,
1003 PURPLE_CALLBACK(plugin_load_cb), event_view);
1004 purple_signal_connect(purple_plugins_get_handle(), "plugin-unload", plugin_dialog,
1005 PURPLE_CALLBACK(plugin_unload_cb), event_view);
1007 rend = gtk_cell_renderer_toggle_new();
1008 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (event_view));
1010 col = gtk_tree_view_column_new_with_attributes (_("Enabled"),
1011 rend,
1012 "active", 0,
1013 NULL);
1014 gtk_tree_view_append_column (GTK_TREE_VIEW(event_view), col);
1015 gtk_tree_view_column_set_sort_column_id(col, 0);
1016 g_signal_connect(G_OBJECT(rend), "toggled",
1017 G_CALLBACK(plugin_toggled), ls);
1019 rendt = gtk_cell_renderer_text_new();
1020 g_object_set(rendt,
1021 "foreground", "#c0c0c0",
1022 NULL);
1023 col = gtk_tree_view_column_new_with_attributes (_("Name"),
1024 rendt,
1025 "markup", 1,
1026 "foreground-set", 3,
1027 NULL);
1028 gtk_tree_view_column_set_expand (col, TRUE);
1029 g_object_set(rendt, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1030 gtk_tree_view_append_column (GTK_TREE_VIEW(event_view), col);
1031 gtk_tree_view_column_set_sort_column_id(col, 1);
1032 g_object_unref(G_OBJECT(ls));
1033 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(plugin_dialog))),
1034 pidgin_make_scrollable(event_view, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN, -1, -1),
1035 TRUE, TRUE, 0);
1036 gtk_tree_view_set_search_column(GTK_TREE_VIEW(event_view), 1);
1037 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(event_view),
1038 pidgin_tree_view_search_equal_func, NULL, NULL);
1040 pidgin_tooltip_setup_for_treeview(event_view, event_view,
1041 pidgin_plugins_create_tooltip,
1042 pidgin_plugins_paint_tooltip);
1045 expander = gtk_expander_new(_("<b>Plugin Details</b>"));
1046 gtk_expander_set_use_markup(GTK_EXPANDER(expander), TRUE);
1047 gtk_widget_set_sensitive(expander, FALSE);
1048 gtk_container_add(GTK_CONTAINER(expander), create_details());
1049 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(plugin_dialog))),
1050 expander, FALSE, FALSE, 0);
1053 g_signal_connect (G_OBJECT (sel), "changed", G_CALLBACK (prefs_plugin_sel), NULL);
1054 g_signal_connect(G_OBJECT(plugin_dialog), "response", G_CALLBACK(plugin_dialog_response_cb), sel);
1055 gtk_window_set_default_size(GTK_WINDOW(plugin_dialog), 430, 530);
1057 pidgin_auto_parent_window(plugin_dialog);
1059 gtk_widget_show_all(plugin_dialog);