gnm-plugin.c 29.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * go-plugin-service.c: Plugin services - reading XML info, activating, etc.
 *                   (everything independent of plugin loading method)
 *
 * Author: Zbigniew Chyla (cyba@gnome.pl)
 */

#include <gnumeric-config.h>
#include "gutils.h"
11
#include <tools/gnm-solver.h>
12 13
#include "func.h"
#include "gnm-plugin.h"
14
#include "gnumeric-conf.h"
15 16
#include "application.h"

17
#include <goffice/goffice.h>
18
#include <gsf/gsf-impl-utils.h>
19 20
#include <gsf/gsf-input-stdio.h>
#include <gsf/gsf-input-memory.h>
21
#include <glib/gi18n-lib.h>
22 23
#include <string.h>

Morten Welinder's avatar
Morten Welinder committed
24
#define CXML2C(s) ((char const *)(s))
25
#define CC2XML(s) ((xmlChar const *)(s))
Morten Welinder's avatar
Morten Welinder committed
26

27 28 29 30 31 32 33 34
static char *
xml2c (xmlChar *src)
{
	char *dst = g_strdup (CXML2C (src));
	xmlFree (src);
	return dst;
}

Morten Welinder's avatar
Morten Welinder committed
35 36
typedef GOPluginServiceSimpleClass GnmPluginServiceFunctionGroupClass;
struct GnmPluginServiceFunctionGroup_ {
37
	GOPluginServiceSimple	base;
38 39 40 41

	gchar *category_name, *translated_category_name;
	GSList *function_name_list;

42
	GnmFuncGroup *func_group;
Morten Welinder's avatar
Morten Welinder committed
43
	GnmPluginServiceFunctionGroupCallbacks cbs;
44
	char *tdomain;
45 46 47 48 49
};

static void
plugin_service_function_group_finalize (GObject *obj)
{
Morten Welinder's avatar
Morten Welinder committed
50
	GnmPluginServiceFunctionGroup *sfg = GNM_PLUGIN_SERVICE_FUNCTION_GROUP (obj);
51 52
	GObjectClass *parent_class;

53 54 55 56 57 58
	g_free (sfg->category_name);
	sfg->category_name = NULL;

	g_free (sfg->translated_category_name);
	sfg->translated_category_name = NULL;

59
	g_slist_free_full (sfg->function_name_list, g_free);
60 61
	sfg->function_name_list = NULL;

62 63
	g_free (sfg->tdomain);
	sfg->tdomain = NULL;
64

65
	parent_class = g_type_class_peek (GO_TYPE_PLUGIN_SERVICE);
66 67 68 69
	parent_class->finalize (obj);
}

static void
70
plugin_service_function_group_read_xml (GOPluginService *service, xmlNode *tree, GOErrorInfo **ret_error)
71 72 73 74
{
	xmlNode *category_node, *translated_category_node, *functions_node;
	gchar *category_name, *translated_category_name;
	GSList *function_name_list = NULL;
75
	gchar *tdomain = NULL;
76

Jody Goldberg's avatar
Jody Goldberg committed
77
	GO_INIT_RET_ERROR_INFO (ret_error);
78
	category_node = go_xml_get_child_by_name_no_lang (tree, "category");
79 80 81 82
	category_name = category_node
		? xml2c (xmlNodeGetContent (category_node))
		: NULL;

83
	translated_category_node = go_xml_get_child_by_name_by_lang (tree, "category");
84
	if (translated_category_node != NULL) {
85
		xmlChar *lang;
86

87
		lang = go_xml_node_get_cstr (translated_category_node, "lang");
88
		if (lang != NULL) {
89 90 91
			translated_category_name =
				xml2c (xmlNodeGetContent (translated_category_node));
			xmlFree (lang);
92 93 94 95 96 97
		} else {
			translated_category_name = NULL;
		}
	} else {
		translated_category_name = NULL;
	}
98
	functions_node = go_xml_get_child_by_name (tree, CC2XML ("functions"));
99 100 101
	if (functions_node != NULL) {
		xmlNode *node;

102
		tdomain = xml2c (go_xml_node_get_cstr (functions_node, "textdomain"));
103

104 105 106
		for (node = functions_node->xmlChildrenNode; node != NULL; node = node->next) {
			gchar *func_name;

107
			if (strcmp (CXML2C (node->name), "function") != 0)
108
				continue;
109 110 111 112 113

			func_name = xml2c (go_xml_node_get_cstr (node, "name"));
			if (!func_name)
				continue;

Jody Goldberg's avatar
Jody Goldberg committed
114
			GO_SLIST_PREPEND (function_name_list, func_name);
115
		}
Jody Goldberg's avatar
Jody Goldberg committed
116
		GO_SLIST_REVERSE (function_name_list);
117 118
	}
	if (category_name != NULL && function_name_list != NULL) {
Morten Welinder's avatar
Morten Welinder committed
119
		GnmPluginServiceFunctionGroup *sfg = GNM_PLUGIN_SERVICE_FUNCTION_GROUP (service);
120

121 122 123
		sfg->category_name = category_name;
		sfg->translated_category_name = translated_category_name;
		sfg->function_name_list = function_name_list;
124
		sfg->tdomain = tdomain;
125 126 127 128
	} else {
		GSList *error_list = NULL;

		if (category_name == NULL) {
129
			GO_SLIST_PREPEND (error_list, go_error_info_new_str (
130 131 132
				_("Missing function category name.")));
		}
		if (function_name_list == NULL) {
133
			GO_SLIST_PREPEND (error_list, go_error_info_new_str (
134 135
				_("Function group is empty.")));
		}
Jody Goldberg's avatar
Jody Goldberg committed
136
		GO_SLIST_REVERSE (error_list);
137
		*ret_error = go_error_info_new_from_error_list (error_list);
138 139 140

		g_free (category_name);
		g_free (translated_category_name);
141
		g_slist_free_full (function_name_list, g_free);
142

143
		g_free (tdomain);
144 145 146 147 148 149 150
	}
}

