1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * Anjuta
4  * Copyright 2000 Dave Camp, Naba Kumar  <naba@gnome.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 /**
23  * SECTION:anjuta-plugin
24  * @short_description: Anjuta plugin base class from which all plugins are
25  * derived.
26  * @see_also: #AnjutaPluginManager, #AnjutaProfileManager
27  * @stability: Unstable
28  * @include: libanjuta/anjuta-plugin.h
29  *
30  * Anjuta plugins are components which are loaded in Anjuta IDE shell
31  * either on startup or on demand to perform various subtasks. Plugins are
32  * specialized in doing only a very specific task and can let other plugins
33  * interract with it using interfaces.
34  *
35  * A plugin class is derived from #AnjutaPlugin class and will be used by
36  * shell to instanciate any number of plugin objects.
37  *
38  * When a plugin class is derived from #AnjutaPlugin, the virtual mehtods
39  * <emphasis>activate</emphasis> and <emphasis>deactivate</emphasis> must
40  * be implemented. The <emphasis>activate</emphasis> method is used to
41  * activate the plugin. Note that plugin activation is different from plugin
42  * instance initialization. Instance initialization is use to do internal
43  * initialization, while <emphasis>activate</emphasis> method is used to
44  * setup the plugin in shell, UI and preferences. Other plugins can also
45  * be queried in <emphasis>activate</emphasis> method.
46  *
47  * Following things should be done in <emphasis>activate</emphasis> method.
48  * <orderedlist>
49  * 	<listitem>
50  * 		<para>
51  * 			Register UI Actions: Use anjuta_ui_add_action_group_entries() or
52  * 			anjuta_ui_add_toggle_action_group_entries() to add your action
53  * 			groups.
54  * 		</para>
55  * 	</listitem>
56  * 	<listitem>
57  * 		<para>
58  * 			Merge UI definition file: Use anjuta_ui_merge() to merge a UI
59  * 			file. See #AnjutaUI for more detail.
60  * 		</para>
61  * 	</listitem>
62  * 	<listitem>
63  * 		<para>
64  * 			Add widgets to Anjuta Shell: If the plugin has one or more
65  * 			widgets as its User Interface, they can be added with
66  * 			anjuta_shell_add_widget().
67  * 		</para>
68  * 	</listitem>
69  * 	<listitem>
70  * 		<para>
71  * 			Setup value watches with anjuta_plugin_add_watch().
72  * 		</para>
73  * 	</listitem>
74  * </orderedlist>
75  *
76  * <emphasis>deactivate</emphasis> method undos all the above. That is, it
77  * removes widgets from the shell, unmerges UI and removes the action groups.
78  *
79  * Plugins interact with each other using interfaces. A plugin can expose an
80  * interface which will let other plugins find it. Any number of interfaces can
81  * be exposed by a plugin. These exposed interfaces are called
82  * <emphasis>Primary</emphasis> interfaces of the plugin. The condition for
83  * the interfaces to be primary is that they should be independent (i.e.
84  * an external entity requesting to use a primary interface should not require
85  * other primary interfaces). For example, an editor plugin can implement
86  * #IAnjutaEditor, #IAnjutaStream and #IAnjutaFile interfaces and expose them
87  * as primary interfaces, because they are independent.
88  * <emphasis>Primary</emphasis> interfaces exposed by a plugin are exported in
89  * its plugin meta-data file, so that plugin manager could register them.
90  *
91  * Any other interfaces implemented by the plugin are called
92  * <emphasis>Secondary</emphasis> interfaces and they generally depend on
93  * one or more primary interfaces.
94  * For example, #IAnjutaEditor is the primary interface of anjuta-editor plugin,
95  * but it also implements secondary interfaces #IAnjutaEditorGutter and
96  * #IAnjutaEditorBuffer. Notice that secondary interfaces #IAnjutaEditorGutter
97  * and #IAnjutaEditorBuffer depend on #IAnjutaEditor interface.
98  *
99  * The purpose of distinguishing between primary and
100  * secondary interfaces is only at plugin level. At code level, they behave
101  * just the same and there is no difference.
102  * So, a natural sequence for a plugin to communicate with another plugin is:
103  * <orderedlist>
104  * 	<listitem>
105  * 		<para>
106  * 			Query the shell for a plugin implemeting the primary interface
107  * 			using anjuta_shell_get_interface(). It will return an
108  * 			implemetation of the interface (or %NULL if not found).
109  * 			Do not save this object for longer use, because the implementor
110  * 			plugin can change anytime and a different plugin implementing
111  * 			the same primary interface may be activated.
112  * 				<programlisting>
113  * GError *err = NULL;
114  * IAnjutaDocumentManager *docman;
115  * IAnjutaEditor *editor;
116  *
117  * docman = anjuta_shell_get_interface (ANJUTA_PLUGIN(plugin)->shell,
118  *                                      IAnjutaDocumentManager, &amp;err);
119  * if (err)
120  * {
121  * 	g_warning ("Error encountered: %s", err->message);
122  * 	g_error_free (err);
123  * 	return;
124  * }
125  *
126  * editor = ianjuta_document_manager_get_current_editor (docman, &amp;err);
127  * if (err)
128  * {
129  * 	g_warning ("Error encountered: %s", err->message);
130  * 	g_error_free (err);
131  * 	return;
132  * }
133  *
134  * ianjuta_editor_goto_line (editor, 200);
135  * ...
136  * 			</programlisting>
137  * 		</para>
138  * </listitem>
139  * <listitem>
140  * 	<para>
141  * 	A primary interface of a plugin can be directly used, but
142  * 	to use a secondary interface, make sure to check if the plugin
143  * 	object implements it. For example, to check if editor plugin
144  * 	implements IAnjutaEditorGutter interface, do something like:
145  * 		<programlisting>
146  *
147  * if (IANJUTA_IS_EDITOR_GUTTER(editor))
148  * {
149  * 	ianjuta_editor_gutter_set_marker(IANJUTA_EDITOR_GUTTER (editor),
150  * 	                                 ANJUTA_EDITOR_MARKER_1,
151  * 	                                 line_number, &amp;err);
152  * }
153  * 			</programlisting>
154  * 		</para>
155  * 	</listitem>
156  * </orderedlist>
157  *
158  * Plugins can also communicate with outside using Shell's <emphasis>Values
159  * System</emphasis>. Values are objects exported by plugins to make them
160  * available to other plugins. Read #AnjutaShell documentation for more
161  * detail on <emphasis>Values System</emphasis>. A plugin can set up watches
162  * with anjuta_plugin_add_watch() to get notifications for values exported
163  * by other plugins.
164  *
165  * Values are very unreliable way of passing objects between plugins, but are
166  * nevertheless very useful (and quicker to code). It must be used with care.
167  * As a rule of thumb, a plugin should only watch values of other trusted
168  * plugins. For example, a group of plugins forming a subsystem can comfortably
169  * use values to pass objects and notifications. Use anjuta_plugin_add_watch()
170  * and anjuta_plugin_remove_watch() to add or remove value watches.
171  */
172 
173 #include <config.h>
174 #include <string.h>
175 #include <libanjuta/anjuta-marshal.h>
176 #include <libanjuta/interfaces/ianjuta-preferences.h>
177 #include "anjuta-plugin.h"
178 
179 typedef struct
180 {
181 	guint id;
182 	char *name;
183 	AnjutaPluginValueAdded added;
184 	AnjutaPluginValueRemoved removed;
185 	gboolean need_remove;
186 	gpointer user_data;
187 } Watch;
188 
189 struct _AnjutaPluginPrivate {
190 	guint watch_num;
191 
192 	int added_signal_id;
193 	int removed_signal_id;
194 
195 	GList *watches;
196 
197 	gboolean activated;
198 };
199 
200 enum {
201 	PROP_0,
202 	PROP_SHELL,
203 };
204 
205 enum
206 {
207 	ACTIVATED_SIGNAL,
208 	DEACTIVATED_SIGNAL,
209 	LAST_SIGNAL
210 };
211 
212 static guint plugin_signals[LAST_SIGNAL] = { 0 };
213 
214 static void anjuta_plugin_finalize (GObject *object);
215 static void anjuta_plugin_class_init (AnjutaPluginClass *class);
216 
217 G_DEFINE_TYPE (AnjutaPlugin, anjuta_plugin, G_TYPE_OBJECT);
218 
219 static void
destroy_watch(Watch * watch)220 destroy_watch (Watch *watch)
221 {
222 	g_free (watch->name);
223 	g_free (watch);
224 }
225 
226 static void
anjuta_plugin_dispose(GObject * object)227 anjuta_plugin_dispose (GObject *object)
228 {
229 	AnjutaPlugin *plugin = ANJUTA_PLUGIN (object);
230 
231 	if (plugin->priv->watches) {
232 		g_list_foreach (plugin->priv->watches, (GFunc)destroy_watch, NULL);
233 		g_list_free (plugin->priv->watches);
234 		plugin->priv->watches = NULL;
235 	}
236 
237 	if (plugin->shell) {
238 		if (plugin->priv->added_signal_id) {
239 			g_signal_handler_disconnect (G_OBJECT (plugin->shell),
240 										 plugin->priv->added_signal_id);
241 			g_signal_handler_disconnect (G_OBJECT (plugin->shell),
242 										 plugin->priv->removed_signal_id);
243 			plugin->priv->added_signal_id = 0;
244 			plugin->priv->removed_signal_id = 0;
245 		}
246 		g_object_unref (plugin->shell);
247 		plugin->shell = NULL;
248 	}
249 }
250 
251 static void
anjuta_plugin_finalize(GObject * object)252 anjuta_plugin_finalize (GObject *object)
253 {
254 	AnjutaPlugin *plugin = ANJUTA_PLUGIN (object);
255 	g_free (plugin->priv);
256 }
257 
258 static void
anjuta_plugin_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)259 anjuta_plugin_get_property (GObject *object,
260 			  guint param_id,
261 			  GValue *value,
262 			  GParamSpec *pspec)
263 {
264 	AnjutaPlugin *plugin = ANJUTA_PLUGIN (object);
265 
266 	switch (param_id) {
267 	case PROP_SHELL:
268 		g_value_set_object (value, plugin->shell);
269 		break;
270 	default :
271 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
272 		break;
273 	}
274 }
275 
276 static void
anjuta_plugin_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)277 anjuta_plugin_set_property (GObject *object,
278 							guint param_id,
279 							const GValue *value,
280 							GParamSpec *pspec)
281 {
282 	AnjutaPlugin *plugin = ANJUTA_PLUGIN (object);
283 
284 	switch (param_id) {
285 	case PROP_SHELL:
286 		g_return_if_fail (plugin->shell == NULL);
287 		plugin->shell = g_value_get_object (value);
288 		g_object_ref (plugin->shell);
289 		g_object_notify (object, "shell");
290 		break;
291 	default :
292 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
293 		break;
294 	}
295 }
296 
297 static void
anjuta_plugin_class_init(AnjutaPluginClass * class)298 anjuta_plugin_class_init (AnjutaPluginClass *class)
299 {
300 	GObjectClass *object_class = (GObjectClass*) class;
301 
302 	object_class->dispose = anjuta_plugin_dispose;
303 	object_class->finalize = anjuta_plugin_finalize;
304 	object_class->get_property = anjuta_plugin_get_property;
305 	object_class->set_property = anjuta_plugin_set_property;
306 
307 	class->activate = NULL;
308 	class->deactivate = NULL;
309 
310 	/**
311 	 * AnjutaPlugin:shell:
312 	 *
313 	 * The #AnjutaShell object associated with this plugin
314 	 */
315 	g_object_class_install_property
316 		(object_class,
317 		 PROP_SHELL,
318 		 g_param_spec_object ("shell",
319 				      _("Anjuta Shell"),
320 				      _("Anjuta shell that will contain the plugin"),
321 				      ANJUTA_TYPE_SHELL,
322 				      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
323 	plugin_signals[ACTIVATED_SIGNAL] =
324 		g_signal_new ("activated",
325 					G_TYPE_FROM_CLASS (object_class),
326 					G_SIGNAL_RUN_FIRST,
327 					G_STRUCT_OFFSET (AnjutaPluginClass,
328 									 activated),
329 					NULL, NULL,
330 					anjuta_cclosure_marshal_VOID__VOID,
331 					G_TYPE_NONE, 0);
332 
333 	plugin_signals[ACTIVATED_SIGNAL] =
334 		g_signal_new ("deactivated",
335 					G_TYPE_FROM_CLASS (object_class),
336 					G_SIGNAL_RUN_FIRST,
337 					G_STRUCT_OFFSET (AnjutaPluginClass,
338 									 deactivated),
339 					NULL, NULL,
340 					anjuta_cclosure_marshal_VOID__VOID,
341 					G_TYPE_NONE, 0);
342 }
343 
344 static void
anjuta_plugin_init(AnjutaPlugin * plugin)345 anjuta_plugin_init (AnjutaPlugin *plugin)
346 {
347 	plugin->priv = g_new0 (AnjutaPluginPrivate, 1);
348 }
349 
350 static void
value_added_cb(AnjutaShell * shell,const char * name,const GValue * value,gpointer user_data)351 value_added_cb (AnjutaShell *shell,
352 		const char *name,
353 		const GValue *value,
354 		gpointer user_data)
355 {
356 	AnjutaPlugin *plugin = ANJUTA_PLUGIN (user_data);
357 	GList *l;
358 
359 	for (l = plugin->priv->watches; l != NULL; l = l->next) {
360 		Watch *watch = (Watch *)l->data;
361 		if (!strcmp (watch->name, name)) {
362 			if (watch->added) {
363 				watch->added (plugin,
364 					      name,
365 					      value,
366 					      watch->user_data);
367 			}
368 
369 			watch->need_remove = TRUE;
370 		}
371 	}
372 }
373 
374 static void
value_removed_cb(AnjutaShell * shell,const char * name,gpointer user_data)375 value_removed_cb (AnjutaShell *shell,
376 		  const char *name,
377 		  gpointer user_data)
378 {
379 	AnjutaPlugin *plugin = ANJUTA_PLUGIN (user_data);
380 	GList *l;
381 
382 	for (l = plugin->priv->watches; l != NULL; l = l->next) {
383 		Watch *watch = (Watch *)l->data;
384 		if (!strcmp (watch->name, name)) {
385 			if (watch->removed) {
386 				watch->removed (plugin, name, watch->user_data);
387 			}
388 			if (!watch->need_remove) {
389 				g_warning ("watch->need_remove FALSE when it should be TRUE");
390 			}
391 
392 			watch->need_remove = FALSE;
393 		}
394 	}
395 }
396 
397 /**
398  * anjuta_plugin_add_watch:
399  * @plugin: a #AnjutaPlugin derived class object.
400  * @name: Name of the value to watch.
401  * @added: (closure user_data) (scope call): Callback to call when the value is added.
402  * @removed: (closure user_data) (scope call): Callback to call when the value is removed.
403  * @user_data: User data to pass to callbacks.
404  *
405  * Adds a watch for @name value. When the value is added in shell, the @added
406  * callback will be called and when it is removed, the @removed callback will
407  * be called. The returned ID is used to remove the watch later.
408  *
409  * Return value: Watch ID.
410  */
411 guint
anjuta_plugin_add_watch(AnjutaPlugin * plugin,const gchar * name,AnjutaPluginValueAdded added,AnjutaPluginValueRemoved removed,gpointer user_data)412 anjuta_plugin_add_watch (AnjutaPlugin *plugin,
413 						 const gchar *name,
414 						 AnjutaPluginValueAdded added,
415 						 AnjutaPluginValueRemoved removed,
416 						 gpointer user_data)
417 {
418 	Watch *watch;
419 	GValue value = {0, };
420 	GError *error = NULL;
421 
422 	g_return_val_if_fail (plugin != NULL, -1);
423 	g_return_val_if_fail (ANJUTA_IS_PLUGIN (plugin), -1);
424 	g_return_val_if_fail (name != NULL, -1);
425 
426 	watch = g_new0 (Watch, 1);
427 
428 	watch->id = ++plugin->priv->watch_num;
429 	watch->name = g_strdup (name);
430 	watch->added = added;
431 	watch->removed = removed;
432 	watch->need_remove = FALSE;
433 	watch->user_data = user_data;
434 
435 	plugin->priv->watches = g_list_prepend (plugin->priv->watches, watch);
436 
437 	anjuta_shell_get_value (plugin->shell, name, &value, &error);
438 	if (!error) {
439 		if (added) {
440 			watch->added (plugin, name, &value, user_data);
441 			g_value_unset (&value);
442 		}
443 		watch->need_remove = TRUE;
444 	} else {
445 		/* g_warning ("Error in getting value '%s': %s", name, error->message); */
446 		g_error_free (error);
447 	}
448 
449 	if (!plugin->priv->added_signal_id) {
450 		plugin->priv->added_signal_id =
451 			g_signal_connect (plugin->shell,
452 					  "value_added",
453 					  G_CALLBACK (value_added_cb),
454 					  plugin);
455 
456 		plugin->priv->removed_signal_id =
457 			g_signal_connect (plugin->shell,
458 					  "value_removed",
459 					  G_CALLBACK (value_removed_cb),
460 					  plugin);
461 	}
462 
463 	return watch->id;
464 }
465 
466 /**
467  * anjuta_plugin_remove_watch:
468  * @plugin: a #AnjutaPlugin derived class object.
469  * @id: Watch id to remove.
470  * @send_remove: If true, calls value_removed callback.
471  *
472  * Removes the watch represented by @id (which was returned by
473  * anjuta_plugin_add_watch()).
474  */
475 void
anjuta_plugin_remove_watch(AnjutaPlugin * plugin,guint id,gboolean send_remove)476 anjuta_plugin_remove_watch (AnjutaPlugin *plugin, guint id,
477 							gboolean send_remove)
478 {
479 	GList *l;
480 	Watch *watch = NULL;
481 
482 	g_return_if_fail (plugin != NULL);
483 	g_return_if_fail (ANJUTA_IS_PLUGIN (plugin));
484 
485 	for (l = plugin->priv->watches; l != NULL; l = l->next) {
486 		watch = l->data;
487 
488 		if (watch->id == id) {
489 			break;
490 		}
491 	}
492 
493 	if (!watch) {
494 		g_warning ("Attempted to remove non-existant watch %d\n", id);
495 		return;
496 	}
497 
498 	if (send_remove && watch->need_remove && watch->removed) {
499 		watch->removed (plugin, watch->name, watch->user_data);
500 	}
501 
502 	plugin->priv->watches = g_list_remove (plugin->priv->watches, watch);
503 	destroy_watch (watch);
504 }
505 
506 /**
507  * anjuta_plugin_activate:
508  * @plugin: a #AnjutaPlugin derived class object.
509  *
510  * Activates the plugin by calling activate() virtual method. All plugins
511  * should derive their classes from this virtual class and implement this
512  * method.
513  * If the plugin implements IAnjutaPreferences, it is prompted to install
514  * it's preferences.
515  *
516  * Return value: %TRUE if sucessfully activated, %FALSE otherwise.
517  */
518 gboolean
anjuta_plugin_activate(AnjutaPlugin * plugin)519 anjuta_plugin_activate (AnjutaPlugin *plugin)
520 {
521 	AnjutaPluginClass *klass;
522 
523 	g_return_val_if_fail (plugin != NULL, FALSE);
524 	g_return_val_if_fail (ANJUTA_IS_PLUGIN (plugin), FALSE);
525 	g_return_val_if_fail (plugin->priv->activated == FALSE, FALSE);
526 
527 	klass = ANJUTA_PLUGIN_GET_CLASS(plugin);
528 	g_return_val_if_fail (klass->activate != NULL, FALSE);
529 
530 	plugin->priv->activated = klass->activate(plugin);
531 
532 	if (plugin->priv->activated)
533 		g_signal_emit_by_name (G_OBJECT (plugin), "activated");
534 
535 	return plugin->priv->activated;
536 }
537 
538 /**
539  * anjuta_plugin_deactivate:
540  * @plugin: a #AnjutaPlugin derived class object.
541  *
542  * Deactivates the plugin by calling deactivate() virtual method. All plugins
543  * should derive their classes from this virtual class and implement this
544  * method.
545  *
546  * Return value: %TRUE if sucessfully activated, %FALSE otherwise.
547  */
548 gboolean
anjuta_plugin_deactivate(AnjutaPlugin * plugin)549 anjuta_plugin_deactivate (AnjutaPlugin *plugin)
550 {
551 	AnjutaPluginClass *klass;
552 	gboolean success;
553 
554 	g_return_val_if_fail (plugin != NULL, FALSE);
555 	g_return_val_if_fail (ANJUTA_IS_PLUGIN (plugin), FALSE);
556 	g_return_val_if_fail (plugin->priv->activated == TRUE, FALSE);
557 
558 	klass = ANJUTA_PLUGIN_GET_CLASS(plugin);
559 	g_return_val_if_fail (klass->deactivate != NULL, FALSE);
560 
561 	success = klass->deactivate(plugin);
562 	plugin->priv->activated = !success;
563 	if (!plugin->priv->activated)
564 		g_signal_emit_by_name (G_OBJECT (plugin), "deactivated");
565 	return success;
566 }
567 
568 /**
569  * anjuta_plugin_is_active:
570  * @plugin: a #AnjutaPlugin derived class object.
571  *
572  * Returns %TRUE if the plugin has been activated.
573  *
574  * Return value: %TRUE if activated, %FALSE otherwise.
575  */
576 gboolean
anjuta_plugin_is_active(AnjutaPlugin * plugin)577 anjuta_plugin_is_active (AnjutaPlugin *plugin)
578 {
579 	return plugin->priv->activated;
580 }
581 
582 /**
583  * anjuta_plugin_get_shell:
584  * @plugin: a #AnjutaPlugin
585  *
586  * Returns: (transfer none): The #AnjutaShell object associated with this plugin
587  */
588 AnjutaShell *
anjuta_plugin_get_shell(AnjutaPlugin * plugin)589 anjuta_plugin_get_shell (AnjutaPlugin* plugin)
590 {
591 	return plugin->shell;
592 }
593 
594