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