static gboolean
plugin_service_function_group_func_desc_load (GnmFunc const *fn_def,
					      GnmFuncDescriptor *res)
{
151
	GOPluginService	*service = gnm_func_get_user_data (fn_def);
Morten Welinder's avatar
Morten Welinder committed
152
	GnmPluginServiceFunctionGroup *sfg = GNM_PLUGIN_SERVICE_FUNCTION_GROUP (service);
153
	GOErrorInfo *error = NULL;
154 155 156

	g_return_val_if_fail (fn_def != NULL, FALSE);

157
	go_plugin_service_load (service, &error);
158
	if (error != NULL) {
159 160
		go_error_info_print (error);
		go_error_info_free (error);
161 162
		return FALSE;
	}
163
	if (NULL == sfg->cbs.func_desc_load) {
164 165 166
                error = go_error_info_new_printf (_("No func_desc_load method.\n"));
		go_error_info_print (error);
		go_error_info_free (error);
167 168
		return FALSE;
	}
169
	return sfg->cbs.func_desc_load (service,
170
					gnm_func_get_name (fn_def, FALSE),
171
					res);
172 173 174
}

static void
Morten Welinder's avatar
Morten Welinder committed
175
plugin_service_function_group_func_ref_notify (GnmFunc *fn_def, int refcount)
176 177 178 179
{
	GOPluginService *service;

	service = gnm_func_get_user_data (fn_def);
Morten Welinder's avatar
Morten Welinder committed
180
	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (service));
181
	if (refcount == 0) {
Jody Goldberg's avatar
Jody Goldberg committed
182
		go_plugin_use_unref (service->plugin);
183
	} else {
Jody Goldberg's avatar
Jody Goldberg committed
184
		go_plugin_use_ref (service->plugin);
185 186 187
	}
}

188 189 190 191 192 193 194 195 196 197 198
static void
delayed_ref_notify (GOPlugin *plugin, GnmFunc *fd)
{
	g_signal_handlers_disconnect_by_func (plugin,
					      G_CALLBACK (delayed_ref_notify),
					      fd);

	/* We cannot do this until after the plugin has been activated.  */
	plugin_service_function_group_func_ref_notify (fd, 1);
}

199
static void
200
plugin_service_function_group_activate (GOPluginService *service, GOErrorInfo **ret_error)
201
{
Morten Welinder's avatar
Morten Welinder committed
202
	GnmPluginServiceFunctionGroup *sfg =
203
		GNM_PLUGIN_SERVICE_FUNCTION_GROUP (service);
204

Jody Goldberg's avatar
Jody Goldberg committed
205
	GO_INIT_RET_ERROR_INFO (ret_error);
206 207
	sfg->func_group = gnm_func_group_fetch (sfg->category_name,
						sfg->translated_category_name);
208 209 210 211 212 213 214 215 216 217 218 219
	if (gnm_debug_flag ("plugin-func"))
		g_printerr ("Activating group %s\n", sfg->category_name);
	GO_SLIST_FOREACH
		(sfg->function_name_list, char, fname,
		 GnmFunc *fd;

		 fd = gnm_func_lookup (fname, NULL);
		 if (fd) {
#if 0
			 g_printerr ("Reusing placeholder for %s\n", fname);
#endif
		 } else {
220
			 fd = gnm_func_add_placeholder (NULL, fname, "?");
221 222 223 224 225
		 }
		 if (fd->flags & GNM_FUNC_IS_PLACEHOLDER) {
			 gnm_func_set_user_data (fd, service);
			 gnm_func_upgrade_placeholder
				 (fd, sfg->func_group,
226
				  sfg->tdomain,
227 228 229 230 231 232 233 234 235 236
				  plugin_service_function_group_func_desc_load,
				  plugin_service_function_group_func_ref_notify);
			 if (fd->usage_count > 0)
				 g_signal_connect (go_plugin_service_get_plugin (service),
						   "state_changed",
						   G_CALLBACK (delayed_ref_notify),
						   fd);
		 } else {
			 g_warning ("Multiple definitions of function %s -- this cannot be good!", fname);
		 }
237 238 239 240 241
	);
	service->is_active = TRUE;
}

static void
242
plugin_service_function_group_deactivate (GOPluginService *service, GOErrorInfo **ret_error)
243
{
Morten Welinder's avatar
Morten Welinder committed
244
	GnmPluginServiceFunctionGroup *sfg = GNM_PLUGIN_SERVICE_FUNCTION_GROUP (service);
245

246 247 248
	if (gnm_debug_flag ("plugin-func"))
		g_printerr ("Deactivating group %s\n", sfg->category_name);

Jody Goldberg's avatar
Jody Goldberg committed
249
	GO_INIT_RET_ERROR_INFO (ret_error);
250
	GO_SLIST_FOREACH (sfg->function_name_list, char, fname,
251 252 253 254 255 256 257 258
		gnm_func_free (gnm_func_lookup (fname, NULL));
	);
	service->is_active = FALSE;
}

