1 /**
2  * @file gtkplugin.c GTK+ Plugins support
3  * @ingroup pidgin
4  */
5 
6 /* pidgin
7  *
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.
11  *
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.
16  *
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.
21  *
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
25  */
26 #include "internal.h"
27 #include "pidgin.h"
28 #include "gtkplugin.h"
29 #include "gtkpluginpref.h"
30 #include "gtkutils.h"
31 #include "debug.h"
32 #include "prefs.h"
33 #include "request.h"
34 #include "pidgintooltip.h"
35 
36 #include <string.h>
37 
38 #define PIDGIN_RESPONSE_CONFIGURE 98121
39 
40 static void plugin_toggled_stage_two(PurplePlugin *plug, GtkTreeModel *model,
41                                   GtkTreeIter *iter, gboolean unload);
42 
43 static GtkWidget *expander = NULL;
44 static GtkWidget *plugin_dialog = NULL;
45 
46 static GtkLabel *plugin_name = NULL;
47 static GtkTextBuffer *plugin_desc = NULL;
48 static GtkLabel *plugin_error = NULL;
49 static GtkLabel *plugin_author = NULL;
50 static GtkLabel *plugin_website = NULL;
51 static gchar *plugin_website_uri = NULL;
52 static GtkLabel *plugin_filename = NULL;
53 
54 static GtkWidget *pref_button = NULL;
55 static GHashTable *plugin_pref_dialogs = NULL;
56 
57 GtkWidget *
pidgin_plugin_get_config_frame(PurplePlugin * plugin)58 pidgin_plugin_get_config_frame(PurplePlugin *plugin)
59 {
60 	GtkWidget *config = NULL;
61 
62 	g_return_val_if_fail(plugin != NULL, NULL);
63 
64 	if (PIDGIN_IS_PIDGIN_PLUGIN(plugin) && plugin->info->ui_info
65 		&& PIDGIN_PLUGIN_UI_INFO(plugin)->get_config_frame)
66 	{
67 		PidginPluginUiInfo *ui_info;
68 
69 		ui_info = PIDGIN_PLUGIN_UI_INFO(plugin);
70 
71 		config = ui_info->get_config_frame(plugin);
72 
73 		if (plugin->info->prefs_info
74 			&& plugin->info->prefs_info->get_plugin_pref_frame)
75 		{
76 			purple_debug_warning("gtkplugin",
77 				"Plugin %s contains both, ui_info and "
78 				"prefs_info preferences; prefs_info will be "
79 				"ignored.", plugin->info->name);
80 		}
81 	}
82 
83 	if (config == NULL && plugin->info->prefs_info
84 		&& plugin->info->prefs_info->get_plugin_pref_frame)
85 	{
86 		PurplePluginPrefFrame *frame;
87 
88 		frame = plugin->info->prefs_info->get_plugin_pref_frame(plugin);
89 
90 		config = pidgin_plugin_pref_create_frame(frame);
91 
92 		plugin->info->prefs_info->frame = frame;
93 	}
94 
95 	return config;
96 }
97 
98 void
pidgin_plugins_save(void)99 pidgin_plugins_save(void)
100 {
101 	purple_plugins_save_loaded(PIDGIN_PREFS_ROOT "/plugins/loaded");
102 }
103 
104 static void
update_plugin_list(void * data)105 update_plugin_list(void *data)
106 {
107 	GtkListStore *ls = GTK_LIST_STORE(data);
108 	GtkTreeIter iter;
109 	GList *probes;
110 	PurplePlugin *plug;
111 
112 	gtk_list_store_clear(ls);
113 	purple_plugins_probe(G_MODULE_SUFFIX);
114 
115 	for (probes = purple_plugins_get_all();
116 		 probes != NULL;
117 		 probes = probes->next)
118 	{
119 		char *name;
120 		char *version;
121 		char *summary;
122 		char *desc;
123 		plug = probes->data;
124 
125 		if (plug->info->type == PURPLE_PLUGIN_LOADER) {
126 			GList *cur;
127 			for (cur = PURPLE_PLUGIN_LOADER_INFO(plug)->exts; cur != NULL;
128 					 cur = cur->next)
129 				purple_plugins_probe(cur->data);
130 			continue;
131 		} else if (plug->info->type != PURPLE_PLUGIN_STANDARD ||
132 			(plug->info->flags & PURPLE_PLUGIN_FLAG_INVISIBLE)) {
133 			continue;
134 		}
135 
136 		gtk_list_store_append (ls, &iter);
137 
138 		if (plug->info->name) {
139 			name = g_markup_escape_text(_(plug->info->name), -1);
140 		} else {
141 			char *tmp = g_path_get_basename(plug->path);
142 			name = g_markup_escape_text(tmp, -1);
143 			g_free(tmp);
144 		}
145 		version = g_markup_escape_text(purple_plugin_get_version(plug), -1);
146 		summary = g_markup_escape_text(purple_plugin_get_summary(plug), -1);
147 
148 		desc = g_strdup_printf("<b>%s</b> %s\n%s", name,
149 				       version,
150 				       summary);
151 		g_free(name);
152 		g_free(version);
153 		g_free(summary);
154 
155 		gtk_list_store_set(ls, &iter,
156 				   0, purple_plugin_is_loaded(plug),
157 				   1, desc,
158 				   2, plug,
159 				   3, purple_plugin_is_unloadable(plug),
160 				   -1);
161 		g_free(desc);
162 	}
163 }
164 
plugin_loading_common(PurplePlugin * plugin,GtkTreeView * view,gboolean loaded)165 static void plugin_loading_common(PurplePlugin *plugin, GtkTreeView *view, gboolean loaded)
166 {
167 	GtkTreeIter iter;
168 	GtkTreeModel *model = gtk_tree_view_get_model(view);
169 
170 	if (gtk_tree_model_get_iter_first(model, &iter)) {
171 		do {
172 			PurplePlugin *plug;
173 			GtkTreeSelection *sel;
174 
175 			gtk_tree_model_get(model, &iter, 2, &plug, -1);
176 
177 			if (plug != plugin)
178 				continue;
179 
180 			gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, loaded, -1);
181 
182 			/* If the loaded/unloaded plugin is the selected row,
183 			 * update the pref_button. */
184 			sel = gtk_tree_view_get_selection(view);
185 			if (gtk_tree_selection_get_selected(sel, &model, &iter))
186 			{
187 				gtk_tree_model_get(model, &iter, 2, &plug, -1);
188 				if (plug == plugin)
189 				{
190 					gtk_widget_set_sensitive(pref_button,
191 						loaded
192 						&& ((PIDGIN_IS_PIDGIN_PLUGIN(plug) && plug->info->ui_info
193 							&& PIDGIN_PLUGIN_UI_INFO(plug)->get_config_frame)
194 						 || (plug->info->prefs_info
195 							&& plug->info->prefs_info->get_plugin_pref_frame)));
196 				}
197 			}
198 
199 			break;
200 		} while (gtk_tree_model_iter_next(model, &iter));
201 	}
202 }
203 
plugin_load_cb(PurplePlugin * plugin,gpointer data)204 static void plugin_load_cb(PurplePlugin *plugin, gpointer data)
205 {
206 	GtkTreeView *view = (GtkTreeView *)data;
207 	plugin_loading_common(plugin, view, TRUE);
208 }
209 
plugin_unload_cb(PurplePlugin * plugin,gpointer data)210 static void plugin_unload_cb(PurplePlugin *plugin, gpointer data)
211 {
212 	GtkTreeView *view = (GtkTreeView *)data;
213 	plugin_loading_common(plugin, view, FALSE);
214 }
215 
pref_dialog_response_cb(GtkWidget * d,int response,PurplePlugin * plug)216 static void pref_dialog_response_cb(GtkWidget *d, int response, PurplePlugin *plug)
217 {
218 	switch (response) {
219 	case GTK_RESPONSE_CLOSE:
220 	case GTK_RESPONSE_DELETE_EVENT:
221 		g_hash_table_remove(plugin_pref_dialogs, plug);
222 		if (g_hash_table_size(plugin_pref_dialogs) == 0) {
223 			g_hash_table_destroy(plugin_pref_dialogs);
224 			plugin_pref_dialogs = NULL;
225 		}
226 		gtk_widget_destroy(d);
227 
228 		if (plug->info->prefs_info && plug->info->prefs_info->frame) {
229 			purple_plugin_pref_frame_destroy(plug->info->prefs_info->frame);
230 			plug->info->prefs_info->frame = NULL;
231 		}
232 
233 		break;
234 	}
235 }
236 
plugin_unload_confirm_cb(gpointer * data)237 static void plugin_unload_confirm_cb(gpointer *data)
238 {
239 	PurplePlugin *plugin = (PurplePlugin *)data[0];
240 	GtkTreeModel *model = (GtkTreeModel *)data[1];
241 	GtkTreeIter *iter = (GtkTreeIter *)data[2];
242 
243 	plugin_toggled_stage_two(plugin, model, iter, TRUE);
244 
245 	g_free(data);
246 }
247 
plugin_toggled(GtkCellRendererToggle * cell,gchar * pth,gpointer data)248 static void plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer data)
249 {
250 	GtkTreeModel *model = (GtkTreeModel *)data;
251 	GtkTreeIter *iter = g_new(GtkTreeIter, 1);
252 	GtkTreePath *path = gtk_tree_path_new_from_string(pth);
253 	PurplePlugin *plug;
254 	GtkWidget *dialog = NULL;
255 
256 	gtk_tree_model_get_iter(model, iter, path);
257 	gtk_tree_path_free(path);
258 	gtk_tree_model_get(model, iter, 2, &plug, -1);
259 
260 	/* Apparently, GTK+ won't honor the sensitive flag on cell renderers for booleans. */
261 	if (purple_plugin_is_unloadable(plug))
262 	{
263 		g_free(iter);
264 		return;
265 	}
266 
267 	if (!purple_plugin_is_loaded(plug))
268 	{
269 		pidgin_set_cursor(plugin_dialog, GDK_WATCH);
270 
271 		purple_plugin_load(plug);
272 		plugin_toggled_stage_two(plug, model, iter, FALSE);
273 
274 		pidgin_clear_cursor(plugin_dialog);
275 	}
276 	else
277 	{
278 		if (plugin_pref_dialogs != NULL &&
279 			(dialog = g_hash_table_lookup(plugin_pref_dialogs, plug)))
280 			pref_dialog_response_cb(dialog, GTK_RESPONSE_DELETE_EVENT, plug);
281 
282 		if (plug->dependent_plugins != NULL)
283 		{
284 			GString *tmp = g_string_new(_("The following plugins will be unloaded."));
285 			GList *l;
286 			gpointer *cb_data;
287 
288 			for (l = plug->dependent_plugins ; l != NULL ; l = l->next)
289 			{
290 				const char *dep_name = (const char *)l->data;
291 				PurplePlugin *dep_plugin = purple_plugins_find_with_id(dep_name);
292 				g_return_if_fail(dep_plugin != NULL);
293 
294 				g_string_append_printf(tmp, "\n\t%s\n", purple_plugin_get_name(dep_plugin));
295 			}
296 
297 			cb_data = g_new(gpointer, 3);
298 			cb_data[0] = plug;
299 			cb_data[1] = model;
300 			cb_data[2] = iter;
301 
302 			purple_request_action(plugin_dialog, NULL,
303 			                    _("Multiple plugins will be unloaded."),
304 			                    tmp->str, 0,
305 								NULL, NULL, NULL,
306 								cb_data, 2,
307 			                    _("Unload Plugins"), G_CALLBACK(plugin_unload_confirm_cb),
308 			                    _("Cancel"), g_free);
309 			g_string_free(tmp, TRUE);
310 		}
311 		else
312 			plugin_toggled_stage_two(plug, model, iter, TRUE);
313 	}
314 }
315 
plugin_toggled_stage_two(PurplePlugin * plug,GtkTreeModel * model,GtkTreeIter * iter,gboolean unload)316 static void plugin_toggled_stage_two(PurplePlugin *plug, GtkTreeModel *model, GtkTreeIter *iter, gboolean unload)
317 {
318 	if (unload)
319 	{
320 		pidgin_set_cursor(plugin_dialog, GDK_WATCH);
321 
322 		if (!purple_plugin_unload(plug))
323 		{
324 			const char *primary = _("Could not unload plugin");
325 			const char *reload = _("The plugin could not be unloaded now, but will be disabled at the next startup.");
326 
327 			if (!plug->error)
328 			{
329 				purple_notify_warning(NULL, NULL, primary, reload);
330 			}
331 			else
332 			{
333 				char *tmp = g_strdup_printf("%s\n\n%s", reload, plug->error);
334 				purple_notify_warning(NULL, NULL, primary, tmp);
335 				g_free(tmp);
336 			}
337 
338 			purple_plugin_disable(plug);
339 		}
340 
341 		pidgin_clear_cursor(plugin_dialog);
342 	}
343 
344 	gtk_widget_set_sensitive(pref_button,
345 		purple_plugin_is_loaded(plug)
346 		&& ((PIDGIN_IS_PIDGIN_PLUGIN(plug) && plug->info->ui_info
347 			&& PIDGIN_PLUGIN_UI_INFO(plug)->get_config_frame)
348 		 || (plug->info->prefs_info
349 			&& plug->info->prefs_info->get_plugin_pref_frame)));
350 
351 	if (plug->error != NULL)
352 	{
353 		gchar *name = g_markup_escape_text(purple_plugin_get_name(plug), -1);
354 
355 		gchar *error = g_markup_escape_text(plug->error, -1);
356 		gchar *text;
357 
358 		text = g_strdup_printf(
359 			"<b>%s</b> %s\n<span weight=\"bold\" color=\"red\"%s</span>",
360 			purple_plugin_get_name(plug), purple_plugin_get_version(plug), error);
361 		gtk_list_store_set(GTK_LIST_STORE (model), iter,
362 				   1, text,
363 				   -1);
364 		g_free(text);
365 
366 		text = g_strdup_printf(
367 			"<span weight=\"bold\" color=\"red\">%s</span>",
368 			error);
369 		gtk_label_set_markup(plugin_error, text);
370 		g_free(text);
371 
372 		g_free(error);
373 		g_free(name);
374 	}
375 
376 	gtk_list_store_set(GTK_LIST_STORE (model), iter,
377 	                   0, purple_plugin_is_loaded(plug),
378 	                   -1);
379 	g_free(iter);
380 
381 	pidgin_plugins_save();
382 }
383 
ensure_plugin_visible(void * data)384 static gboolean ensure_plugin_visible(void *data)
385 {
386 	GtkTreeSelection *sel = GTK_TREE_SELECTION(data);
387 	GtkTreeView *tv = gtk_tree_selection_get_tree_view(sel);
388 	GtkTreeModel *model = gtk_tree_view_get_model(tv);
389 	GtkTreePath *path;
390 	GtkTreeIter iter;
391 	if (!gtk_tree_selection_get_selected (sel, &model, &iter))
392 		return FALSE;
393 	path = gtk_tree_model_get_path(model, &iter);
394 	gtk_tree_view_scroll_to_cell(gtk_tree_selection_get_tree_view(sel), path, NULL, FALSE, 0, 0);
395 	gtk_tree_path_free(path);
396 	return FALSE;
397 }
398 
prefs_plugin_sel(GtkTreeSelection * sel,GtkTreeModel * model)399 static void prefs_plugin_sel (GtkTreeSelection *sel, GtkTreeModel *model)
400 {
401 	gchar *buf, *tmp, *name, *version;
402 	GtkTreeIter  iter;
403 	GValue val;
404 	PurplePlugin *plug;
405 
406 	if (!gtk_tree_selection_get_selected (sel, &model, &iter))
407 	{
408 		gtk_widget_set_sensitive(pref_button, FALSE);
409 
410 		/* Collapse and disable the expander widget */
411 		gtk_expander_set_expanded(GTK_EXPANDER(expander), FALSE);
412 		gtk_widget_set_sensitive(expander, FALSE);
413 
414 		return;
415 	}
416 
417 	gtk_widget_set_sensitive(expander, TRUE);
418 
419 	val.g_type = 0;
420 	gtk_tree_model_get_value (model, &iter, 2, &val);
421 	plug = g_value_get_pointer(&val);
422 
423 	name = g_markup_escape_text(purple_plugin_get_name(plug), -1);
424 	version = g_markup_escape_text(purple_plugin_get_version(plug), -1);
425 	buf = g_strdup_printf(
426 		"<span size=\"larger\" weight=\"bold\">%s</span> "
427 		"<span size=\"smaller\">%s</span>",
428 		name, version);
429 	gtk_label_set_markup(plugin_name, buf);
430 	g_free(name);
431 	g_free(version);
432 	g_free(buf);
433 
434 	gtk_text_buffer_set_text(plugin_desc, purple_plugin_get_description(plug), -1);
435 	gtk_label_set_text(plugin_author, purple_plugin_get_author(plug));
436 	gtk_label_set_text(plugin_filename, plug->path);
437 
438 	g_free(plugin_website_uri);
439 	plugin_website_uri = g_strdup(purple_plugin_get_homepage(plug));
440 	if (plugin_website_uri)
441 	{
442 		tmp = g_markup_escape_text(plugin_website_uri, -1);
443 		buf = g_strdup_printf("<span underline=\"single\" "
444 			"foreground=\"blue\">%s</span>", tmp);
445 		gtk_label_set_markup(plugin_website, buf);
446 		g_free(tmp);
447 		g_free(buf);
448 	}
449 	else
450 	{
451 		gtk_label_set_text(plugin_website, NULL);
452 	}
453 
454 	if (plug->error == NULL)
455 	{
456 		gtk_label_set_text(plugin_error, NULL);
457 	}
458 	else
459 	{
460 		tmp = g_markup_escape_text(plug->error, -1);
461 		buf = g_strdup_printf(
462 			_("<span foreground=\"red\" weight=\"bold\">"
463 			  "Error: %s\n"
464 			  "Check the plugin website for an update."
465 			  "</span>"),
466 			tmp);
467 		gtk_label_set_markup(plugin_error, buf);
468 		g_free(buf);
469 		g_free(tmp);
470 	}
471 
472 	gtk_widget_set_sensitive(pref_button,
473 		purple_plugin_is_loaded(plug)
474 		&& ((PIDGIN_IS_PIDGIN_PLUGIN(plug) && plug->info->ui_info
475 			&& PIDGIN_PLUGIN_UI_INFO(plug)->get_config_frame)
476 		 || (plug->info->prefs_info
477 			&& plug->info->prefs_info->get_plugin_pref_frame)));
478 
479 	/* Make sure the selected plugin is still visible */
480 	g_idle_add(ensure_plugin_visible, sel);
481 
482 	g_value_unset(&val);
483 }
484 
plugin_dialog_response_cb(GtkWidget * d,int response,GtkTreeSelection * sel)485 static void plugin_dialog_response_cb(GtkWidget *d, int response, GtkTreeSelection *sel)
486 {
487 	PurplePlugin *plug;
488 	GtkWidget *dialog, *box;
489 	GtkTreeModel *model;
490 	GValue val;
491 	GtkTreeIter iter;
492 
493 	switch (response) {
494 	case GTK_RESPONSE_CLOSE:
495 	case GTK_RESPONSE_DELETE_EVENT:
496 		purple_request_close_with_handle(plugin_dialog);
497 		purple_signals_disconnect_by_handle(plugin_dialog);
498 		gtk_widget_destroy(d);
499 		if (plugin_pref_dialogs != NULL) {
500 			g_hash_table_destroy(plugin_pref_dialogs);
501 			plugin_pref_dialogs = NULL;
502 		}
503 		plugin_dialog = NULL;
504 		break;
505 	case PIDGIN_RESPONSE_CONFIGURE:
506 		if (! gtk_tree_selection_get_selected (sel, &model, &iter))
507 			return;
508 		val.g_type = 0;
509 		gtk_tree_model_get_value(model, &iter, 2, &val);
510 		plug = g_value_get_pointer(&val);
511 		if (plug == NULL)
512 			break;
513 		if (plugin_pref_dialogs != NULL &&
514 			g_hash_table_lookup(plugin_pref_dialogs, plug))
515 			break;
516 		box = pidgin_plugin_get_config_frame(plug);
517 		if (box == NULL)
518 			break;
519 
520 		dialog = gtk_dialog_new_with_buttons(PIDGIN_ALERT_TITLE, GTK_WINDOW(d),
521 						     GTK_DIALOG_NO_SEPARATOR | GTK_DIALOG_DESTROY_WITH_PARENT,
522 						     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
523 						     NULL);
524 		if (plugin_pref_dialogs == NULL)
525 			plugin_pref_dialogs = g_hash_table_new(NULL, NULL);
526 
527 		g_hash_table_insert(plugin_pref_dialogs, plug, dialog);
528 
529 		g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(pref_dialog_response_cb), plug);
530 		gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
531 				pidgin_make_scrollable(box, GTK_POLICY_AUTOMATIC,
532 						GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN, 400, 400));
533 		gtk_window_set_role(GTK_WINDOW(dialog), "plugin_config");
534 		gtk_window_set_title(GTK_WINDOW(dialog), _(purple_plugin_get_name(plug)));
535 		gtk_widget_show_all(dialog);
536 		g_value_unset(&val);
537 		break;
538 	}
539 }
540 
541 static void
show_plugin_prefs_cb(GtkTreeView * view,GtkTreePath * path,GtkTreeViewColumn * column,GtkWidget * dialog)542 show_plugin_prefs_cb(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, GtkWidget *dialog)
543 {
544 	GtkTreeSelection *sel;
545 	GtkTreeIter iter;
546 	PurplePlugin *plugin;
547 	GtkTreeModel *model;
548 
549 	sel = gtk_tree_view_get_selection(view);
550 
551 	if (!gtk_tree_selection_get_selected(sel, &model, &iter))
552 		return;
553 
554 	gtk_tree_model_get(model, &iter, 2, &plugin, -1);
555 
556 	if (!purple_plugin_is_loaded(plugin))
557 		return;
558 
559 	/* Now show the pref-dialog for the plugin */
560 	plugin_dialog_response_cb(dialog, PIDGIN_RESPONSE_CONFIGURE, sel);
561 }
562 
563 static gboolean
pidgin_plugins_paint_tooltip(GtkWidget * tipwindow,gpointer data)564 pidgin_plugins_paint_tooltip(GtkWidget *tipwindow, gpointer data)
565 {
566 	PangoLayout *layout = g_object_get_data(G_OBJECT(tipwindow), "tooltip-plugin");
567 	gtk_paint_layout(tipwindow->style, tipwindow->window, GTK_STATE_NORMAL, FALSE,
568 			NULL, tipwindow, "tooltip",
569 			6, 6, layout);
570 	return TRUE;
571 }
572 
573 static gboolean
pidgin_plugins_create_tooltip(GtkWidget * tipwindow,GtkTreePath * path,gpointer data,int * w,int * h)574 pidgin_plugins_create_tooltip(GtkWidget *tipwindow, GtkTreePath *path,
575 		gpointer data, int *w, int *h)
576 {
577 	GtkTreeIter iter;
578 	GtkTreeView *treeview = GTK_TREE_VIEW(data);
579 	PurplePlugin *plugin = NULL;
580 	GtkTreeModel *model = gtk_tree_view_get_model(treeview);
581 	PangoLayout *layout;
582 	int width, height;
583 	char *markup, *name, *desc, *author;
584 
585 	if (!gtk_tree_model_get_iter(model, &iter, path))
586 		return FALSE;
587 
588 	gtk_tree_model_get(model, &iter, 2, &plugin, -1);
589 
590 	markup = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>\n<b>%s:</b> %s\n<b>%s:</b> %s",
591 			name = g_markup_escape_text(purple_plugin_get_name(plugin), -1),
592 			_("Description"), desc = g_markup_escape_text(purple_plugin_get_description(plugin), -1),
593 			_("Author"), author = g_markup_escape_text(purple_plugin_get_author(plugin), -1));
594 
595 	layout = gtk_widget_create_pango_layout(tipwindow, NULL);
596 	pango_layout_set_markup(layout, markup, -1);
597 	pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
598 	pango_layout_set_width(layout, 600000);
599 	pango_layout_get_size(layout, &width, &height);
600 	g_object_set_data_full(G_OBJECT(tipwindow), "tooltip-plugin", layout, g_object_unref);
601 
602 	if (w)
603 		*w = PANGO_PIXELS(width) + 12;
604 	if (h)
605 		*h = PANGO_PIXELS(height) + 12;
606 
607 	g_free(markup);
608 	g_free(name);
609 	g_free(desc);
610 	g_free(author);
611 
612 	return TRUE;
613 }
614 
615 static gboolean
website_button_motion_cb(GtkWidget * button,GdkEventCrossing * event,gpointer unused)616 website_button_motion_cb(GtkWidget *button, GdkEventCrossing *event,
617                           gpointer unused)
618 {
619 	if (plugin_website_uri) {
620 		pidgin_set_cursor(button, GDK_HAND2);
621 		return TRUE;
622 	}
623 	return FALSE;
624 }
625 
626 static gboolean
website_button_clicked_cb(GtkButton * button,GdkEventButton * event,gpointer unused)627 website_button_clicked_cb(GtkButton *button, GdkEventButton *event,
628                           gpointer unused)
629 {
630 	if (plugin_website_uri) {
631 		purple_notify_uri(NULL, plugin_website_uri);
632 		return TRUE;
633 	}
634 	return FALSE;
635 }
636 
637 static GtkWidget *
create_details()638 create_details()
639 {
640 	GtkBox *vbox = GTK_BOX(gtk_vbox_new(FALSE, 3));
641 	GtkSizeGroup *sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
642 	GtkWidget *label, *view, *website_button;
643 
644 	plugin_name = GTK_LABEL(gtk_label_new(NULL));
645 	gtk_misc_set_alignment(GTK_MISC(plugin_name), 0, 0);
646 	gtk_label_set_line_wrap(plugin_name, FALSE);
647 	gtk_label_set_selectable(plugin_name, TRUE);
648 	gtk_box_pack_start(vbox, GTK_WIDGET(plugin_name), FALSE, FALSE, 0);
649 
650 	view = gtk_text_view_new();
651 	plugin_desc = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
652 	g_object_set(view, "wrap-mode", GTK_WRAP_WORD,
653 	                   "editable",  FALSE,
654 	                   "left-margin",  PIDGIN_HIG_CAT_SPACE,
655 	                   "right-margin", PIDGIN_HIG_CAT_SPACE,
656 	                   NULL);
657 	gtk_box_pack_start(vbox, view, TRUE, TRUE, 0);
658 
659 	plugin_error = GTK_LABEL(gtk_label_new(NULL));
660 	gtk_misc_set_alignment(GTK_MISC(plugin_error), 0, 0);
661 	gtk_label_set_line_wrap(plugin_error, FALSE);
662 	gtk_label_set_selectable(plugin_error, TRUE);
663 	gtk_box_pack_start(vbox, GTK_WIDGET(plugin_error), FALSE, FALSE, 0);
664 
665 	plugin_author = GTK_LABEL(gtk_label_new(NULL));
666 	gtk_label_set_line_wrap(plugin_author, FALSE);
667 	gtk_misc_set_alignment(GTK_MISC(plugin_author), 0, 0);
668 	gtk_label_set_selectable(plugin_author, TRUE);
669 	pidgin_add_widget_to_vbox(vbox, "", sg,
670 		GTK_WIDGET(plugin_author), TRUE, &label);
671 	gtk_label_set_markup(GTK_LABEL(label), _("<b>Written by:</b>"));
672 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
673 
674 	website_button = gtk_event_box_new();
675 	gtk_event_box_set_visible_window(GTK_EVENT_BOX(website_button), FALSE);
676 
677 	plugin_website = GTK_LABEL(gtk_label_new(NULL));
678 	g_object_set(G_OBJECT(plugin_website),
679 		"ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL);
680 	gtk_misc_set_alignment(GTK_MISC(plugin_website), 0, 0);
681 	gtk_container_add(GTK_CONTAINER(website_button),
682 		GTK_WIDGET(plugin_website));
683 	g_signal_connect(website_button, "button-release-event",
684 		G_CALLBACK(website_button_clicked_cb), NULL);
685 	g_signal_connect(website_button, "enter-notify-event",
686 		G_CALLBACK(website_button_motion_cb), NULL);
687 	g_signal_connect(website_button, "leave-notify-event",
688 		G_CALLBACK(pidgin_clear_cursor), NULL);
689 
690 	pidgin_add_widget_to_vbox(vbox, "", sg, website_button, TRUE, &label);
691 	gtk_label_set_markup(GTK_LABEL(label), _("<b>Web site:</b>"));
692 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
693 
694 	plugin_filename = GTK_LABEL(gtk_label_new(NULL));
695 	gtk_label_set_line_wrap(plugin_filename, FALSE);
696 	gtk_misc_set_alignment(GTK_MISC(plugin_filename), 0, 0);
697 	gtk_label_set_selectable(plugin_filename, TRUE);
698 	pidgin_add_widget_to_vbox(vbox, "", sg,
699 		GTK_WIDGET(plugin_filename), TRUE, &label);
700 	gtk_label_set_markup(GTK_LABEL(label), _("<b>Filename:</b>"));
701 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
702 
703 	g_object_unref(sg);
704 
705 	return GTK_WIDGET(vbox);
706 }
707 
708 
pidgin_plugin_dialog_show()709 void pidgin_plugin_dialog_show()
710 {
711 	GtkWidget *event_view;
712 	GtkListStore *ls;
713 	GtkCellRenderer *rend, *rendt;
714 	GtkTreeViewColumn *col;
715 	GtkTreeSelection *sel;
716 
717 	if (plugin_dialog != NULL) {
718 		gtk_window_present(GTK_WINDOW(plugin_dialog));
719 		return;
720 	}
721 
722 	plugin_dialog = gtk_dialog_new_with_buttons(_("Plugins"),
723 						    NULL,
724 						    GTK_DIALOG_NO_SEPARATOR,
725 						    NULL);
726 	pref_button = gtk_dialog_add_button(GTK_DIALOG(plugin_dialog),
727 						_("Configure Pl_ugin"), PIDGIN_RESPONSE_CONFIGURE);
728 	gtk_dialog_add_button(GTK_DIALOG(plugin_dialog),
729 						GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
730 	gtk_widget_set_sensitive(pref_button, FALSE);
731 	gtk_window_set_role(GTK_WINDOW(plugin_dialog), "plugins");
732 
733 	ls = gtk_list_store_new(4, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_BOOLEAN);
734 	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ls),
735 					     1, GTK_SORT_ASCENDING);
736 
737 	update_plugin_list(ls);
738 
739 	event_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(ls));
740 
741 	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(event_view), TRUE);
742 
743 	g_signal_connect(G_OBJECT(event_view), "row-activated",
744 				G_CALLBACK(show_plugin_prefs_cb), plugin_dialog);
745 
746 	purple_signal_connect(purple_plugins_get_handle(), "plugin-load", plugin_dialog,
747 	                    PURPLE_CALLBACK(plugin_load_cb), event_view);
748 	purple_signal_connect(purple_plugins_get_handle(), "plugin-unload", plugin_dialog,
749 	                    PURPLE_CALLBACK(plugin_unload_cb), event_view);
750 
751 	rend = gtk_cell_renderer_toggle_new();
752 	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (event_view));
753 
754 	col = gtk_tree_view_column_new_with_attributes (_("Enabled"),
755 							rend,
756 							"active", 0,
757 							NULL);
758 	gtk_tree_view_append_column (GTK_TREE_VIEW(event_view), col);
759 	gtk_tree_view_column_set_sort_column_id(col, 0);
760 	g_signal_connect(G_OBJECT(rend), "toggled",
761 			 G_CALLBACK(plugin_toggled), ls);
762 
763 	rendt = gtk_cell_renderer_text_new();
764 	g_object_set(rendt,
765 		     "foreground", "#c0c0c0",
766 		     NULL);
767 	col = gtk_tree_view_column_new_with_attributes (_("Name"),
768 							rendt,
769 							"markup", 1,
770 							"foreground-set", 3,
771 							NULL);
772 	gtk_tree_view_column_set_expand (col, TRUE);
773 	g_object_set(rendt, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
774 	gtk_tree_view_append_column (GTK_TREE_VIEW(event_view), col);
775 	gtk_tree_view_column_set_sort_column_id(col, 1);
776 	g_object_unref(G_OBJECT(ls));
777 	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(plugin_dialog)->vbox),
778 		pidgin_make_scrollable(event_view, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_IN, -1, -1),
779 		TRUE, TRUE, 0);
780 	gtk_tree_view_set_search_column(GTK_TREE_VIEW(event_view), 1);
781 	gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(event_view),
782 				pidgin_tree_view_search_equal_func, NULL, NULL);
783 
784 	pidgin_tooltip_setup_for_treeview(event_view, event_view,
785 			pidgin_plugins_create_tooltip,
786 			pidgin_plugins_paint_tooltip);
787 
788 
789 	expander = gtk_expander_new(_("<b>Plugin Details</b>"));
790 	gtk_expander_set_use_markup(GTK_EXPANDER(expander), TRUE);
791 	gtk_widget_set_sensitive(expander, FALSE);
792 	gtk_container_add(GTK_CONTAINER(expander), create_details());
793 	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(plugin_dialog)->vbox), expander,
794 		FALSE, FALSE, 0);
795 
796 
797 	g_signal_connect (G_OBJECT (sel), "changed", G_CALLBACK (prefs_plugin_sel), NULL);
798 	g_signal_connect(G_OBJECT(plugin_dialog), "response", G_CALLBACK(plugin_dialog_response_cb), sel);
799 	gtk_window_set_default_size(GTK_WINDOW(plugin_dialog), 430, 530);
800 
801 	pidgin_auto_parent_window(plugin_dialog);
802 
803 	gtk_widget_show_all(plugin_dialog);
804 }
805