Add deleted HTTP digest functions to ChangeLog.API.
[pidgin-git.git] / pidgin / gtkplugin.c
blob53ae976e51f5e875ce61ffadbfbf26a6b0b58090
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_RESPONSE_CONFIGURE 98121
37 typedef struct
39 PidginPluginConfigFrameCb config_frame_cb;
40 } PidginPluginInfoPrivate;
42 enum
44 PROP_0,
45 PROP_GTK_CONFIG_FRAME_CB,
46 PROP_LAST
49 typedef struct
51 enum
53 PIDGIN_PLUGIN_UI_DATA_TYPE_FRAME,
54 PIDGIN_PLUGIN_UI_DATA_TYPE_REQUEST
55 } type;
57 union
59 struct
61 GtkWidget *dialog;
62 PurplePluginPrefFrame *pref_frame;
63 } frame;
65 gpointer request_handle;
66 } u;
67 } PidginPluginUiData;
69 G_DEFINE_TYPE_WITH_PRIVATE(PidginPluginInfo, pidgin_plugin_info,
70 PURPLE_TYPE_PLUGIN_INFO);
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 =
94 pidgin_plugin_info_get_instance_private(
95 PIDGIN_PLUGIN_INFO(obj));
97 switch (param_id) {
98 case PROP_GTK_CONFIG_FRAME_CB:
99 priv->config_frame_cb = g_value_get_pointer(value);
100 break;
101 default:
102 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
103 break;
107 /* Get method for GObject properties */
108 static void
109 pidgin_plugin_info_get_property(GObject *obj, guint param_id, GValue *value,
110 GParamSpec *pspec)
112 PidginPluginInfoPrivate *priv =
113 pidgin_plugin_info_get_instance_private(
114 PIDGIN_PLUGIN_INFO(obj));
116 switch (param_id) {
117 case PROP_GTK_CONFIG_FRAME_CB:
118 g_value_set_pointer(value, priv->config_frame_cb);
119 break;
120 default:
121 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
122 break;
126 /* Class initializer function */
127 static void pidgin_plugin_info_class_init(PidginPluginInfoClass *klass)
129 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
131 /* Setup properties */
132 obj_class->get_property = pidgin_plugin_info_get_property;
133 obj_class->set_property = pidgin_plugin_info_set_property;
135 g_object_class_install_property(obj_class, PROP_GTK_CONFIG_FRAME_CB,
136 g_param_spec_pointer("gtk-config-frame-cb",
137 "GTK configuration frame callback",
138 "Callback that returns a GTK configuration frame",
139 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
140 G_PARAM_STATIC_STRINGS));
143 static void
144 pidgin_plugin_info_init(PidginPluginInfo *info)
148 PidginPluginInfo *
149 pidgin_plugin_info_new(const char *first_property, ...)
151 GObject *info;
152 va_list var_args;
154 /* at least ID is required */
155 if (!first_property)
156 return NULL;
158 va_start(var_args, first_property);
159 info = g_object_new_valist(PIDGIN_TYPE_PLUGIN_INFO, first_property,
160 var_args);
161 va_end(var_args);
163 g_object_set(info, "ui-requirement", PIDGIN_UI, NULL);
165 return PIDGIN_PLUGIN_INFO(info);
168 static gboolean
169 pidgin_plugin_has_prefs(PurplePlugin *plugin)
171 PurplePluginInfo *info = purple_plugin_get_info(plugin);
172 PidginPluginInfoPrivate *priv = NULL;
173 gboolean ret;
175 g_return_val_if_fail(plugin != NULL, FALSE);
177 if (!purple_plugin_is_loaded(plugin))
178 return FALSE;
180 if (PIDGIN_IS_PLUGIN_INFO(info))
181 priv = pidgin_plugin_info_get_instance_private(
182 PIDGIN_PLUGIN_INFO(info));
184 ret = ((priv && priv->config_frame_cb) ||
185 purple_plugin_info_get_pref_frame_cb(info) ||
186 purple_plugin_info_get_pref_request_cb(info));
188 return ret;
191 static GtkWidget *
192 pidgin_plugin_get_config_frame(PurplePlugin *plugin,
193 PurplePluginPrefFrame **purple_pref_frame)
195 GtkWidget *config = NULL;
196 PurplePluginInfo *info;
197 PurplePluginPrefFrameCb pref_frame_cb = NULL;
199 g_return_val_if_fail(PURPLE_IS_PLUGIN(plugin), NULL);
201 info = purple_plugin_get_info(plugin);
202 if(!PURPLE_IS_PLUGIN_INFO(info))
203 return NULL;
205 pref_frame_cb = purple_plugin_info_get_pref_frame_cb(info);
206 if(pref_frame_cb) {
207 PurplePluginPrefFrame *frame = pref_frame_cb(plugin);
209 if(frame) {
210 config = pidgin_plugin_pref_create_frame(frame);
212 *purple_pref_frame = frame;
216 return config;
219 static void
220 pref_dialog_close(PurplePlugin *plugin)
222 PurplePluginInfo *info;
223 PidginPluginUiData *ui_data;
225 g_return_if_fail(plugin != NULL);
227 info = purple_plugin_get_info(plugin);
229 ui_data = purple_plugin_info_get_ui_data(info);
230 if (ui_data == NULL)
231 return;
233 if (ui_data->type == PIDGIN_PLUGIN_UI_DATA_TYPE_REQUEST) {
234 purple_request_close(PURPLE_REQUEST_FIELDS,
235 ui_data->u.request_handle);
236 return;
239 g_return_if_fail(ui_data->type == PIDGIN_PLUGIN_UI_DATA_TYPE_FRAME);
241 gtk_widget_destroy(ui_data->u.frame.dialog);
243 if (ui_data->u.frame.pref_frame)
244 purple_plugin_pref_frame_destroy(ui_data->u.frame.pref_frame);
246 g_free(ui_data);
247 purple_plugin_info_set_ui_data(info, NULL);
251 static void
252 pref_dialog_response_cb(GtkWidget *dialog, int response, PurplePlugin *plugin)
254 if (response == GTK_RESPONSE_CLOSE ||
255 response == GTK_RESPONSE_DELETE_EVENT)
257 pref_dialog_close(plugin);
261 static void
262 pidgin_plugin_open_config(PurplePlugin *plugin, GtkWindow *parent)
264 PurplePluginInfo *info;
265 PidginPluginInfoPrivate *priv = NULL;
266 PidginPluginUiData *ui_data;
267 PurplePluginPrefFrameCb pref_frame_cb;
268 PurplePluginPrefRequestCb pref_request_cb;
269 PidginPluginConfigFrameCb get_pidgin_frame = NULL;
270 gint prefs_count;
272 g_return_if_fail(plugin != NULL);
274 info = purple_plugin_get_info(plugin);
276 if (!pidgin_plugin_has_prefs(plugin)) {
277 purple_debug_warning("gtkplugin", "Plugin has no prefs");
278 return;
281 if (purple_plugin_info_get_ui_data(info))
282 return;
284 if (PIDGIN_IS_PLUGIN_INFO(info))
285 priv = pidgin_plugin_info_get_instance_private(
286 PIDGIN_PLUGIN_INFO(info));
288 pref_frame_cb = purple_plugin_info_get_pref_frame_cb(info);
289 pref_request_cb = purple_plugin_info_get_pref_request_cb(info);
291 if (priv)
292 get_pidgin_frame = priv->config_frame_cb;
294 prefs_count = 0;
295 if (pref_frame_cb)
296 prefs_count++;
297 if (pref_request_cb)
298 prefs_count++;
299 if (get_pidgin_frame)
300 prefs_count++;
302 if (prefs_count > 1) {
303 purple_debug_warning("gtkplugin",
304 "Plugin %s contains more than one prefs "
305 "callback, some will be ignored.",
306 gplugin_plugin_info_get_name(
307 GPLUGIN_PLUGIN_INFO(info)));
309 g_return_if_fail(prefs_count > 0);
311 ui_data = g_new0(PidginPluginUiData, 1);
312 purple_plugin_info_set_ui_data(info, ui_data);
314 /* Priority: pidgin frame > purple request > purple frame
315 * Purple frame could be replaced with purple request some day.
317 if (pref_request_cb && !get_pidgin_frame) {
318 ui_data->type = PIDGIN_PLUGIN_UI_DATA_TYPE_REQUEST;
319 ui_data->u.request_handle = pref_request_cb(plugin);
320 purple_request_add_close_notify(ui_data->u.request_handle,
321 purple_callback_set_zero, &info->ui_data);
322 purple_request_add_close_notify(ui_data->u.request_handle,
323 g_free, ui_data);
324 } else {
325 GtkWidget *box, *dialog;
327 ui_data->type = PIDGIN_PLUGIN_UI_DATA_TYPE_FRAME;
329 box = pidgin_plugin_get_config_frame(plugin,
330 &ui_data->u.frame.pref_frame);
331 if (box == NULL) {
332 purple_debug_error("gtkplugin",
333 "Failed to display prefs frame");
334 g_free(ui_data);
335 purple_plugin_info_set_ui_data(info, NULL);
336 return;
338 gtk_widget_set_vexpand(box, TRUE);
340 ui_data->u.frame.dialog = dialog = gtk_dialog_new_with_buttons(
341 PIDGIN_ALERT_TITLE, parent,
342 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE,
343 GTK_RESPONSE_CLOSE, NULL);
345 g_signal_connect(G_OBJECT(dialog), "response",
346 G_CALLBACK(pref_dialog_response_cb), plugin);
348 gtk_container_add(GTK_CONTAINER(
349 gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
350 pidgin_make_scrollable(box, GTK_POLICY_NEVER,
351 GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN, 400, 400));
353 gtk_window_set_role(GTK_WINDOW(dialog), "plugin_config");
354 gtk_window_set_title(GTK_WINDOW(dialog),
355 _(gplugin_plugin_info_get_name(
356 GPLUGIN_PLUGIN_INFO(info))));
357 gtk_widget_show_all(dialog);
361 void
362 pidgin_plugins_save(void)
364 purple_plugins_save_loaded(PIDGIN_PREFS_ROOT "/plugins/loaded");
367 static void
368 update_plugin_list(void *data)
370 GtkListStore *ls = GTK_LIST_STORE(data);
371 GtkTreeIter iter;
372 GList *plugins, *l;
373 PurplePlugin *plug;
374 PurplePluginInfo *info;
375 GPluginPluginInfo *ginfo;
377 gtk_list_store_clear(ls);
378 purple_plugins_refresh();
380 plugins = purple_plugins_find_all();
382 for (l = plugins; l != NULL; l = l->next)
384 char *name;
385 char *version;
386 char *summary;
387 char *desc;
388 plug = PURPLE_PLUGIN(l->data);
389 info = purple_plugin_get_info(plug);
390 ginfo = GPLUGIN_PLUGIN_INFO(info);
392 if (purple_plugin_is_internal(plug))
393 continue;
395 gtk_list_store_append (ls, &iter);
397 if (gplugin_plugin_info_get_name(ginfo)) {
398 name = g_markup_escape_text(
399 _(gplugin_plugin_info_get_name(ginfo)), -1);
400 } else {
401 char *tmp = g_path_get_basename(
402 gplugin_plugin_get_filename(plug));
403 name = g_markup_escape_text(tmp, -1);
404 g_free(tmp);
406 version = g_markup_escape_text(
407 gplugin_plugin_info_get_version(ginfo), -1);
408 summary = g_markup_escape_text(
409 gplugin_plugin_info_get_summary(ginfo), -1);
411 desc = g_strdup_printf("<b>%s</b> %s\n%s", name,
412 version,
413 summary);
414 g_free(name);
415 g_free(version);
416 g_free(summary);
418 gtk_list_store_set(ls, &iter,
419 0, purple_plugin_is_loaded(plug),
420 1, desc,
421 2, plug,
422 3, purple_plugin_info_get_error(info),
423 -1);
424 g_free(desc);
427 g_list_free(plugins);
430 static gboolean
431 check_if_loaded(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
433 PurplePlugin *plugin;
434 gtk_tree_model_get(model, iter, 2, &plugin, -1);
435 gtk_list_store_set(GTK_LIST_STORE(model), iter,
436 0, purple_plugin_is_loaded(plugin),
437 -1);
438 return FALSE;
441 static void
442 update_loaded_plugins(GtkTreeModel *model)
444 gtk_tree_model_foreach(model, check_if_loaded, NULL);
447 static void plugin_loading_common(PurplePlugin *plugin, GtkTreeView *view, gboolean loaded)
449 GtkTreeIter iter;
450 GtkTreeModel *model = gtk_tree_view_get_model(view);
452 if (gtk_tree_model_get_iter_first(model, &iter)) {
453 do {
454 PurplePlugin *plug;
455 GtkTreeSelection *sel;
457 gtk_tree_model_get(model, &iter, 2, &plug, -1);
459 if (plug != plugin)
460 continue;
462 gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, loaded, -1);
464 /* If the loaded/unloaded plugin is the selected row,
465 * update the pref_button. */
466 sel = gtk_tree_view_get_selection(view);
467 if (gtk_tree_selection_get_selected(sel, &model, &iter))
469 gtk_tree_model_get(model, &iter, 2, &plug, -1);
470 if (plug == plugin)
472 gtk_widget_set_sensitive(pref_button,
473 pidgin_plugin_has_prefs(plug));
477 break;
478 } while (gtk_tree_model_iter_next(model, &iter));
482 static void plugin_load_cb(PurplePlugin *plugin, gpointer data)
484 GtkTreeView *view = (GtkTreeView *)data;
485 plugin_loading_common(plugin, view, TRUE);
488 static void plugin_unload_cb(PurplePlugin *plugin, gpointer data)
490 GtkTreeView *view = (GtkTreeView *)data;
491 plugin_loading_common(plugin, view, FALSE);
494 static void plugin_unload_confirm_cb(gpointer *data)
496 PurplePlugin *plugin = (PurplePlugin *)data[0];
497 GtkTreeModel *model = (GtkTreeModel *)data[1];
498 GtkTreeIter *iter = (GtkTreeIter *)data[2];
500 plugin_toggled_stage_two(plugin, model, iter, NULL, TRUE);
502 g_free(data);
505 static void plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer data)
507 GtkTreeModel *model = (GtkTreeModel *)data;
508 GtkTreeIter *iter = g_new(GtkTreeIter, 1);
509 GtkTreePath *path = gtk_tree_path_new_from_string(pth);
510 PurplePlugin *plug;
511 GError *error = NULL;
513 gtk_tree_model_get_iter(model, iter, path);
514 gtk_tree_path_free(path);
515 gtk_tree_model_get(model, iter, 2, &plug, -1);
517 if (!purple_plugin_is_loaded(plug))
519 pidgin_set_cursor(plugin_dialog, GDK_WATCH);
521 purple_plugin_load(plug, &error);
522 plugin_toggled_stage_two(plug, model, iter, error, FALSE);
524 pidgin_clear_cursor(plugin_dialog);
526 else
528 pref_dialog_close(plug);
530 if (purple_plugin_get_dependent_plugins(plug) != NULL)
532 GString *tmp = g_string_new(_("The following plugins will be unloaded."));
533 GSList *l;
534 gpointer *cb_data;
536 for (l = purple_plugin_get_dependent_plugins(plug); l != NULL ; l = l->next)
538 const char *dep_name = (const char *)l->data;
539 PurplePlugin *dep_plugin = purple_plugins_find_plugin(dep_name);
540 GPluginPluginInfo *dep_info;
542 if (dep_plugin == NULL) {
543 purple_debug_error("gtkplugin",
544 "The %s plugin could not be found.",
545 dep_name);
546 continue;
549 dep_info = GPLUGIN_PLUGIN_INFO(
550 purple_plugin_get_info(dep_plugin));
551 g_string_append_printf(
552 tmp, "\n\t%s\n",
553 gplugin_plugin_info_get_name(dep_info));
556 cb_data = g_new(gpointer, 3);
557 cb_data[0] = plug;
558 cb_data[1] = model;
559 cb_data[2] = iter;
561 purple_request_action(plugin_dialog, NULL,
562 _("Multiple plugins will be unloaded."),
563 tmp->str, 0, NULL, cb_data, 2,
564 _("Unload Plugins"),
565 G_CALLBACK(plugin_unload_confirm_cb),
566 _("Cancel"), g_free);
567 g_string_free(tmp, TRUE);
569 else
570 plugin_toggled_stage_two(plug, model, iter, NULL, TRUE);
574 static void plugin_toggled_stage_two(PurplePlugin *plug, GtkTreeModel *model, GtkTreeIter *iter, GError *error, gboolean unload)
576 GPluginPluginInfo *info =
577 GPLUGIN_PLUGIN_INFO(purple_plugin_get_info(plug));
579 if (unload)
581 pidgin_set_cursor(plugin_dialog, GDK_WATCH);
583 if (!purple_plugin_unload(plug, &error))
585 const char *primary = _("Could not unload plugin");
586 const char *reload = _("The plugin could not be unloaded now, but will be disabled at the next startup.");
588 char *tmp = g_strdup_printf("%s\n\n%s", reload, error->message);
589 purple_notify_warning(NULL, NULL, primary, tmp, NULL);
590 g_free(tmp);
592 purple_plugin_disable(plug);
595 pidgin_clear_cursor(plugin_dialog);
596 } else if (error) {
597 purple_notify_warning(NULL, NULL, _("Could not load plugin"), error->message, NULL);
600 gtk_widget_set_sensitive(pref_button, pidgin_plugin_has_prefs(plug));
602 if (error != NULL)
604 gchar *name = g_markup_escape_text(
605 gplugin_plugin_info_get_name(info), -1);
607 gchar *disp_error = g_markup_escape_text(error->message, -1);
608 gchar *text;
610 text = g_strdup_printf("<b>%s</b> %s\n<span weight=\"bold\" "
611 "color=\"red\">%s</span>",
612 gplugin_plugin_info_get_name(info),
613 gplugin_plugin_info_get_version(info),
614 disp_error);
615 gtk_list_store_set(GTK_LIST_STORE (model), iter,
616 1, text,
617 -1);
618 g_free(text);
620 text = g_strdup_printf(
621 "<span weight=\"bold\" color=\"red\">%s</span>",
622 disp_error);
623 gtk_label_set_markup(plugin_error, text);
624 g_free(text);
626 g_free(disp_error);
627 g_free(name);
629 g_error_free(error);
632 if ((unload && purple_plugin_get_dependent_plugins(plug)) ||
633 (!unload && gplugin_plugin_info_get_dependencies(info))) {
634 update_loaded_plugins(model);
635 } else {
636 gtk_list_store_set(GTK_LIST_STORE (model), iter,
637 0, purple_plugin_is_loaded(plug),
638 -1);
640 g_free(iter);
642 pidgin_plugins_save();
645 static gboolean ensure_plugin_visible(void *data)
647 GtkTreeSelection *sel = GTK_TREE_SELECTION(data);
648 GtkTreeView *tv = gtk_tree_selection_get_tree_view(sel);
649 GtkTreeModel *model = gtk_tree_view_get_model(tv);
650 GtkTreePath *path;
651 GtkTreeIter iter;
652 if (!gtk_tree_selection_get_selected (sel, &model, &iter))
653 return FALSE;
654 path = gtk_tree_model_get_path(model, &iter);
655 gtk_tree_view_scroll_to_cell(gtk_tree_selection_get_tree_view(sel), path, NULL, FALSE, 0, 0);
656 gtk_tree_path_free(path);
657 return FALSE;
660 static void prefs_plugin_sel (GtkTreeSelection *sel, GtkTreeModel *model)
662 gchar *buf, *tmp, *name, *version;
663 gchar *authors = NULL;
664 const gchar * const *authorlist;
665 GtkTreeIter iter;
666 GValue val;
667 PurplePlugin *plug;
668 PurplePluginInfo *info;
669 GPluginPluginInfo *ginfo;
671 if (!gtk_tree_selection_get_selected (sel, &model, &iter))
673 gtk_widget_set_sensitive(pref_button, FALSE);
675 /* Collapse and disable the expander widget */
676 gtk_expander_set_expanded(GTK_EXPANDER(expander), FALSE);
677 gtk_widget_set_sensitive(expander, FALSE);
679 return;
682 gtk_widget_set_sensitive(expander, TRUE);
684 val.g_type = 0;
685 gtk_tree_model_get_value (model, &iter, 2, &val);
686 plug = g_value_get_pointer(&val);
687 info = purple_plugin_get_info(plug);
688 ginfo = GPLUGIN_PLUGIN_INFO(info);
690 name = g_markup_escape_text(gplugin_plugin_info_get_name(ginfo), -1);
691 version = g_markup_escape_text(gplugin_plugin_info_get_version(ginfo),
692 -1);
693 buf = g_strdup_printf(
694 "<span size=\"larger\" weight=\"bold\">%s</span> "
695 "<span size=\"smaller\">%s</span>",
696 name, version);
697 gtk_label_set_markup(plugin_name, buf);
698 g_free(name);
699 g_free(version);
700 g_free(buf);
702 gtk_text_buffer_set_text(
703 plugin_desc, gplugin_plugin_info_get_description(ginfo), -1);
705 authorlist = gplugin_plugin_info_get_authors(ginfo);
706 if (authorlist)
707 authors = g_strjoinv(",\n", (gchar **)authorlist);
708 gtk_label_set_text(plugin_authors, authors);
709 g_free(authors);
711 gtk_label_set_text(plugin_filename, gplugin_plugin_get_filename(plug));
713 g_free(plugin_website_uri);
714 plugin_website_uri = g_strdup(gplugin_plugin_info_get_website(ginfo));
716 if (plugin_website_uri)
718 tmp = g_markup_escape_text(plugin_website_uri, -1);
719 buf = g_strdup_printf("<span underline=\"single\" "
720 "foreground=\"blue\">%s</span>", tmp);
721 gtk_label_set_markup(plugin_website, buf);
722 g_free(tmp);
723 g_free(buf);
725 else
727 gtk_label_set_text(plugin_website, NULL);
730 if (purple_plugin_info_get_error(info) == NULL)
732 gtk_label_set_text(plugin_error, NULL);
734 else
736 tmp = g_markup_escape_text(purple_plugin_info_get_error(info), -1);
737 buf = g_strdup_printf(
738 _("<span foreground=\"red\" weight=\"bold\">"
739 "Error: %s\n"
740 "Check the plugin website for an update."
741 "</span>"),
742 tmp);
743 gtk_label_set_markup(plugin_error, buf);
744 g_free(buf);
745 g_free(tmp);
748 gtk_widget_set_sensitive(pref_button, pidgin_plugin_has_prefs(plug));
750 /* Make sure the selected plugin is still visible */
751 g_idle_add(ensure_plugin_visible, sel);
753 g_value_unset(&val);
756 static void plugin_dialog_response_cb(GtkWidget *d, int response, GtkTreeSelection *sel)
758 PurplePlugin *plugin;
759 GtkTreeModel *model;
760 GValue val;
761 GtkTreeIter iter;
762 GList *list, *it;
764 g_return_if_fail(d != NULL);
766 switch (response) {
767 case GTK_RESPONSE_CLOSE:
768 case GTK_RESPONSE_DELETE_EVENT:
769 purple_request_close_with_handle(plugin_dialog);
770 purple_signals_disconnect_by_handle(plugin_dialog);
771 list = purple_plugins_find_all();
772 for (it = list; it; it = g_list_next(it))
773 pref_dialog_close(it->data);
774 g_list_free(list);
775 gtk_widget_destroy(d);
776 plugin_dialog = NULL;
777 break;
778 case PIDGIN_RESPONSE_CONFIGURE:
779 if (! gtk_tree_selection_get_selected (sel, &model, &iter))
780 return;
781 val.g_type = 0;
782 gtk_tree_model_get_value(model, &iter, 2, &val);
783 plugin = g_value_get_pointer(&val);
784 g_value_unset(&val);
785 if (plugin == NULL)
786 break;
788 pidgin_plugin_open_config(plugin, GTK_WINDOW(d));
790 break;
794 static void
795 show_plugin_prefs_cb(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, GtkWidget *dialog)
797 GtkTreeSelection *sel;
798 GtkTreeIter iter;
799 PurplePlugin *plugin;
800 GtkTreeModel *model;
802 sel = gtk_tree_view_get_selection(view);
804 if (!gtk_tree_selection_get_selected(sel, &model, &iter))
805 return;
807 gtk_tree_model_get(model, &iter, 2, &plugin, -1);
809 if (!purple_plugin_is_loaded(plugin))
810 return;
812 /* Now show the pref-dialog for the plugin */
813 plugin_dialog_response_cb(dialog, PIDGIN_RESPONSE_CONFIGURE, sel);
816 static gboolean
817 pidgin_plugins_paint_tooltip(GtkWidget *tipwindow, cairo_t *cr, gpointer data)
819 PangoLayout *layout = g_object_get_data(G_OBJECT(tipwindow), "tooltip-plugin");
820 GtkStyleContext *context = gtk_widget_get_style_context(tipwindow);
821 gtk_style_context_add_class(context, GTK_STYLE_CLASS_TOOLTIP);
822 gtk_render_layout(context, cr, 6, 6, layout);
823 return TRUE;
826 static gboolean
827 pidgin_plugins_create_tooltip(GtkWidget *tipwindow, GtkTreePath *path,
828 gpointer data, int *w, int *h)
830 GtkTreeIter iter;
831 GtkTreeView *treeview = GTK_TREE_VIEW(data);
832 PurplePlugin *plugin = NULL;
833 GPluginPluginInfo *info;
834 GtkTreeModel *model = gtk_tree_view_get_model(treeview);
835 PangoLayout *layout;
836 int width, height;
837 const char * const *authorlist;
838 char *markup, *name, *desc;
839 char *authors = NULL, *pauthors = NULL;
841 if (!gtk_tree_model_get_iter(model, &iter, path))
842 return FALSE;
844 gtk_tree_model_get(model, &iter, 2, &plugin, -1);
845 info = GPLUGIN_PLUGIN_INFO(purple_plugin_get_info(plugin));
846 authorlist = gplugin_plugin_info_get_authors(info);
848 if (authorlist)
849 authors = g_strjoinv(", ", (gchar **)authorlist);
850 if (authors)
851 pauthors = g_markup_escape_text(authors, -1);
853 markup = g_strdup_printf(
854 "<span size='x-large' weight='bold'>%s</span>\n<b>%s:</b> "
855 "%s\n<b>%s:</b> %s",
856 name = g_markup_escape_text(gplugin_plugin_info_get_name(info), -1),
857 _("Description"),
858 desc = g_markup_escape_text(
859 gplugin_plugin_info_get_description(info), -1),
860 (authorlist && g_strv_length((gchar **)authorlist) > 1
861 ? _("Authors")
862 : _("Author")),
863 pauthors);
865 layout = gtk_widget_create_pango_layout(tipwindow, NULL);
866 pango_layout_set_markup(layout, markup, -1);
867 pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
868 pango_layout_set_width(layout, 600000);
869 pango_layout_get_size(layout, &width, &height);
870 g_object_set_data_full(G_OBJECT(tipwindow), "tooltip-plugin", layout, g_object_unref);
872 if (w)
873 *w = PANGO_PIXELS(width) + 12;
874 if (h)
875 *h = PANGO_PIXELS(height) + 12;
877 g_free(markup);
878 g_free(name);
879 g_free(desc);
880 g_free(pauthors);
881 g_free(authors);
883 return TRUE;
886 static gboolean
887 website_button_motion_cb(GtkWidget *button, GdkEventCrossing *event,
888 gpointer unused)
890 if (plugin_website_uri) {
891 pidgin_set_cursor(button, GDK_HAND2);
892 return TRUE;
894 return FALSE;
897 static gboolean
898 website_button_clicked_cb(GtkButton *button, GdkEventButton *event,
899 gpointer unused)
901 if (plugin_website_uri) {
902 purple_notify_uri(NULL, plugin_website_uri);
903 return TRUE;
905 return FALSE;
908 static GtkWidget *
909 create_details()
911 GtkBox *vbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 3));
912 GtkSizeGroup *sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
913 GtkWidget *label, *view, *website_button;
915 plugin_name = GTK_LABEL(gtk_label_new(NULL));
916 gtk_label_set_xalign(plugin_name, 0);
917 gtk_label_set_yalign(plugin_name, 0);
918 gtk_label_set_line_wrap(plugin_name, FALSE);
919 gtk_label_set_selectable(plugin_name, TRUE);
920 gtk_box_pack_start(vbox, GTK_WIDGET(plugin_name), FALSE, FALSE, 0);
922 view = gtk_text_view_new();
923 plugin_desc = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
924 g_object_set(view, "wrap-mode", GTK_WRAP_WORD,
925 "editable", FALSE,
926 "left-margin", PIDGIN_HIG_CAT_SPACE,
927 "right-margin", PIDGIN_HIG_CAT_SPACE,
928 NULL);
929 gtk_box_pack_start(vbox, view, TRUE, TRUE, 0);
931 plugin_error = GTK_LABEL(gtk_label_new(NULL));
932 gtk_label_set_xalign(plugin_error, 0);
933 gtk_label_set_yalign(plugin_error, 0);
934 gtk_label_set_line_wrap(plugin_error, FALSE);
935 gtk_label_set_selectable(plugin_error, TRUE);
936 gtk_box_pack_start(vbox, GTK_WIDGET(plugin_error), FALSE, FALSE, 0);
938 plugin_authors = GTK_LABEL(gtk_label_new(NULL));
939 gtk_label_set_line_wrap(plugin_authors, FALSE);
940 gtk_label_set_xalign(plugin_authors, 0);
941 gtk_label_set_yalign(plugin_authors, 0);
942 gtk_label_set_selectable(plugin_authors, TRUE);
943 pidgin_add_widget_to_vbox(vbox, "", sg,
944 GTK_WIDGET(plugin_authors), TRUE, &label);
945 gtk_label_set_markup(GTK_LABEL(label), _("<b>Written by:</b>"));
946 gtk_label_set_xalign(GTK_LABEL(label), 0);
947 gtk_label_set_yalign(GTK_LABEL(label), 0);
949 website_button = gtk_event_box_new();
950 gtk_event_box_set_visible_window(GTK_EVENT_BOX(website_button), FALSE);
952 plugin_website = GTK_LABEL(gtk_label_new(NULL));
953 g_object_set(G_OBJECT(plugin_website),
954 "ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL);
955 gtk_label_set_xalign(plugin_website, 0);
956 gtk_label_set_yalign(plugin_website, 0);
957 gtk_container_add(GTK_CONTAINER(website_button),
958 GTK_WIDGET(plugin_website));
959 g_signal_connect(website_button, "button-release-event",
960 G_CALLBACK(website_button_clicked_cb), NULL);
961 g_signal_connect(website_button, "enter-notify-event",
962 G_CALLBACK(website_button_motion_cb), NULL);
963 g_signal_connect(website_button, "leave-notify-event",
964 G_CALLBACK(pidgin_clear_cursor), NULL);
966 pidgin_add_widget_to_vbox(vbox, "", sg, website_button, TRUE, &label);
967 gtk_label_set_markup(GTK_LABEL(label), _("<b>Web site:</b>"));
968 gtk_label_set_xalign(GTK_LABEL(label), 0);
970 plugin_filename = GTK_LABEL(gtk_label_new(NULL));
971 gtk_label_set_line_wrap(plugin_filename, FALSE);
972 gtk_label_set_xalign(plugin_filename, 0);
973 gtk_label_set_yalign(plugin_filename, 0);
974 gtk_label_set_selectable(plugin_filename, TRUE);
975 pidgin_add_widget_to_vbox(vbox, "", sg,
976 GTK_WIDGET(plugin_filename), TRUE, &label);
977 gtk_label_set_markup(GTK_LABEL(label), _("<b>Filename:</b>"));
978 gtk_label_set_xalign(GTK_LABEL(label), 0);
979 gtk_label_set_yalign(GTK_LABEL(label), 0);
981 g_object_unref(sg);
983 return GTK_WIDGET(vbox);
987 void pidgin_plugin_dialog_show()
989 GtkWidget *event_view;
990 GtkListStore *ls;
991 GtkCellRenderer *rend, *rendt;
992 GtkTreeViewColumn *col;
993 GtkTreeSelection *sel;
995 if (plugin_dialog != NULL) {
996 gtk_window_present(GTK_WINDOW(plugin_dialog));
997 return;
1000 plugin_dialog = gtk_dialog_new();
1001 gtk_window_set_title(GTK_WINDOW(plugin_dialog), _("Plugins"));
1002 pref_button = gtk_dialog_add_button(GTK_DIALOG(plugin_dialog),
1003 _("Configure Pl_ugin"), PIDGIN_RESPONSE_CONFIGURE);
1004 gtk_dialog_add_button(GTK_DIALOG(plugin_dialog),
1005 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1006 gtk_widget_set_sensitive(pref_button, FALSE);
1007 gtk_window_set_role(GTK_WINDOW(plugin_dialog), "plugins");
1009 ls = gtk_list_store_new(4, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_BOOLEAN);
1010 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls),
1011 1, GTK_SORT_ASCENDING);
1013 update_plugin_list(ls);
1015 event_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls));
1017 g_signal_connect(G_OBJECT(event_view), "row-activated",
1018 G_CALLBACK(show_plugin_prefs_cb), plugin_dialog);
1020 purple_signal_connect(purple_plugins_get_handle(), "plugin-load", plugin_dialog,
1021 PURPLE_CALLBACK(plugin_load_cb), event_view);
1022 purple_signal_connect(purple_plugins_get_handle(), "plugin-unload", plugin_dialog,
1023 PURPLE_CALLBACK(plugin_unload_cb), event_view);
1025 rend = gtk_cell_renderer_toggle_new();
1026 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (event_view));
1028 col = gtk_tree_view_column_new_with_attributes (_("Enabled"),
1029 rend,
1030 "active", 0,
1031 NULL);
1032 gtk_tree_view_append_column (GTK_TREE_VIEW(event_view), col);
1033 gtk_tree_view_column_set_sort_column_id(col, 0);
1034 g_signal_connect(G_OBJECT(rend), "toggled",
1035 G_CALLBACK(plugin_toggled), ls);
1037 rendt = gtk_cell_renderer_text_new();
1038 g_object_set(rendt,
1039 "foreground", "#c0c0c0",
1040 NULL);
1041 col = gtk_tree_view_column_new_with_attributes (_("Name"),
1042 rendt,
1043 "markup", 1,
1044 "foreground-set", 3,
1045 NULL);
1046 gtk_tree_view_column_set_expand (col, TRUE);
1047 g_object_set(rendt, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1048 gtk_tree_view_append_column (GTK_TREE_VIEW(event_view), col);
1049 gtk_tree_view_column_set_sort_column_id(col, 1);
1050 g_object_unref(G_OBJECT(ls));
1051 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(plugin_dialog))),
1052 pidgin_make_scrollable(event_view, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN, -1, -1),
1053 TRUE, TRUE, 0);
1054 gtk_tree_view_set_search_column(GTK_TREE_VIEW(event_view), 1);
1055 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(event_view),
1056 pidgin_tree_view_search_equal_func, NULL, NULL);
1058 pidgin_tooltip_setup_for_treeview(event_view, event_view,
1059 pidgin_plugins_create_tooltip,
1060 pidgin_plugins_paint_tooltip);
1063 expander = gtk_expander_new(_("<b>Plugin Details</b>"));
1064 gtk_expander_set_use_markup(GTK_EXPANDER(expander), TRUE);
1065 gtk_widget_set_sensitive(expander, FALSE);
1066 gtk_container_add(GTK_CONTAINER(expander), create_details());
1067 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(plugin_dialog))),
1068 expander, FALSE, FALSE, 0);
1071 g_signal_connect (G_OBJECT (sel), "changed", G_CALLBACK (prefs_plugin_sel), NULL);
1072 g_signal_connect(G_OBJECT(plugin_dialog), "response", G_CALLBACK(plugin_dialog_response_cb), sel);
1073 gtk_window_set_default_size(GTK_WINDOW(plugin_dialog), 430, 530);
1075 pidgin_auto_parent_window(plugin_dialog);
1077 gtk_widget_show_all(plugin_dialog);