static char *
plugin_service_function_group_get_description (GOPluginService *service)
{
Morten Welinder's avatar
Morten Welinder committed
259
	GnmPluginServiceFunctionGroup *sfg = GNM_PLUGIN_SERVICE_FUNCTION_GROUP (service);
260 261 262
	int n_functions;
	char const *category_name;

263 264 265 266
	n_functions = g_slist_length (sfg->function_name_list);
	category_name = sfg->translated_category_name != NULL
		? sfg->translated_category_name
		: sfg->category_name;
267 268

	return g_strdup_printf (ngettext (
269 270
			"%d function in category \"%s\"",
			"Group of %d functions in category \"%s\"",
271 272 273 274 275
			n_functions),
		n_functions, category_name);
}

static void
Morten Welinder's avatar
Morten Welinder committed
276
plugin_service_function_group_init (GnmPluginServiceFunctionGroup *s)
277
{
Jody Goldberg's avatar
Jody Goldberg committed
278
	GO_PLUGIN_SERVICE (s)->cbs_ptr = &s->cbs;
279 280 281 282
	s->category_name = NULL;
	s->translated_category_name = NULL;
	s->function_name_list = NULL;
	s->func_group = NULL;
283
	s->tdomain = NULL;
284
}
285

286 287 288
static void
plugin_service_function_group_class_init (GObjectClass *gobject_class)
{
Jean Bréfort's avatar
Jean Bréfort committed
289
	GOPluginServiceClass *plugin_service_class = GO_PLUGIN_SERVICE_CLASS (gobject_class);
290 291 292 293 294 295 296 297

	gobject_class->finalize		= plugin_service_function_group_finalize;
	plugin_service_class->read_xml	= plugin_service_function_group_read_xml;
	plugin_service_class->activate	= plugin_service_function_group_activate;
	plugin_service_class->deactivate = plugin_service_function_group_deactivate;
	plugin_service_class->get_description = plugin_service_function_group_get_description;
}

Morten Welinder's avatar
Morten Welinder committed
298
GSF_CLASS (GnmPluginServiceFunctionGroup, gnm_plugin_service_function_group,
299
           plugin_service_function_group_class_init, plugin_service_function_group_init,
300
           GO_TYPE_PLUGIN_SERVICE_SIMPLE)
301 302 303 304 305 306

/****************************************************************************/

/*
 * PluginServiceUI
 */
307
typedef GOPluginServiceSimpleClass PluginServiceUIClass;
Morten Welinder's avatar
Morten Welinder committed
308
struct GnmPluginServiceUI_ {
309
	GOPluginServiceSimple base;
310 311 312 313 314

	char *file_name;
	GSList *actions;

	gpointer layout_id;
Morten Welinder's avatar
Morten Welinder committed
315
	GnmPluginServiceUICallbacks cbs;
316 317 318 319 320
};

static void
plugin_service_ui_init (PluginServiceUI *s)
{
Jody Goldberg's avatar
Jody Goldberg committed
321
	GO_PLUGIN_SERVICE (s)->cbs_ptr = &s->cbs;
322 323 324 325 326 327 328 329 330 331 332 333 334 335
	s->file_name = NULL;
	s->actions = NULL;
	s->layout_id = NULL;
	s->cbs.plugin_func_exec_action = NULL;
}

static void
plugin_service_ui_finalize (GObject *obj)
{
	PluginServiceUI *service_ui = GNM_PLUGIN_SERVICE_UI (obj);
	GObjectClass *parent_class;

	g_free (service_ui->file_name);
	service_ui->file_name = NULL;
336
	g_slist_free_full (service_ui->actions, (GDestroyNotify)gnm_action_free);
337 338
	service_ui->actions = NULL;

339
	parent_class = g_type_class_peek (GO_TYPE_PLUGIN_SERVICE);
340 341 342 343 344 345
	parent_class->finalize (obj);
}

static void
cb_ui_service_activate (GnmAction const *action, WorkbookControl *wbc, GOPluginService *service)
{
346
	GOErrorInfo *load_error = NULL;
347

348
	go_plugin_service_load (service, &load_error);
349 350
	if (load_error == NULL) {
		PluginServiceUI *service_ui = GNM_PLUGIN_SERVICE_UI (service);
351
		GOErrorInfo *ignored_error = NULL;
352 353 354 355 356

		g_return_if_fail (service_ui->cbs.plugin_func_exec_action != NULL);
		service_ui->cbs.plugin_func_exec_action (
			service, action, wbc, &ignored_error);
		if (ignored_error != NULL) {
357 358
			go_error_info_print (ignored_error);
			go_error_info_free (ignored_error);
359 360
		}
	} else {
361 362
		go_error_info_print (load_error);
		go_error_info_free (load_error);
363 364 365 366
	}
}

