1 /*
2  * gnm-plugin.c: Plugin services - reading XML info, activating, etc.
3  *                   (everything independent of plugin loading method)
4  *
5  * Author: Zbigniew Chyla (cyba@gnome.pl)
6  */
7 
8 #include <gnumeric-config.h>
9 #include <gutils.h>
10 #include <tools/gnm-solver.h>
11 #include <func.h>
12 #include <gnm-plugin.h>
13 #include <gnumeric-conf.h>
14 #include <application.h>
15 
16 #include <goffice/goffice.h>
17 #include <gsf/gsf-impl-utils.h>
18 #include <gsf/gsf-input-stdio.h>
19 #include <gsf/gsf-input-memory.h>
20 #include <glib/gi18n-lib.h>
21 #include <string.h>
22 
23 #define CXML2C(s) ((char const *)(s))
24 #define CC2XML(s) ((xmlChar const *)(s))
25 
26 static char *
xml2c(xmlChar * src)27 xml2c (xmlChar *src)
28 {
29 	char *dst = g_strdup (CXML2C (src));
30 	xmlFree (src);
31 	return dst;
32 }
33 
34 typedef GOPluginServiceSimpleClass GnmPluginServiceFunctionGroupClass;
35 struct GnmPluginServiceFunctionGroup_ {
36 	GOPluginServiceSimple	base;
37 
38 	gchar *category_name, *translated_category_name;
39 	GSList *function_name_list;
40 
41 	GnmFuncGroup *func_group;
42 	GnmPluginServiceFunctionGroupCallbacks cbs;
43 	char *tdomain;
44 };
45 
46 static void
plugin_service_function_group_finalize(GObject * obj)47 plugin_service_function_group_finalize (GObject *obj)
48 {
49 	GnmPluginServiceFunctionGroup *sfg = GNM_PLUGIN_SERVICE_FUNCTION_GROUP (obj);
50 	GObjectClass *parent_class;
51 
52 	g_free (sfg->category_name);
53 	sfg->category_name = NULL;
54 
55 	g_free (sfg->translated_category_name);
56 	sfg->translated_category_name = NULL;
57 
58 	g_slist_free_full (sfg->function_name_list, g_free);
59 	sfg->function_name_list = NULL;
60 
61 	g_free (sfg->tdomain);
62 	sfg->tdomain = NULL;
63 
64 	parent_class = g_type_class_peek (GO_TYPE_PLUGIN_SERVICE);
65 	parent_class->finalize (obj);
66 }
67 
68 static void
plugin_service_function_group_read_xml(GOPluginService * service,xmlNode * tree,GOErrorInfo ** ret_error)69 plugin_service_function_group_read_xml (GOPluginService *service, xmlNode *tree, GOErrorInfo **ret_error)
70 {
71 	xmlNode *category_node, *translated_category_node, *functions_node;
72 	gchar *category_name, *translated_category_name;
73 	GSList *function_name_list = NULL;
74 	gchar *tdomain = NULL;
75 
76 	GO_INIT_RET_ERROR_INFO (ret_error);
77 	category_node = go_xml_get_child_by_name_no_lang (tree, "category");
78 	category_name = category_node
79 		? xml2c (xmlNodeGetContent (category_node))
80 		: NULL;
81 
82 	translated_category_node = go_xml_get_child_by_name_by_lang (tree, "category");
83 	if (translated_category_node != NULL) {
84 		xmlChar *lang;
85 
86 		lang = go_xml_node_get_cstr (translated_category_node, "lang");
87 		if (lang != NULL) {
88 			translated_category_name =
89 				xml2c (xmlNodeGetContent (translated_category_node));
90 			xmlFree (lang);
91 		} else {
92 			translated_category_name = NULL;
93 		}
94 	} else {
95 		translated_category_name = NULL;
96 	}
97 	functions_node = go_xml_get_child_by_name (tree, CC2XML ("functions"));
98 	if (functions_node != NULL) {
99 		xmlNode *node;
100 
101 		tdomain = xml2c (go_xml_node_get_cstr (functions_node, "textdomain"));
102 
103 		for (node = functions_node->xmlChildrenNode; node != NULL; node = node->next) {
104 			gchar *func_name;
105 
106 			if (strcmp (CXML2C (node->name), "function") != 0)
107 				continue;
108 
109 			func_name = xml2c (go_xml_node_get_cstr (node, "name"));
110 			if (!func_name)
111 				continue;
112 
113 			GO_SLIST_PREPEND (function_name_list, func_name);
114 		}
115 		GO_SLIST_REVERSE (function_name_list);
116 	}
117 	if (category_name != NULL && function_name_list != NULL) {
118 		GnmPluginServiceFunctionGroup *sfg = GNM_PLUGIN_SERVICE_FUNCTION_GROUP (service);
119 
120 		sfg->category_name = category_name;
121 		sfg->translated_category_name = translated_category_name;
122 		sfg->function_name_list = function_name_list;
123 		sfg->tdomain = tdomain;
124 	} else {
125 		GSList *error_list = NULL;
126 
127 		if (category_name == NULL) {
128 			GO_SLIST_PREPEND (error_list, go_error_info_new_str (
129 				_("Missing function category name.")));
130 		}
131 		if (function_name_list == NULL) {
132 			GO_SLIST_PREPEND (error_list, go_error_info_new_str (
133 				_("Function group is empty.")));
134 		}
135 		GO_SLIST_REVERSE (error_list);
136 		*ret_error = go_error_info_new_from_error_list (error_list);
137 
138 		g_free (category_name);
139 		g_free (translated_category_name);
140 		g_slist_free_full (function_name_list, g_free);
141 
142 		g_free (tdomain);
143 	}
144 }
145 
146 static void
plugin_service_function_group_func_ref_notify(GnmFunc * fn_def,G_GNUC_UNUSED GParamSpec * pspec,GOPlugin * plugin)147 plugin_service_function_group_func_ref_notify (GnmFunc *fn_def,
148 					       G_GNUC_UNUSED GParamSpec *pspec,
149 					       GOPlugin *plugin)
150 {
151 	if (gnm_func_get_in_use (fn_def))
152 		go_plugin_use_ref (plugin);
153 	else
154 		go_plugin_use_unref (plugin);
155 }
156 
157 static void
plugin_service_function_group_func_load_stub(GnmFunc * fn_def,GOPluginService * service)158 plugin_service_function_group_func_load_stub (GnmFunc *fn_def,
159 					      GOPluginService *service)
160 {
161 	GnmPluginServiceFunctionGroup *sfg = GNM_PLUGIN_SERVICE_FUNCTION_GROUP (service);
162 	GOErrorInfo *error = NULL;
163 
164 	g_return_if_fail (fn_def != NULL);
165 
166 	go_plugin_service_load (service, &error);
167 	if (error != NULL) {
168 		go_error_info_print (error);
169 		go_error_info_free (error);
170 		return;
171 	}
172 
173 	if (!sfg->cbs.load_stub) {
174                 error = go_error_info_new_printf (_("No load_stub method.\n"));
175 		go_error_info_print (error);
176 		go_error_info_free (error);
177 		return;
178 	}
179 
180 	sfg->cbs.load_stub (service, fn_def);
181 }
182 
183 static void
delayed_ref_notify(GOPlugin * plugin,GnmFunc * fd)184 delayed_ref_notify (GOPlugin *plugin, GnmFunc *fd)
185 {
186 	g_signal_handlers_disconnect_by_func (plugin,
187 					      G_CALLBACK (delayed_ref_notify),
188 					      fd);
189 
190 	/* We cannot do this until after the plugin has been activated.  */
191 	plugin_service_function_group_func_ref_notify (fd, NULL, plugin);
192 }
193 
194 static void
plugin_service_function_group_activate(GOPluginService * service,GOErrorInfo ** ret_error)195 plugin_service_function_group_activate (GOPluginService *service, GOErrorInfo **ret_error)
196 {
197 	GnmPluginServiceFunctionGroup *sfg =
198 		GNM_PLUGIN_SERVICE_FUNCTION_GROUP (service);
199 	GOPlugin *plugin = go_plugin_service_get_plugin (service);
200 	GSList *l;
201 
202 	GO_INIT_RET_ERROR_INFO (ret_error);
203 	sfg->func_group = gnm_func_group_fetch (sfg->category_name,
204 						sfg->translated_category_name);
205 	if (gnm_debug_flag ("plugin-func"))
206 		g_printerr ("Activating group %s\n", sfg->category_name);
207 
208 	for (l = sfg->function_name_list; l; l = l->next) {
209 		const char *fname = l->data;
210 		GnmFunc *func = gnm_func_lookup_or_add_placeholder (fname);
211 
212 		gnm_func_set_stub (func);
213 		gnm_func_set_translation_domain (func, sfg->tdomain);
214 		gnm_func_set_function_group (func, sfg->func_group);
215 		// Clear localized_name so we can deduce the proper name.
216 		//gnm_func_set_localized_name (func, NULL);
217 
218 		g_signal_connect
219 			(func, "notify::in-use",
220 			 G_CALLBACK (plugin_service_function_group_func_ref_notify),
221 			 plugin);
222 
223 		g_signal_connect
224 			(func, "load-stub",
225 			 G_CALLBACK (plugin_service_function_group_func_load_stub),
226 			 service);
227 
228 		if (gnm_func_get_in_use (func))
229 			g_signal_connect (plugin,
230 					  "state_changed",
231 					  G_CALLBACK (delayed_ref_notify),
232 					  func);
233 	}
234 	service->is_active = TRUE;
235 }
236 
237 static void
plugin_service_function_group_deactivate(GOPluginService * service,GOErrorInfo ** ret_error)238 plugin_service_function_group_deactivate (GOPluginService *service, GOErrorInfo **ret_error)
239 {
240 	GnmPluginServiceFunctionGroup *sfg = GNM_PLUGIN_SERVICE_FUNCTION_GROUP (service);
241 	GOPlugin *plugin = go_plugin_service_get_plugin (service);
242 	GSList *l;
243 
244 	if (gnm_debug_flag ("plugin-func"))
245 		g_printerr ("Deactivating group %s\n", sfg->category_name);
246 
247 	GO_INIT_RET_ERROR_INFO (ret_error);
248 
249 	for (l = sfg->function_name_list; l; l = l->next) {
250 		const char *fname = l->data;
251 		GnmFunc *func = gnm_func_lookup (fname, NULL);
252 
253 		// This should not happen, but if it were to, having a handler
254 		// of some other object is not going to be good.
255 		if (gnm_func_get_in_use (func))
256 			g_signal_handlers_disconnect_by_func
257 				(plugin, G_CALLBACK (delayed_ref_notify), func);
258 
259 		// Someone else might hold a ref so make sure the object
260 		// becomes inaccessible via gnm_func_lookup
261 		gnm_func_dispose (func);
262 
263 		g_object_unref (func);
264 	}
265 	service->is_active = FALSE;
266 }
267 
268 static char *
plugin_service_function_group_get_description(GOPluginService * service)269 plugin_service_function_group_get_description (GOPluginService *service)
270 {
271 	GnmPluginServiceFunctionGroup *sfg = GNM_PLUGIN_SERVICE_FUNCTION_GROUP (service);
272 	int n_functions;
273 	char const *category_name;
274 
275 	n_functions = g_slist_length (sfg->function_name_list);
276 	category_name = sfg->translated_category_name != NULL
277 		? sfg->translated_category_name
278 		: sfg->category_name;
279 
280 	return g_strdup_printf (ngettext (
281 			"%d function in category \"%s\"",
282 			"Group of %d functions in category \"%s\"",
283 			n_functions),
284 		n_functions, category_name);
285 }
286 
287 static void
plugin_service_function_group_init(GnmPluginServiceFunctionGroup * s)288 plugin_service_function_group_init (GnmPluginServiceFunctionGroup *s)
289 {
290 	GO_PLUGIN_SERVICE (s)->cbs_ptr = &s->cbs;
291 	s->category_name = NULL;
292 	s->translated_category_name = NULL;
293 	s->function_name_list = NULL;
294 	s->func_group = NULL;
295 	s->tdomain = NULL;
296 }
297 
298 static void
plugin_service_function_group_class_init(GObjectClass * gobject_class)299 plugin_service_function_group_class_init (GObjectClass *gobject_class)
300 {
301 	GOPluginServiceClass *plugin_service_class = GO_PLUGIN_SERVICE_CLASS (gobject_class);
302 
303 	gobject_class->finalize		= plugin_service_function_group_finalize;
304 	plugin_service_class->read_xml	= plugin_service_function_group_read_xml;
305 	plugin_service_class->activate	= plugin_service_function_group_activate;
306 	plugin_service_class->deactivate = plugin_service_function_group_deactivate;
307 	plugin_service_class->get_description = plugin_service_function_group_get_description;
308 }
309 
310 GSF_CLASS (GnmPluginServiceFunctionGroup, gnm_plugin_service_function_group,
311            plugin_service_function_group_class_init, plugin_service_function_group_init,
312            GO_TYPE_PLUGIN_SERVICE_SIMPLE)
313 
314 /****************************************************************************/
315 
316 /*
317  * PluginServiceUI
318  */
319 typedef GOPluginServiceSimpleClass PluginServiceUIClass;
320 struct GnmPluginServiceUI_ {
321 	GOPluginServiceSimple base;
322 
323 	char *file_name;
324 	GSList *actions;
325 
326 	gpointer layout_id;
327 	GnmPluginServiceUICallbacks cbs;
328 };
329 
330 static void
plugin_service_ui_init(PluginServiceUI * s)331 plugin_service_ui_init (PluginServiceUI *s)
332 {
333 	GO_PLUGIN_SERVICE (s)->cbs_ptr = &s->cbs;
334 	s->file_name = NULL;
335 	s->actions = NULL;
336 	s->layout_id = NULL;
337 	s->cbs.plugin_func_exec_action = NULL;
338 }
339 
340 static void
plugin_service_ui_finalize(GObject * obj)341 plugin_service_ui_finalize (GObject *obj)
342 {
343 	PluginServiceUI *service_ui = GNM_PLUGIN_SERVICE_UI (obj);
344 	GObjectClass *parent_class;
345 
346 	g_free (service_ui->file_name);
347 	service_ui->file_name = NULL;
348 	g_slist_free_full (service_ui->actions, (GDestroyNotify)gnm_action_unref);
349 	service_ui->actions = NULL;
350 
351 	parent_class = g_type_class_peek (GO_TYPE_PLUGIN_SERVICE);
352 	parent_class->finalize (obj);
353 }
354 
355 static void
cb_ui_service_activate(GnmAction const * action,WorkbookControl * wbc,GOPluginService * service)356 cb_ui_service_activate (GnmAction const *action, WorkbookControl *wbc, GOPluginService *service)
357 {
358 	GOErrorInfo *load_error = NULL;
359 
360 	go_plugin_service_load (service, &load_error);
361 	if (load_error == NULL) {
362 		PluginServiceUI *service_ui = GNM_PLUGIN_SERVICE_UI (service);
363 		GOErrorInfo *ignored_error = NULL;
364 
365 		g_return_if_fail (service_ui->cbs.plugin_func_exec_action != NULL);
366 		service_ui->cbs.plugin_func_exec_action (
367 			service, action, wbc, &ignored_error);
368 		if (ignored_error != NULL) {
369 			go_error_info_print (ignored_error);
370 			go_error_info_free (ignored_error);
371 		}
372 	} else {
373 		go_error_info_print (load_error);
374 		go_error_info_free (load_error);
375 	}
376 }
377 
378 static void
plugin_service_ui_read_xml(GOPluginService * service,xmlNode * tree,GOErrorInfo ** ret_error)379 plugin_service_ui_read_xml (GOPluginService *service, xmlNode *tree, GOErrorInfo **ret_error)
380 {
381 	PluginServiceUI *service_ui = GNM_PLUGIN_SERVICE_UI (service);
382 	xmlChar *file_name;
383 	xmlNode *verbs_node;
384 	GSList *actions = NULL;
385 
386 	GO_INIT_RET_ERROR_INFO (ret_error);
387 	file_name = xml2c (go_xml_node_get_cstr (tree, "file"));
388 	if (file_name == NULL) {
389 		*ret_error = go_error_info_new_str (
390 		             _("Missing file name."));
391 		return;
392 	}
393 	verbs_node = go_xml_get_child_by_name (tree, "actions");
394 	if (verbs_node != NULL) {
395 		xmlNode *ptr, *label_node;
396 		xmlChar *name, *icon;
397 		gchar *label;
398 		gboolean always_available;
399 		GnmAction *action;
400 
401 		for (ptr = verbs_node->xmlChildrenNode; ptr != NULL; ptr = ptr->next) {
402 			if (xmlIsBlankNode (ptr) || ptr->name == NULL ||
403 			    strcmp (CXML2C (ptr->name), "action"))
404 				continue;
405 			name  = go_xml_node_get_cstr (ptr, "name");
406 /*			label = go_xml_node_get_cstr (ptr, "label");*/
407 /*****************************************************************************************/
408 			label_node = go_xml_get_child_by_name_no_lang (ptr, "label");
409 			label = label_node
410 				? xml2c (xmlNodeGetContent (label_node))
411 				: NULL;
412 
413 			label_node = go_xml_get_child_by_name_by_lang (ptr, "label");
414 			if (label_node != NULL) {
415 				gchar *lang;
416 
417 				lang = go_xml_node_get_cstr (label_node, "lang");
418 				if (lang != NULL) {
419 					label = xml2c (xmlNodeGetContent (label_node));
420 					xmlFree (lang);
421 				}
422 			}
423 /*****************************************************************************************/
424 			icon  = go_xml_node_get_cstr (ptr, "icon");
425 			if (!go_xml_node_get_bool (ptr, "always_available", &always_available))
426 				always_available = FALSE;
427 			action = gnm_action_new (name, label, icon, always_available,
428 						 (GnmActionHandler) cb_ui_service_activate,
429 						 service, NULL);
430 			if (NULL != name) xmlFree (name);
431 			g_free (label);
432 			if (NULL != icon) xmlFree (icon);
433 			if (NULL != action)
434 				GO_SLIST_PREPEND (actions, action);
435 		}
436 	}
437 	GO_SLIST_REVERSE (actions);
438 
439 	service_ui->file_name = file_name;
440 	service_ui->actions = actions;
441 }
442 
443 static void
plugin_service_ui_activate(GOPluginService * service,GOErrorInfo ** ret_error)444 plugin_service_ui_activate (GOPluginService *service, GOErrorInfo **ret_error)
445 {
446 	PluginServiceUI *service_ui = GNM_PLUGIN_SERVICE_UI (service);
447 	const char *uifile = service_ui->file_name;
448 	char *xml_ui, *group_name;
449 	char const *tdomain;
450 	GError *error = NULL;
451 	GsfInput *src;
452 	size_t len;
453 
454 	GO_INIT_RET_ERROR_INFO (ret_error);
455 
456 	if (strncmp (uifile, "res:", 4) == 0) {
457 		size_t len;
458 		gconstpointer data = go_rsm_lookup (uifile + 4, &len);
459 		src = data
460 			? gsf_input_memory_new (data, len, FALSE)
461 			: NULL;
462 	} else if (strncmp (uifile, "data:", 5) == 0) {
463 		const char *data = uifile + 5;
464 		src = gsf_input_memory_new (data, strlen (data), FALSE);
465 	} else {
466 		char *full_file_name = g_path_is_absolute (uifile)
467 			? g_strdup (uifile)
468 			: g_build_filename
469 			(go_plugin_get_dir_name (service->plugin),
470 			 uifile,
471 			 NULL);
472 		src = gsf_input_stdio_new (full_file_name, &error);
473 		g_free (full_file_name);
474 	}
475 	if (!src)
476 		goto err;
477 
478 	src = gsf_input_uncompress (src);
479 	len = gsf_input_size (src);
480 	xml_ui = g_strndup (gsf_input_read (src, len, NULL), len);
481 	if (!xml_ui)
482 		goto err;
483 
484 	tdomain = go_plugin_get_textdomain (service->plugin);
485 	group_name = g_strconcat (go_plugin_get_id (service->plugin), service->id, NULL);
486 	service_ui->layout_id = gnm_app_add_extra_ui (group_name,
487 		service_ui->actions,
488 		xml_ui, tdomain);
489 	g_free (group_name);
490 	g_free (xml_ui);
491 	g_object_unref (src);
492 	service->is_active = TRUE;
493 	return;
494 
495 err:
496 	*ret_error = go_error_info_new_printf
497 		(_("Cannot read UI description from %s: %s"),
498 		 uifile,
499 		 error ? error->message : "?");
500 	g_clear_error (&error);
501 	if (src)
502 		g_object_unref (src);
503 }
504 
505 static void
plugin_service_ui_deactivate(GOPluginService * service,GOErrorInfo ** ret_error)506 plugin_service_ui_deactivate (GOPluginService *service, GOErrorInfo **ret_error)
507 {
508 	PluginServiceUI *service_ui = GNM_PLUGIN_SERVICE_UI (service);
509 
510 	GO_INIT_RET_ERROR_INFO (ret_error);
511 	gnm_app_remove_extra_ui (service_ui->layout_id);
512 	service_ui->layout_id = NULL;
513 	service->is_active = FALSE;
514 }
515 
516 static char *
plugin_service_ui_get_description(GOPluginService * service)517 plugin_service_ui_get_description (GOPluginService *service)
518 {
519 	PluginServiceUI *service_ui = GNM_PLUGIN_SERVICE_UI (service);
520 	int n_actions;
521 
522 	n_actions = g_slist_length (service_ui->actions);
523 	return g_strdup_printf (
524 		ngettext (
525 /* xgettext : %d gives the number of actions. This is input to ngettext. */
526 			"User interface with %d action",
527 			"User interface with %d actions",
528 			n_actions),
529 		n_actions);
530 }
531 
532 static void
plugin_service_ui_class_init(GObjectClass * gobject_class)533 plugin_service_ui_class_init (GObjectClass *gobject_class)
534 {
535 	GOPluginServiceClass *plugin_service_class = GO_PLUGIN_SERVICE_CLASS (gobject_class);
536 
537 	gobject_class->finalize = plugin_service_ui_finalize;
538 	plugin_service_class->read_xml = plugin_service_ui_read_xml;
539 	plugin_service_class->activate = plugin_service_ui_activate;
540 	plugin_service_class->deactivate = plugin_service_ui_deactivate;
541 	plugin_service_class->get_description = plugin_service_ui_get_description;
542 }
543 
544 GSF_CLASS (PluginServiceUI, gnm_plugin_service_ui,
545            plugin_service_ui_class_init, plugin_service_ui_init,
546            GO_TYPE_PLUGIN_SERVICE_SIMPLE)
547 
548 /****************************************************************************/
549 
550 /*
551  * PluginServiceSolver
552  */
553 typedef GOPluginServiceClass PluginServiceSolverClass;
554 struct GnmPluginServiceSolver_ {
555 	GOPluginService base;
556 
557 	GnmSolverFactory *factory;
558 
559 	GnmPluginServiceSolverCallbacks cbs;
560 };
561 
562 static GnmSolver *
cb_load_and_create(GnmSolverFactory * factory,GnmSolverParameters * param,gpointer data)563 cb_load_and_create (GnmSolverFactory *factory, GnmSolverParameters *param,
564 		    gpointer data)
565 {
566 	PluginServiceSolver *ssol =
567 		g_object_get_data (G_OBJECT (factory), "ssol");
568 	GOPluginService *service = GO_PLUGIN_SERVICE (ssol);
569 	GOErrorInfo *ignored_error = NULL;
570 	GnmSolver *res;
571 
572 	go_plugin_service_load (service, &ignored_error);
573 	if (ignored_error != NULL) {
574 		go_error_info_print (ignored_error);
575 		go_error_info_free (ignored_error);
576 		return NULL;
577 	}
578 
579 	res = ssol->cbs.creator (factory, param, data);
580 	if (res) {
581 		go_plugin_use_ref (service->plugin);
582 		g_object_set_data_full (G_OBJECT (res),
583 					"plugin-use", service->plugin,
584 					(GDestroyNotify)go_plugin_use_unref);
585 	}
586 
587 	return res;
588 }
589 
590 static gboolean
cb_load_and_functional(GnmSolverFactory * factory,WBCGtk * wbcg,gpointer data)591 cb_load_and_functional (GnmSolverFactory *factory,
592 			WBCGtk *wbcg,
593 			gpointer data)
594 {
595 	PluginServiceSolver *ssol =
596 		g_object_get_data (G_OBJECT (factory), "ssol");
597 	GOPluginService *service = GO_PLUGIN_SERVICE (ssol);
598 	GOErrorInfo *ignored_error = NULL;
599 	GnmSolverFactoryFunctional functional;
600 
601 	go_plugin_service_load (service, &ignored_error);
602 	if (ignored_error != NULL) {
603 		go_error_info_print (ignored_error);
604 		go_error_info_free (ignored_error);
605 		return FALSE;
606 	}
607 
608 	functional = ssol->cbs.functional;
609 	return (functional == NULL || functional (factory, wbcg, data));
610 }
611 
612 static void
plugin_service_solver_init(PluginServiceSolver * ssol)613 plugin_service_solver_init (PluginServiceSolver *ssol)
614 {
615 	GO_PLUGIN_SERVICE (ssol)->cbs_ptr = &ssol->cbs;
616 	ssol->factory = NULL;
617 	ssol->cbs.creator = NULL;
618 }
619 
620 static void
plugin_service_solver_finalize(GObject * obj)621 plugin_service_solver_finalize (GObject *obj)
622 {
623 	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (obj);
624 	GObjectClass *parent_class;
625 
626 	if (ssol->factory)
627 		g_object_unref (ssol->factory);
628 
629 	parent_class = g_type_class_peek (GO_TYPE_PLUGIN_SERVICE);
630 	parent_class->finalize (obj);
631 }
632 
633 static void
plugin_service_solver_read_xml(GOPluginService * service,xmlNode * tree,GOErrorInfo ** ret_error)634 plugin_service_solver_read_xml (GOPluginService *service, xmlNode *tree,
635 				GOErrorInfo **ret_error)
636 {
637 	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (service);
638 	xmlChar *s_id, *s_name, *s_type;
639 	GnmSolverModelType type = GNM_SOLVER_LP;
640 	xmlNode *information_node;
641 
642 	GO_INIT_RET_ERROR_INFO (ret_error);
643 
644 	s_type = go_xml_node_get_cstr (tree, "model_type");
645 	if (s_type && strcmp (CXML2C (s_type), "mip") == 0)
646 		type = GNM_SOLVER_LP;
647 	else if (s_type && strcmp (CXML2C (s_type), "qp") == 0)
648 		type = GNM_SOLVER_QP;
649 	else if (s_type && strcmp (CXML2C (s_type), "nlp") == 0)
650 		type = GNM_SOLVER_NLP;
651 	else {
652 		*ret_error = go_error_info_new_str (_("Invalid solver model type."));
653 		return;
654 	}
655 	xmlFree (s_type);
656 
657 	s_id = go_xml_node_get_cstr (tree, "id");
658 
659 	s_name = NULL;
660 	information_node = go_xml_get_child_by_name (tree, "information");
661 	if (information_node != NULL) {
662 		xmlNode *node =
663 			go_xml_get_child_by_name_by_lang (information_node,
664 							  "description");
665 		if (node != NULL) {
666 			s_name = xmlNodeGetContent (node);
667 		}
668 	}
669 
670 	if (!s_id || !s_name) {
671 		*ret_error = go_error_info_new_str (_("Missing fields in plugin file"));
672 	} else {
673 		ssol->factory = gnm_solver_factory_new (CXML2C (s_id),
674 							CXML2C (s_name),
675 							type,
676 							cb_load_and_create,
677 							cb_load_and_functional,
678 							NULL,
679 							NULL);
680 		g_object_set_data (G_OBJECT (ssol->factory), "ssol", ssol);
681 	}
682 	xmlFree (s_id);
683 	xmlFree (s_name);
684 	if (*ret_error)
685 		return;
686 
687 	/* More? */
688 }
689 
690 static void
plugin_service_solver_activate(GOPluginService * service,GOErrorInfo ** ret_error)691 plugin_service_solver_activate (GOPluginService *service, GOErrorInfo **ret_error)
692 {
693 	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (service);
694 
695 	GO_INIT_RET_ERROR_INFO (ret_error);
696 	gnm_solver_db_register (ssol->factory);
697 	service->is_active = TRUE;
698 }
699 
700 static void
plugin_service_solver_deactivate(GOPluginService * service,GOErrorInfo ** ret_error)701 plugin_service_solver_deactivate (GOPluginService *service, GOErrorInfo **ret_error)
702 {
703 	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (service);
704 
705 	GO_INIT_RET_ERROR_INFO (ret_error);
706 	gnm_solver_db_unregister (ssol->factory);
707 	service->is_active = FALSE;
708 }
709 
710 static char *
plugin_service_solver_get_description(GOPluginService * service)711 plugin_service_solver_get_description (GOPluginService *service)
712 {
713 	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (service);
714 	return g_strdup_printf (_("Solver Algorithm %s"),
715 				ssol->factory->name);
716 }
717 
718 static void
plugin_service_solver_class_init(GObjectClass * gobject_class)719 plugin_service_solver_class_init (GObjectClass *gobject_class)
720 {
721 	GOPluginServiceClass *plugin_service_class = GO_PLUGIN_SERVICE_CLASS (gobject_class);
722 
723 	gobject_class->finalize = plugin_service_solver_finalize;
724 	plugin_service_class->read_xml = plugin_service_solver_read_xml;
725 	plugin_service_class->activate = plugin_service_solver_activate;
726 	plugin_service_class->deactivate = plugin_service_solver_deactivate;
727 	plugin_service_class->get_description = plugin_service_solver_get_description;
728 }
729 
730 GSF_CLASS (PluginServiceSolver, gnm_plugin_service_solver,
731            plugin_service_solver_class_init, plugin_service_solver_init,
732            GO_TYPE_PLUGIN_SERVICE)
733 
734 /****************************************************************************/
735 
736 
737 typedef GOPluginLoaderModule	  GnmPluginLoaderModule;
738 typedef GOPluginLoaderModuleClass GnmPluginLoaderModuleClass;
739 
740 /*
741  * Service - function_group
742  */
743 typedef struct {
744 	GnmFuncDescriptor *module_fn_info_array;
745 	GHashTable *function_indices;
746 } ServiceLoaderDataFunctionGroup;
747 
748 static void
function_group_loader_data_free(gpointer data)749 function_group_loader_data_free (gpointer data)
750 {
751 	ServiceLoaderDataFunctionGroup *ld = data;
752 
753 	g_hash_table_destroy (ld->function_indices);
754 	g_free (ld);
755 }
756 
757 static void
gnm_plugin_loader_module_func_load_stub(GOPluginService * service,GnmFunc * func)758 gnm_plugin_loader_module_func_load_stub (GOPluginService *service,
759 					 GnmFunc *func)
760 {
761 	ServiceLoaderDataFunctionGroup *loader_data;
762 	gpointer index_ptr;
763 	GnmFuncDescriptor *desc;
764 	const char *name;
765 
766 	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (service));
767 	g_return_if_fail (GNM_IS_FUNC (func));
768 
769 	name = gnm_func_get_name (func, FALSE);
770 	loader_data = g_object_get_data (G_OBJECT (service), "loader_data");
771 	if (!g_hash_table_lookup_extended (loader_data->function_indices,
772 					   (gpointer)name,
773 					   NULL, &index_ptr))
774 		return; // Failed
775 
776 	desc = loader_data->module_fn_info_array + GPOINTER_TO_INT (index_ptr);
777 	gnm_func_set_from_desc (func, desc);
778 }
779 
780 static void
gnm_plugin_loader_module_load_service_function_group(GOPluginLoader * loader,GOPluginService * service,GOErrorInfo ** ret_error)781 gnm_plugin_loader_module_load_service_function_group (GOPluginLoader  *loader,
782 						      GOPluginService *service,
783 						      GOErrorInfo **ret_error)
784 {
785 	GnmPluginLoaderModule *loader_module = GNM_PLUGIN_LOADER_MODULE (loader);
786 	gchar *fn_info_array_name;
787 	GnmFuncDescriptor *module_fn_info_array = NULL;
788 
789 	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (service));
790 
791 	GO_INIT_RET_ERROR_INFO (ret_error);
792 	fn_info_array_name = g_strconcat (
793 		go_plugin_service_get_id (service), "_functions", NULL);
794 	g_module_symbol (loader_module->handle, fn_info_array_name, (gpointer) &module_fn_info_array);
795 	if (module_fn_info_array != NULL) {
796 		GnmPluginServiceFunctionGroupCallbacks *cbs;
797 		ServiceLoaderDataFunctionGroup *loader_data;
798 		gint i;
799 
800 		cbs = go_plugin_service_get_cbs (service);
801 		cbs->load_stub = &gnm_plugin_loader_module_func_load_stub;
802 
803 		loader_data = g_new (ServiceLoaderDataFunctionGroup, 1);
804 		loader_data->module_fn_info_array = module_fn_info_array;
805 		loader_data->function_indices = g_hash_table_new (&g_str_hash, &g_str_equal);
806 		for (i = 0; module_fn_info_array[i].name != NULL; i++) {
807 			g_hash_table_insert (loader_data->function_indices,
808 			                     (gpointer) module_fn_info_array[i].name,
809 			                     GINT_TO_POINTER (i));
810 		}
811 		g_object_set_data_full (
812 			G_OBJECT (service), "loader_data", loader_data, function_group_loader_data_free);
813 	} else {
814 		*ret_error = go_error_info_new_printf (
815 		             _("Module file \"%s\" has invalid format."),
816 		             loader_module->module_file_name);
817 		go_error_info_add_details (*ret_error,
818 					go_error_info_new_printf (
819 					_("File doesn't contain \"%s\" array."),
820 					fn_info_array_name));
821 	}
822 	g_free (fn_info_array_name);
823 }
824 
825 /*
826  * Service - ui
827  */
828 
829 typedef struct {
830 	GnmModulePluginUIActions *module_ui_actions_array;
831 	GHashTable *ui_actions_hash;
832 } ServiceLoaderDataUI;
833 
834 static void
ui_loader_data_free(gpointer data)835 ui_loader_data_free (gpointer data)
836 {
837 	ServiceLoaderDataUI *ld = data;
838 
839 	g_hash_table_destroy (ld->ui_actions_hash);
840 	g_free (ld);
841 }
842 
843 static void
gnm_plugin_loader_module_func_exec_action(GOPluginService * service,GnmAction const * action,WorkbookControl * wbc,GOErrorInfo ** ret_error)844 gnm_plugin_loader_module_func_exec_action (GOPluginService *service,
845 					   GnmAction const *action,
846 					   WorkbookControl *wbc,
847 					   GOErrorInfo **ret_error)
848 {
849 	ServiceLoaderDataUI *loader_data;
850 	gpointer action_index_ptr;
851 	int action_index;
852 
853 	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_UI (service));
854 
855 	GO_INIT_RET_ERROR_INFO (ret_error);
856 	loader_data = g_object_get_data (G_OBJECT (service), "loader_data");
857 	if (!g_hash_table_lookup_extended (loader_data->ui_actions_hash, action->id,
858 	                                   NULL, &action_index_ptr)) {
859 		*ret_error = go_error_info_new_printf (_("Unknown action: %s"), action->id);
860 		return;
861 	}
862 	action_index = GPOINTER_TO_INT (action_index_ptr);
863 	if (NULL != loader_data->module_ui_actions_array [action_index].handler)
864 		(*loader_data->module_ui_actions_array [action_index].handler) (action, wbc);
865 }
866 
867 static void
gnm_plugin_loader_module_load_service_ui(GOPluginLoader * loader,GOPluginService * service,GOErrorInfo ** ret_error)868 gnm_plugin_loader_module_load_service_ui (GOPluginLoader *loader,
869 					  GOPluginService *service,
870 					  GOErrorInfo **ret_error)
871 {
872 	GnmPluginLoaderModule *loader_module = GNM_PLUGIN_LOADER_MODULE (loader);
873 	char *ui_actions_array_name;
874 	GnmModulePluginUIActions *module_ui_actions_array = NULL;
875 	GnmPluginServiceUICallbacks *cbs;
876 	ServiceLoaderDataUI *loader_data;
877 	gint i;
878 
879 	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_UI (service));
880 
881 	GO_INIT_RET_ERROR_INFO (ret_error);
882 	ui_actions_array_name = g_strconcat (
883 		go_plugin_service_get_id (service), "_ui_actions", NULL);
884 	g_module_symbol (loader_module->handle, ui_actions_array_name, (gpointer) &module_ui_actions_array);
885 	if (module_ui_actions_array == NULL) {
886 		*ret_error = go_error_info_new_printf (
887 			_("Module file \"%s\" has invalid format."),
888 			loader_module->module_file_name);
889 		go_error_info_add_details (*ret_error, go_error_info_new_printf (
890 			_("File doesn't contain \"%s\" array."), ui_actions_array_name));
891 		g_free (ui_actions_array_name);
892 		return;
893 	}
894 	g_free (ui_actions_array_name);
895 
896 	cbs = go_plugin_service_get_cbs (service);
897 	cbs->plugin_func_exec_action = gnm_plugin_loader_module_func_exec_action;
898 
899 	loader_data = g_new (ServiceLoaderDataUI, 1);
900 	loader_data->module_ui_actions_array = module_ui_actions_array;
901 	loader_data->ui_actions_hash = g_hash_table_new (g_str_hash, g_str_equal);
902 	for (i = 0; module_ui_actions_array[i].name != NULL; i++)
903 		g_hash_table_insert (loader_data->ui_actions_hash,
904 			(gpointer) module_ui_actions_array[i].name,
905 			GINT_TO_POINTER (i));
906 	g_object_set_data_full (G_OBJECT (service),
907 		"loader_data", loader_data, ui_loader_data_free);
908 }
909 
910 static void
gnm_plugin_loader_module_load_service_solver(GOPluginLoader * loader,GOPluginService * service,GOErrorInfo ** ret_error)911 gnm_plugin_loader_module_load_service_solver (GOPluginLoader *loader,
912 					      GOPluginService *service,
913 					      GOErrorInfo **ret_error)
914 {
915 	GnmPluginLoaderModule *loader_module =
916 		GNM_PLUGIN_LOADER_MODULE (loader);
917 	GnmPluginServiceSolverCallbacks *cbs;
918 	char *symname;
919 	GnmSolverCreator creator;
920 	GnmSolverFactoryFunctional functional;
921 
922 	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_SOLVER (service));
923 
924 	GO_INIT_RET_ERROR_INFO (ret_error);
925 
926 	symname = g_strconcat (go_plugin_service_get_id (service),
927 			       "_solver_factory",
928 			       NULL);
929 	g_module_symbol (loader_module->handle, symname, (gpointer)&creator);
930 	g_free (symname);
931 	if (!creator) {
932 		*ret_error = go_error_info_new_printf (
933 			_("Module file \"%s\" has invalid format."),
934 			loader_module->module_file_name);
935 		return;
936 	}
937 
938 	symname = g_strconcat (go_plugin_service_get_id (service),
939 			       "_solver_factory_functional",
940 			       NULL);
941 	g_module_symbol (loader_module->handle, symname, (gpointer)&functional);
942 	g_free (symname);
943 
944 	cbs = go_plugin_service_get_cbs (service);
945 	cbs->creator = creator;
946 	cbs->functional = functional;
947 }
948 
949 static gboolean
gplm_service_load(GOPluginLoader * l,GOPluginService * s,GOErrorInfo ** err)950 gplm_service_load (GOPluginLoader *l, GOPluginService *s, GOErrorInfo **err)
951 {
952 	if (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (s))
953 		gnm_plugin_loader_module_load_service_function_group (l, s, err);
954 	else if (GNM_IS_PLUGIN_SERVICE_UI (s))
955 		gnm_plugin_loader_module_load_service_ui (l, s, err);
956 	else if (GNM_IS_PLUGIN_SERVICE_SOLVER (s))
957 		gnm_plugin_loader_module_load_service_solver (l, s, err);
958 	else
959 		return FALSE;
960 	return TRUE;
961 }
962 
963 static gboolean
gplm_service_unload(GOPluginLoader * l,GOPluginService * s,GOErrorInfo ** err)964 gplm_service_unload (GOPluginLoader *l, GOPluginService *s, GOErrorInfo **err)
965 {
966 	if (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (s)) {
967 		GnmPluginServiceFunctionGroupCallbacks *cbs = go_plugin_service_get_cbs (s);
968 		cbs->load_stub = NULL;
969 	} else if (GNM_IS_PLUGIN_SERVICE_UI (s)) {
970 		GnmPluginServiceUICallbacks *cbs = go_plugin_service_get_cbs (s);
971 		cbs->plugin_func_exec_action = NULL;
972 	} else if (GNM_IS_PLUGIN_SERVICE_SOLVER (s)) {
973 		GnmPluginServiceSolverCallbacks *cbs =
974 			go_plugin_service_get_cbs (s);
975 		cbs->creator = NULL;
976 		cbs->functional = NULL;
977 	} else
978 		return FALSE;
979 	return TRUE;
980 }
981 
982 static void
go_plugin_loader_module_iface_init(GOPluginLoaderClass * iface)983 go_plugin_loader_module_iface_init (GOPluginLoaderClass *iface)
984 {
985 	iface->service_load   = gplm_service_load;
986 	iface->service_unload = gplm_service_unload;
987 }
988 
989 GSF_CLASS_FULL (GnmPluginLoaderModule, gnm_plugin_loader_module,
990            NULL, NULL, NULL, NULL,
991            NULL, GO_TYPE_PLUGIN_LOADER_MODULE, 0,
992 	   GSF_INTERFACE (go_plugin_loader_module_iface_init, GO_TYPE_PLUGIN_LOADER))
993 
994 /****************************************************************************/
995 
996 /**
997  * gnm_plugins_service_init: (skip)
998  */
999 void
gnm_plugins_service_init(void)1000 gnm_plugins_service_init (void)
1001 {
1002 	go_plugin_service_define ("function_group",
1003 		&gnm_plugin_service_function_group_get_type);
1004 	go_plugin_service_define ("ui",
1005 		&gnm_plugin_service_ui_get_type);
1006 	go_plugin_service_define ("solver",
1007 		&gnm_plugin_service_solver_get_type);
1008 	go_plugin_loader_module_register_version ("gnumeric", GNM_VERSION_FULL);
1009 }
1010 
1011 
1012 void
gnm_plugins_init(GOCmdContext * context)1013 gnm_plugins_init (GOCmdContext *context)
1014 {
1015 	char const *env_var;
1016 	GSList *dir_list = go_slist_create (
1017 		g_build_filename (gnm_sys_lib_dir (), PLUGIN_SUBDIR, NULL),
1018 	        g_strdup (gnm_sys_extern_plugin_dir ()),
1019 		(gnm_usr_dir (TRUE) == NULL ? NULL :
1020 			g_build_filename (gnm_usr_dir (TRUE), PLUGIN_SUBDIR, NULL)),
1021 		NULL);
1022 	dir_list = g_slist_concat (dir_list,
1023 				   go_string_slist_copy (gnm_conf_get_plugins_extra_dirs ()));
1024 
1025 	env_var = g_getenv ("GNUMERIC_PLUGIN_PATH");
1026 	if (env_var != NULL)
1027 		GO_SLIST_CONCAT (dir_list, go_strsplit_to_slist (env_var, G_SEARCHPATH_SEPARATOR));
1028 
1029 	go_plugins_init (GO_CMD_CONTEXT (context),
1030 			 gnm_conf_get_plugins_file_states (),
1031 			 gnm_conf_get_plugins_active (),
1032 			 dir_list,
1033 			 gnm_conf_get_plugins_activate_newplugins (),
1034 			 gnm_plugin_loader_module_get_type ());
1035 }
1036