static void
367
plugin_service_ui_read_xml (GOPluginService *service, xmlNode *tree, GOErrorInfo **ret_error)
368 369
{
	PluginServiceUI *service_ui = GNM_PLUGIN_SERVICE_UI (service);
Morten Welinder's avatar
Morten Welinder committed
370
	xmlChar *file_name;
371 372 373
	xmlNode *verbs_node;
	GSList *actions = NULL;

Jody Goldberg's avatar
Jody Goldberg committed
374
	GO_INIT_RET_ERROR_INFO (ret_error);
375
	file_name = xml2c (go_xml_node_get_cstr (tree, "file"));
376
	if (file_name == NULL) {
377
		*ret_error = go_error_info_new_str (
378 379 380
		             _("Missing file name."));
		return;
	}
381
	verbs_node = go_xml_get_child_by_name (tree, "actions");
382
	if (verbs_node != NULL) {
383 384 385
		xmlNode *ptr, *label_node;
		xmlChar *name, *icon;
		gchar *label;
386 387 388 389 390
		gboolean always_available;
		GnmAction *action;

		for (ptr = verbs_node->xmlChildrenNode; ptr != NULL; ptr = ptr->next) {
			if (xmlIsBlankNode (ptr) || ptr->name == NULL ||
Morten Welinder's avatar
Morten Welinder committed
391
			    strcmp (CXML2C (ptr->name), "action"))
392
				continue;
393
			name  = go_xml_node_get_cstr (ptr, "name");
394
/*			label = go_xml_node_get_cstr (ptr, "label");*/
395 396
/*****************************************************************************************/
			label_node = go_xml_get_child_by_name_no_lang (ptr, "label");
397 398 399 400
			label = label_node
				? xml2c (xmlNodeGetContent (label_node))
				: NULL;

401 402 403 404 405 406
			label_node = go_xml_get_child_by_name_by_lang (ptr, "label");
			if (label_node != NULL) {
				gchar *lang;

				lang = go_xml_node_get_cstr (label_node, "lang");
				if (lang != NULL) {
407 408
					label = xml2c (xmlNodeGetContent (label_node));
					xmlFree (lang);
409 410 411
				}
			}
/*****************************************************************************************/
412 413
			icon  = go_xml_node_get_cstr (ptr, "icon");
			if (!go_xml_node_get_bool (ptr, "always_available", &always_available))
414 415 416 417
				always_available = FALSE;
			action = gnm_action_new (name, label, icon, always_available,
				(GnmActionHandler) cb_ui_service_activate);
			if (NULL != name) xmlFree (name);
418
			g_free (label);
419
			if (NULL != icon) xmlFree (icon);
420
			if (NULL != action)
Jody Goldberg's avatar
Jody Goldberg committed
421
				GO_SLIST_PREPEND (actions, action);
422 423
		}
	}
Jody Goldberg's avatar
Jody Goldberg committed
424
	GO_SLIST_REVERSE (actions);
425 426 427 428 429 430

	service_ui->file_name = file_name;
	service_ui->actions = actions;
}

static void
431
plugin_service_ui_activate (GOPluginService *service, GOErrorInfo **ret_error)
432 433
{
	PluginServiceUI *service_ui = GNM_PLUGIN_SERVICE_UI (service);
434
	const char *uifile = service_ui->file_name;
435
	char *xml_ui, *group_name;
436
	char const *tdomain;
437 438 439
	GError *error = NULL;
	GsfInput *src;
	size_t len;
440

Jody Goldberg's avatar
Jody Goldberg committed
441
	GO_INIT_RET_ERROR_INFO (ret_error);
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459

	if (strncmp (uifile, "res:", 4) == 0) {
		size_t len;
		gconstpointer data = go_rsm_lookup (uifile + 4, &len);
		src = data
			? gsf_input_memory_new (data, len, FALSE)
			: NULL;
	} else if (strncmp (uifile, "data:", 5) == 0) {
		const char *data = uifile + 5;
		src = gsf_input_memory_new (data, strlen (data), FALSE);
	} else {
		char *full_file_name = g_path_is_absolute (uifile)
			? g_strdup (uifile)
			: g_build_filename
			(go_plugin_get_dir_name (service->plugin),
			 uifile,
			 NULL);
		src = gsf_input_stdio_new (full_file_name, &error);
460 461
		g_free (full_file_name);
	}
462 463 464 465 466 467 468 469
	if (!src)
		goto err;

	src = gsf_input_uncompress (src);
	len = gsf_input_size (src);
	xml_ui = g_strndup (gsf_input_read (src, len, NULL), len);
	if (!xml_ui)
		goto err;
470

471
	tdomain = go_plugin_get_textdomain (service->plugin);
472 473
	group_name = g_strconcat (go_plugin_get_id (service->plugin), service->id, NULL);
	service_ui->layout_id = gnm_app_add_extra_ui (group_name,
474
		service_ui->actions,
475
		xml_ui, tdomain, service);
476
	g_free (group_name);
Morten Welinder's avatar
Morten Welinder committed
477
	g_free (xml_ui);
478
	g_object_unref (src);
479
	service->is_active = TRUE;
480 481 482 483 484 485 486 487 488 489
	return;

err:
	*ret_error = go_error_info_new_printf
		(_("Cannot read UI description from %s: %s"),
		 uifile,
		 error ? error->message : "?");
	g_clear_error (&error);
	if (src)
		g_object_unref (src);
490 491 492
}

static void
493
plugin_service_ui_deactivate (GOPluginService *service, GOErrorInfo **ret_error)
494 495 496
{
	PluginServiceUI *service_ui = GNM_PLUGIN_SERVICE_UI (service);

Jody Goldberg's avatar
Jody Goldberg committed
497
	GO_INIT_RET_ERROR_INFO (ret_error);
498 499 500 501 502 503 504 505 506 507 508 509 510 511
	gnm_app_remove_extra_ui (service_ui->layout_id);
	service_ui->layout_id = NULL;
	service->is_active = FALSE;
}

static char *
plugin_service_ui_get_description (GOPluginService *service)
{
	PluginServiceUI *service_ui = GNM_PLUGIN_SERVICE_UI (service);
	int n_actions;

	n_actions = g_slist_length (service_ui->actions);
	return g_strdup_printf (
		ngettext (
512
/* xgettext : %d gives the number of actions. This is input to ngettext. */
513 514
			"User interface with %d action",
			"User interface with %d actions",
515 516 517 518 519 520 521
			n_actions),
		n_actions);
}

static void
plugin_service_ui_class_init (GObjectClass *gobject_class)
{
Jean Bréfort's avatar
Jean Bréfort committed
522
	GOPluginServiceClass *plugin_service_class = GO_PLUGIN_SERVICE_CLASS (gobject_class);
523 524 525 526 527 528 529 530

	gobject_class->finalize = plugin_service_ui_finalize;
	plugin_service_class->read_xml = plugin_service_ui_read_xml;
	plugin_service_class->activate = plugin_service_ui_activate;
	plugin_service_class->deactivate = plugin_service_ui_deactivate;
	plugin_service_class->get_description = plugin_service_ui_get_description;
}

Morten Welinder's avatar
Morten Welinder committed
531
GSF_CLASS (PluginServiceUI, gnm_plugin_service_ui,
532
           plugin_service_ui_class_init, plugin_service_ui_init,
533 534 535 536 537 538 539 540
           GO_TYPE_PLUGIN_SERVICE_SIMPLE)

/****************************************************************************/

/*
 * PluginServiceSolver
 */
typedef GOPluginServiceClass PluginServiceSolverClass;
Morten Welinder's avatar
Morten Welinder committed
541
struct GnmPluginServiceSolver_ {
542 543 544 545
	GOPluginService base;

	GnmSolverFactory *factory;

Morten Welinder's avatar
Morten Welinder committed
546
	GnmPluginServiceSolverCallbacks cbs;
547 548 549
};

static GnmSolver *
550
cb_load_and_create (GnmSolverFactory *factory, GnmSolverParameters *param)
551 552 553
{
	PluginServiceSolver *ssol =
		g_object_get_data (G_OBJECT (factory), "ssol");
554
	GOPluginService *service = GO_PLUGIN_SERVICE (ssol);
555
	GOErrorInfo *ignored_error = NULL;
556
	GnmSolver *res;
557

558
	go_plugin_service_load (service, &ignored_error);
559 560 561 562 563 564
	if (ignored_error != NULL) {
		go_error_info_print (ignored_error);
		go_error_info_free (ignored_error);
		return NULL;
	}

565 566 567 568 569 570 571 572 573
	res = ssol->cbs.creator (factory, param);
	if (res) {
		go_plugin_use_ref (service->plugin);
		g_object_set_data_full (G_OBJECT (res),
					"plugin-use", service->plugin,
					(GDestroyNotify)go_plugin_use_unref);
	}

	return res;
574 575
}

576
static gboolean
577 578
cb_load_and_functional (GnmSolverFactory *factory,
			WBCGtk *wbcg)
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
{
	PluginServiceSolver *ssol =
		g_object_get_data (G_OBJECT (factory), "ssol");
	GOPluginService *service = GO_PLUGIN_SERVICE (ssol);
	GOErrorInfo *ignored_error = NULL;
	GnmSolverFactoryFunctional functional;

	go_plugin_service_load (service, &ignored_error);
	if (ignored_error != NULL) {
		go_error_info_print (ignored_error);
		go_error_info_free (ignored_error);
		return FALSE;
	}

	functional = ssol->cbs.functional;
594
	return (functional == NULL || functional (factory, wbcg));
595 596
}

597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
static void
plugin_service_solver_init (PluginServiceSolver *ssol)
{
	GO_PLUGIN_SERVICE (ssol)->cbs_ptr = &ssol->cbs;
	ssol->factory = NULL;
	ssol->cbs.creator = NULL;
}

static void
plugin_service_solver_finalize (GObject *obj)
{
	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (obj);
	GObjectClass *parent_class;

	if (ssol->factory)
		g_object_unref (ssol->factory);

	parent_class = g_type_class_peek (GO_TYPE_PLUGIN_SERVICE);
	parent_class->finalize (obj);
}

static void
plugin_service_solver_read_xml (GOPluginService *service, xmlNode *tree,
				GOErrorInfo **ret_error)
{
	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (service);
	xmlChar *s_id, *s_name, *s_type;
624
	GnmSolverModelType type = GNM_SOLVER_LP;
625 626 627 628
	xmlNode *information_node;

	GO_INIT_RET_ERROR_INFO (ret_error);

629
	s_type = go_xml_node_get_cstr (tree, "model_type");
630
	if (s_type && strcmp (CXML2C (s_type), "mip") == 0)
631
		type = GNM_SOLVER_LP;
632 633
	else if (s_type && strcmp (CXML2C (s_type), "qp") == 0)
		type = GNM_SOLVER_QP;
634 635
	else if (s_type && strcmp (CXML2C (s_type), "nlp") == 0)
		type = GNM_SOLVER_NLP;
636
	else {
637
		*ret_error = go_error_info_new_str (_("Invalid solver model type."));
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
		return;
	}
	xmlFree (s_type);

	s_id = go_xml_node_get_cstr (tree, "id");

	s_name = NULL;
	information_node = go_xml_get_child_by_name (tree, "information");
	if (information_node != NULL) {
		xmlNode *node =
			go_xml_get_child_by_name_by_lang (information_node,
							  "description");
		if (node != NULL) {
			s_name = xmlNodeGetContent (node);
		}
	}

	if (!s_id || !s_name) {
		*ret_error = go_error_info_new_str (_("Missing fields in plugin file"));
	} else {
		ssol->factory = gnm_solver_factory_new (CXML2C (s_id),
							CXML2C (s_name),
							type,
661 662
							cb_load_and_create,
							cb_load_and_functional);
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712
		g_object_set_data (G_OBJECT (ssol->factory), "ssol", ssol);
	}
	xmlFree (s_id);
	xmlFree (s_name);
	if (*ret_error)
		return;

	/* More? */
}

static void
plugin_service_solver_activate (GOPluginService *service, GOErrorInfo **ret_error)
{
	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (service);

	GO_INIT_RET_ERROR_INFO (ret_error);
	gnm_solver_db_register (ssol->factory);
	service->is_active = TRUE;
}

static void
plugin_service_solver_deactivate (GOPluginService *service, GOErrorInfo **ret_error)
{
	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (service);

	GO_INIT_RET_ERROR_INFO (ret_error);
	gnm_solver_db_unregister (ssol->factory);
	service->is_active = FALSE;
}

static char *
plugin_service_solver_get_description (GOPluginService *service)
{
	PluginServiceSolver *ssol = GNM_PLUGIN_SERVICE_SOLVER (service);
	return g_strdup_printf (_("Solver Algorithm %s"),
				ssol->factory->name);
}

static void
plugin_service_solver_class_init (GObjectClass *gobject_class)
{
	GOPluginServiceClass *plugin_service_class = GO_PLUGIN_SERVICE_CLASS (gobject_class);

	gobject_class->finalize = plugin_service_solver_finalize;
	plugin_service_class->read_xml = plugin_service_solver_read_xml;
	plugin_service_class->activate = plugin_service_solver_activate;
	plugin_service_class->deactivate = plugin_service_solver_deactivate;
	plugin_service_class->get_description = plugin_service_solver_get_description;
}

Morten Welinder's avatar
Morten Welinder committed
713
GSF_CLASS (PluginServiceSolver, gnm_plugin_service_solver,
714
           plugin_service_solver_class_init, plugin_service_solver_init,
715
           GO_TYPE_PLUGIN_SERVICE)
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747

/****************************************************************************/


typedef GOPluginLoaderModule	  GnmPluginLoaderModule;
typedef GOPluginLoaderModuleClass GnmPluginLoaderModuleClass;

/*
 * Service - function_group
 */
typedef struct {
	GnmFuncDescriptor *module_fn_info_array;
	GHashTable *function_indices;
} ServiceLoaderDataFunctionGroup;

static void
function_group_loader_data_free (gpointer data)
{
	ServiceLoaderDataFunctionGroup *ld = data;

	g_hash_table_destroy (ld->function_indices);
	g_free (ld);
}

static gboolean
gnm_plugin_loader_module_func_desc_load (GOPluginService *service,
					 char const *name,
					 GnmFuncDescriptor *res)
{
	ServiceLoaderDataFunctionGroup *loader_data;
	gpointer func_index_ptr;

Morten Welinder's avatar
Morten Welinder committed
748
	g_return_val_if_fail (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (service), FALSE);
749 750 751 752 753 754 755 756 757 758 759 760 761 762
	g_return_val_if_fail (name != NULL, FALSE);

	loader_data = g_object_get_data (G_OBJECT (service), "loader_data");
	if (g_hash_table_lookup_extended (loader_data->function_indices, (gpointer) name,
	                                  NULL, &func_index_ptr)) {
		int i = GPOINTER_TO_INT (func_index_ptr);
		*res = loader_data->module_fn_info_array[i];
		return TRUE;
	}
	return FALSE;
}
static void
gnm_plugin_loader_module_load_service_function_group (GOPluginLoader  *loader,
						      GOPluginService *service,
763
						      GOErrorInfo **ret_error)
764 765 766 767 768
{
	GnmPluginLoaderModule *loader_module = GNM_PLUGIN_LOADER_MODULE (loader);
	gchar *fn_info_array_name;
	GnmFuncDescriptor *module_fn_info_array = NULL;

Morten Welinder's avatar
Morten Welinder committed
769
	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (service));
770

Jody Goldberg's avatar
Jody Goldberg committed
771
	GO_INIT_RET_ERROR_INFO (ret_error);
772
	fn_info_array_name = g_strconcat (
773
		go_plugin_service_get_id (service), "_functions", NULL);
774 775
	g_module_symbol (loader_module->handle, fn_info_array_name, (gpointer) &module_fn_info_array);
	if (module_fn_info_array != NULL) {
Morten Welinder's avatar
Morten Welinder committed
776
		GnmPluginServiceFunctionGroupCallbacks *cbs;
777 778 779
		ServiceLoaderDataFunctionGroup *loader_data;
		gint i;

780
		cbs = go_plugin_service_get_cbs (service);
781 782 783 784 785 786 787 788 789 790 791 792 793
		cbs->func_desc_load = &gnm_plugin_loader_module_func_desc_load;

		loader_data = g_new (ServiceLoaderDataFunctionGroup, 1);
		loader_data->module_fn_info_array = module_fn_info_array;
		loader_data->function_indices = g_hash_table_new (&g_str_hash, &g_str_equal);
		for (i = 0; module_fn_info_array[i].name != NULL; i++) {
			g_hash_table_insert (loader_data->function_indices,
			                     (gpointer) module_fn_info_array[i].name,
			                     GINT_TO_POINTER (i));
		}
		g_object_set_data_full (
			G_OBJECT (service), "loader_data", loader_data, function_group_loader_data_free);
	} else {
794
		*ret_error = go_error_info_new_printf (
795 796
		             _("Module file \"%s\" has invalid format."),
		             loader_module->module_file_name);
797 798
		go_error_info_add_details (*ret_error,
					go_error_info_new_printf (
799 800 801 802 803 804 805 806 807 808 809
					_("File doesn't contain \"%s\" array."),
					fn_info_array_name));
	}
	g_free (fn_info_array_name);
}

/*
 * Service - ui
 */

typedef struct {
Morten Welinder's avatar
Morten Welinder committed
810
	GnmModulePluginUIActions *module_ui_actions_array;
811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826
	GHashTable *ui_actions_hash;
} ServiceLoaderDataUI;

static void
ui_loader_data_free (gpointer data)
{
	ServiceLoaderDataUI *ld = data;

	g_hash_table_destroy (ld->ui_actions_hash);
	g_free (ld);
}

static void
gnm_plugin_loader_module_func_exec_action (GOPluginService *service,
					   GnmAction const *action,
					   WorkbookControl *wbc,
827
					   GOErrorInfo **ret_error)
828 829 830 831 832
{
	ServiceLoaderDataUI *loader_data;
	gpointer action_index_ptr;
	int action_index;

Morten Welinder's avatar
Morten Welinder committed
833
	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_UI (service));
834

Jody Goldberg's avatar
Jody Goldberg committed
835
	GO_INIT_RET_ERROR_INFO (ret_error);
836 837 838
	loader_data = g_object_get_data (G_OBJECT (service), "loader_data");
	if (!g_hash_table_lookup_extended (loader_data->ui_actions_hash, action->id,
	                                   NULL, &action_index_ptr)) {
839
		*ret_error = go_error_info_new_printf (_("Unknown action: %s"), action->id);
840 841 842
		return;
	}
	action_index = GPOINTER_TO_INT (action_index_ptr);
843 844
	if (NULL != loader_data->module_ui_actions_array [action_index].handler)
		(*loader_data->module_ui_actions_array [action_index].handler) (action, wbc);
845 846 847 848 849
}

static void
gnm_plugin_loader_module_load_service_ui (GOPluginLoader *loader,
					  GOPluginService *service,
850
					  GOErrorInfo **ret_error)
851 852 853
{
	GnmPluginLoaderModule *loader_module = GNM_PLUGIN_LOADER_MODULE (loader);
	char *ui_actions_array_name;
Morten Welinder's avatar
Morten Welinder committed
854 855
	GnmModulePluginUIActions *module_ui_actions_array = NULL;
	GnmPluginServiceUICallbacks *cbs;
856 857 858
	ServiceLoaderDataUI *loader_data;
	gint i;

Morten Welinder's avatar
Morten Welinder committed
859
	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_UI (service));
860

Jody Goldberg's avatar
Jody Goldberg committed
861
	GO_INIT_RET_ERROR_INFO (ret_error);
862
	ui_actions_array_name = g_strconcat (
863
		go_plugin_service_get_id (service), "_ui_actions", NULL);
864 865
	g_module_symbol (loader_module->handle, ui_actions_array_name, (gpointer) &module_ui_actions_array);
	if (module_ui_actions_array == NULL) {
866
		*ret_error = go_error_info_new_printf (
867 868
			_("Module file \"%s\" has invalid format."),
			loader_module->module_file_name);
869
		go_error_info_add_details (*ret_error, go_error_info_new_printf (
870 871 872 873 874 875
			_("File doesn't contain \"%s\" array."), ui_actions_array_name));
		g_free (ui_actions_array_name);
		return;
	}
	g_free (ui_actions_array_name);

876
	cbs = go_plugin_service_get_cbs (service);
877 878 879 880 881 882 883 884 885 886 887 888 889
	cbs->plugin_func_exec_action = gnm_plugin_loader_module_func_exec_action;

	loader_data = g_new (ServiceLoaderDataUI, 1);
	loader_data->module_ui_actions_array = module_ui_actions_array;
	loader_data->ui_actions_hash = g_hash_table_new (g_str_hash, g_str_equal);
	for (i = 0; module_ui_actions_array[i].name != NULL; i++)
		g_hash_table_insert (loader_data->ui_actions_hash,
			(gpointer) module_ui_actions_array[i].name,
			GINT_TO_POINTER (i));
	g_object_set_data_full (G_OBJECT (service),
		"loader_data", loader_data, ui_loader_data_free);
}

890 891 892 893 894 895 896
static void
gnm_plugin_loader_module_load_service_solver (GOPluginLoader *loader,
					      GOPluginService *service,
					      GOErrorInfo **ret_error)
{
	GnmPluginLoaderModule *loader_module =
		GNM_PLUGIN_LOADER_MODULE (loader);
Morten Welinder's avatar
Morten Welinder committed
897
	GnmPluginServiceSolverCallbacks *cbs;
898 899
	char *symname;
	GnmSolverCreator creator;
900
	GnmSolverFactoryFunctional functional;
901

Morten Welinder's avatar
Morten Welinder committed
902
	g_return_if_fail (GNM_IS_PLUGIN_SERVICE_SOLVER (service));
903 904 905 906 907 908 909 910 911 912 913 914 915 916 917

	GO_INIT_RET_ERROR_INFO (ret_error);

	symname = g_strconcat (go_plugin_service_get_id (service),
			       "_solver_factory",
			       NULL);
	g_module_symbol (loader_module->handle, symname, (gpointer)&creator);
	g_free (symname);
	if (!creator) {
		*ret_error = go_error_info_new_printf (
			_("Module file \"%s\" has invalid format."),
			loader_module->module_file_name);
		return;
	}

918 919 920 921 922 923
	symname = g_strconcat (go_plugin_service_get_id (service),
			       "_solver_factory_functional",
			       NULL);
	g_module_symbol (loader_module->handle, symname, (gpointer)&functional);
	g_free (symname);

924 925
	cbs = go_plugin_service_get_cbs (service);
	cbs->creator = creator;
926
	cbs->functional = functional;
927 928
}

929
static gboolean
930
gplm_service_load (GOPluginLoader *l, GOPluginService *s, GOErrorInfo **err)
931
{
Morten Welinder's avatar
Morten Welinder committed
932
	if (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (s))
933
		gnm_plugin_loader_module_load_service_function_group (l, s, err);
Morten Welinder's avatar
Morten Welinder committed
934
	else if (GNM_IS_PLUGIN_SERVICE_UI (s))
935
		gnm_plugin_loader_module_load_service_ui (l, s, err);
Morten Welinder's avatar
Morten Welinder committed
936
	else if (GNM_IS_PLUGIN_SERVICE_SOLVER (s))
937
		gnm_plugin_loader_module_load_service_solver (l, s, err);
938 939 940 941 942 943
	else
		return FALSE;
	return TRUE;
}

static gboolean
944
gplm_service_unload (GOPluginLoader *l, GOPluginService *s, GOErrorInfo **err)
945
{
Morten Welinder's avatar
Morten Welinder committed
946
	if (GNM_IS_PLUGIN_SERVICE_FUNCTION_GROUP (s)) {
Morten Welinder's avatar
Morten Welinder committed
947
		GnmPluginServiceFunctionGroupCallbacks *cbs = go_plugin_service_get_cbs (s);
948
		cbs->func_desc_load = NULL;
Morten Welinder's avatar
Morten Welinder committed
949
	} else if (GNM_IS_PLUGIN_SERVICE_UI (s)) {
Morten Welinder's avatar
Morten Welinder committed
950
		GnmPluginServiceUICallbacks *cbs = go_plugin_service_get_cbs (s);
951
		cbs->plugin_func_exec_action = NULL;
Morten Welinder's avatar
Morten Welinder committed
952
	} else if (GNM_IS_PLUGIN_SERVICE_SOLVER (s)) {
Morten Welinder's avatar
Morten Welinder committed
953
		GnmPluginServiceSolverCallbacks *cbs =
954 955
			go_plugin_service_get_cbs (s);
		cbs->creator = NULL;
956
		cbs->functional = NULL;
957 958 959 960 961 962 963 964 965 966 967 968
	} else
		return FALSE;
	return TRUE;
}

static void
go_plugin_loader_module_iface_init (GOPluginLoaderClass *iface)
{
	iface->service_load   = gplm_service_load;
	iface->service_unload = gplm_service_unload;
}

969
GSF_CLASS_FULL (GnmPluginLoaderModule, gnm_plugin_loader_module,
970
           NULL, NULL, NULL, NULL,
971 972
           NULL, GO_TYPE_PLUGIN_LOADER_MODULE, 0,
	   GSF_INTERFACE (go_plugin_loader_module_iface_init, GO_TYPE_PLUGIN_LOADER))
973 974 975 976 977 978 979

/****************************************************************************/

void
gnm_plugins_init (GOCmdContext *context)
{
	char const *env_var;
Jody Goldberg's avatar
Jody Goldberg committed
980
	GSList *dir_list = go_slist_create (
981
		g_build_filename (gnm_sys_lib_dir (), PLUGIN_SUBDIR, NULL),
982
	        g_strdup (gnm_sys_extern_plugin_dir ()),
983 984
		(gnm_usr_dir (TRUE) == NULL ? NULL :
			g_build_filename (gnm_usr_dir (TRUE), PLUGIN_SUBDIR, NULL)),
985 986
		NULL);
	dir_list = g_slist_concat (dir_list,
987
				   go_string_slist_copy (gnm_conf_get_plugins_extra_dirs ()));
988 989 990

	env_var = g_getenv ("GNUMERIC_PLUGIN_PATH");
	if (env_var != NULL)
Jody Goldberg's avatar
Jody Goldberg committed
991
		GO_SLIST_CONCAT (dir_list, go_strsplit_to_slist (env_var, G_SEARCHPATH_SEPARATOR));
992 993

	go_plugins_init (GO_CMD_CONTEXT (context),
994 995 996
			 gnm_conf_get_plugins_file_states (),
			 gnm_conf_get_plugins_active (),
			 dir_list,
997
			 gnm_conf_get_plugins_activate_newplugins (),
998
			 gnm_plugin_loader_module_get_type ());
999
}