1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * plugin.c
4  * Copyright (C) Ishan Chattopadhyaya 2009 <ichattopadhyaya@gmail.com>
5  *
6  * plugin.c is free software.
7  *
8  * You may redistribute it and/or modify it under the terms of the
9  * GNU General Public License, as published by the Free Software
10  * Foundation; either version 2 of the License, or (at your option)
11  * any later version.
12  *
13  * plugin.c is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16  * See the GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with plugin.c.  If not, write to:
20  * 	The Free Software Foundation, Inc.,
21  * 	51 Franklin Street, Fifth Floor
22  * 	Boston, MA  02110-1301, USA.
23  */
24 
25 #include <config.h>
26 #include <ctype.h>
27 #include <stdlib.h>
28 #include <libanjuta/anjuta-shell.h>
29 #include <libanjuta/anjuta-debug.h>
30 #include <libanjuta/anjuta-launcher.h>
31 #include <libanjuta/anjuta-preferences.h>
32 #include <libanjuta/anjuta-utils.h>
33 #include <libanjuta/interfaces/ianjuta-iterable.h>
34 #include <libanjuta/interfaces/ianjuta-document.h>
35 #include <libanjuta/interfaces/ianjuta-document-manager.h>
36 #include <libanjuta/interfaces/ianjuta-editor.h>
37 #include <libanjuta/interfaces/ianjuta-editor-cell.h>
38 #include <libanjuta/interfaces/ianjuta-editor-language.h>
39 #include <libanjuta/interfaces/ianjuta-editor-selection.h>
40 #include <libanjuta/interfaces/ianjuta-editor-assist.h>
41 #include <libanjuta/interfaces/ianjuta-editor-glade-signal.h>
42 #include <libanjuta/interfaces/ianjuta-preferences.h>
43 #include <libanjuta/interfaces/ianjuta-symbol.h>
44 #include <libanjuta/interfaces/ianjuta-language.h>
45 #include <libanjuta/interfaces/ianjuta-indenter.h>
46 
47 #include "plugin.h"
48 #include "python-assist.h"
49 
50 #define UI_FILE PACKAGE_DATA_DIR"/ui/anjuta-language-support-python.xml"
51 #define PROPERTIES_FILE_UI PACKAGE_DATA_DIR"/glade/anjuta-language-support-python.ui"
52 #define ICON_FILE "anjuta-language-support-python-plugin.png"
53 
54 /* Preferences keys */
55 
56 #define ANJUTA_PREF_SCHEMA_PREFIX "org.gnome.anjuta."
57 #define PREF_SCHEMA "org.gnome.anjuta.plugins.python"
58 
59 
60 #define PREF_NO_ROPE_WARNING "no-rope-warning"
61 #define PREF_INTERPRETER_PATH "interpreter-path"
62 
63 static gpointer parent_class;
64 
65 static void
on_check_finished(AnjutaLauncher * launcher,int child_pid,int exit_status,gulong time,gpointer user_data)66 on_check_finished (AnjutaLauncher* launcher,
67                    int child_pid, int exit_status,
68                    gulong time, gpointer user_data)
69 {
70 	PythonPlugin* plugin = ANJUTA_PLUGIN_PYTHON (user_data);
71 	if (exit_status != 0)
72 	{
73 		GtkWidget* dialog = gtk_dialog_new_with_buttons (_("Python support warning"),
74 		                                                 NULL,
75 		                                                 GTK_DIALOG_MODAL,
76 		                                                 GTK_STOCK_OK,
77 		                                                 GTK_RESPONSE_ACCEPT,
78 		                                                 NULL);
79 		GtkWidget* area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
80 
81 		GtkWidget* label = gtk_label_new (_("Either python path is wrong or python-rope (http://rope.sf.net) libraries\n"
82 		                                    "aren't installed. Both are required for autocompletion in python files.\n"
83 		                                    "Please install them and check the python path in the preferences."));
84 		GtkWidget* check_button = gtk_check_button_new_with_label (_("Do not show that warning again"));
85 
86 		gtk_box_pack_start (GTK_BOX (area), label,
87 		                    TRUE, TRUE, 5);
88 		gtk_box_pack_start (GTK_BOX (area), check_button,
89 		                    TRUE, TRUE, 5);
90 		gtk_widget_show_all (dialog);
91 
92 		gtk_dialog_run (GTK_DIALOG(dialog));
93 
94 		/* Save "Do not show again settings" */
95 		g_settings_set_boolean (plugin->settings,
96 		                        PREF_NO_ROPE_WARNING,
97 		                        gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_button)));
98 
99 		gtk_widget_destroy (dialog);
100 	}
101 	g_object_unref (launcher);
102 }
103 
104 static void
check_support(PythonPlugin * python_plugin)105 check_support (PythonPlugin *python_plugin)
106 {
107 	if (!g_settings_get_boolean (python_plugin->settings,
108 	                             PREF_NO_ROPE_WARNING))
109 	{
110 		AnjutaLauncher* launcher = anjuta_launcher_new ();
111 		gchar* python_path = g_settings_get_string (python_plugin->settings,
112 		                                            PREF_INTERPRETER_PATH);
113 		gchar* command = g_strdup_printf ("%s -c \"import rope\"", python_path);
114 
115 		g_signal_connect (launcher, "child-exited",
116 	                  G_CALLBACK(on_check_finished), python_plugin);
117 		anjuta_launcher_execute (launcher, command, NULL, NULL);
118 
119 		g_free (python_path);
120 		g_free (command);
121 	}
122 }
123 
124 /* Glade support */
125 static gchar*
language_support_check_param_name(const gchar * name,GList ** names)126 language_support_check_param_name (const gchar* name,
127                                    GList** names)
128 {
129 	gint index = 0;
130 	GString* real_name = g_string_new (name);
131 	while (g_list_find_custom (*names, real_name->str, (GCompareFunc) strcmp))
132 	{
133 		g_string_free (real_name, TRUE);
134 		real_name = g_string_new (name);
135 		g_string_append_printf (real_name, "%d", ++index);
136 	}
137 	*names = g_list_append (*names, real_name->str);
138 	return g_string_free (real_name, FALSE);
139 }
140 
141 static const gchar*
language_support_get_signal_parameter(const gchar * type_name,GList ** names)142 language_support_get_signal_parameter (const gchar* type_name, GList** names)
143 {
144 	const gchar* c;
145 	const gchar* param_name = NULL;
146 	GString* param_string;
147 	gchar* real_name;
148 	/* Search for the second upper character */
149 	for (c = type_name + 1; *c != '\0'; c++)
150 	{
151 		if (g_ascii_isupper (*c))
152 		{
153 			param_name = c;
154 			break;
155 		}
156 	}
157 	if (param_name && strlen (param_name))
158 	{
159 		param_string = g_string_new (param_name);
160 		g_string_ascii_down (param_string);
161 	}
162 	else
163 	{
164 		param_string = g_string_new ("arg");
165 	}
166 	real_name = language_support_check_param_name (g_string_free (param_string, FALSE), names);
167 
168 	return real_name;
169 }
170 
171 static void
on_glade_drop(IAnjutaEditor * editor,IAnjutaIterable * iterator,const gchar * signal_data,PythonPlugin * lang_plugin)172 on_glade_drop (IAnjutaEditor* editor,
173                IAnjutaIterable* iterator,
174                const gchar* signal_data,
175                PythonPlugin* lang_plugin)
176 {
177 	GSignalQuery query;
178 	GType type;
179 	guint id;
180 
181 	const gchar* widget;
182 	const gchar* signal;
183 	const gchar* handler;
184 	GList* names = NULL;
185 	GString* str = g_string_new (NULL);
186 	int i;
187 	IAnjutaIterable* start, * end;
188 
189 	GStrv data = g_strsplit(signal_data, ":", 5);
190 
191 	widget = data[0];
192 	signal = data[1];
193 	handler = data[2];
194 
195 	type = g_type_from_name (widget);
196 	id = g_signal_lookup (signal, type);
197 
198 	g_signal_query (id, &query);
199 
200 	g_string_append_printf (str, "\ndef %s (self, %s", handler,
201 	                        language_support_get_signal_parameter (widget,
202 	                                                               &names));
203 	for (i = 0; i < query.n_params; i++)
204 	{
205 				const gchar* type_name = g_type_name (query.param_types[i]);
206 		const gchar* param_name = language_support_get_signal_parameter (type_name,
207 		                                                                 &names);
208 
209 		g_string_append_printf (str, ", %s", param_name);
210 	}
211 	g_string_append (str, "):\n");
212 
213 	ianjuta_editor_insert (editor, iterator,
214 	                       str->str, -1, NULL);
215 
216 	/* Indent code correctly */
217 	start = iterator;
218 	end = ianjuta_iterable_clone (iterator, NULL);
219 	ianjuta_iterable_set_position (end,
220 	                               ianjuta_iterable_get_position (iterator, NULL)
221 	                           		+ g_utf8_strlen (str->str, -1),
222 	                               NULL);
223 	ianjuta_indenter_indent (IANJUTA_INDENTER (lang_plugin),
224 	                         start, end, NULL);
225 	g_object_unref (end);
226 
227 	g_string_free (str, TRUE);
228 	anjuta_util_glist_strings_free (names);
229 
230 	g_strfreev (data);
231 }
232 
233 static void
install_support(PythonPlugin * lang_plugin)234 install_support (PythonPlugin *lang_plugin)
235 {
236 	IAnjutaLanguage* lang_manager =
237 		anjuta_shell_get_interface (ANJUTA_PLUGIN (lang_plugin)->shell,
238 									IAnjutaLanguage, NULL);
239 	IAnjutaSymbolManager* sym_manager =
240 		anjuta_shell_get_interface (ANJUTA_PLUGIN (lang_plugin)->shell,
241 		                            IAnjutaSymbolManager,
242 		                            NULL);
243 
244 	if (!lang_manager || !sym_manager)
245 		return;
246 
247 	if (lang_plugin->support_installed)
248 		return;
249 
250 	lang_plugin->current_language =
251 		ianjuta_language_get_name_from_editor (lang_manager,
252 											   IANJUTA_EDITOR_LANGUAGE (lang_plugin->current_editor), NULL);
253 
254 	if (!(lang_plugin->current_language &&
255 		(g_str_equal (lang_plugin->current_language, "Python"))))
256 		return;
257 
258 	/* Disable editor intern auto-indent */
259 	ianjuta_editor_set_auto_indent (IANJUTA_EDITOR(lang_plugin->current_editor),
260 								    FALSE, NULL);
261 
262 	if (IANJUTA_IS_EDITOR_ASSIST (lang_plugin->current_editor) )
263 	{
264 		AnjutaPlugin *plugin;
265 		IAnjutaEditor* ieditor;
266 
267 		const gchar *project_root;
268 
269 		check_support (lang_plugin);
270 
271 		plugin = ANJUTA_PLUGIN (lang_plugin);
272 		ieditor = IANJUTA_EDITOR (lang_plugin->current_editor);
273 
274 		g_assert (lang_plugin->assist == NULL);
275 
276 		project_root = ANJUTA_PLUGIN_PYTHON(plugin)->project_root_directory;
277 
278 		lang_plugin->assist = python_assist_new (ieditor,
279 		                                         sym_manager,
280 		                                         lang_plugin->settings,
281 		                                         plugin,
282 		                                         project_root);
283 	}
284 
285 	if (IANJUTA_IS_EDITOR_GLADE_SIGNAL (lang_plugin->current_editor))
286 	{
287 		g_signal_connect (lang_plugin->current_editor,
288 		                  "drop-possible", G_CALLBACK (gtk_true), NULL);
289 		g_signal_connect (lang_plugin->current_editor,
290 		                  "drop", G_CALLBACK (on_glade_drop),
291 		                  lang_plugin);
292 	}
293 
294 	lang_plugin->support_installed = TRUE;
295 }
296 
297 static void
uninstall_support(PythonPlugin * lang_plugin)298 uninstall_support (PythonPlugin *lang_plugin)
299 {
300 	if (!lang_plugin->support_installed)
301 		return;
302 
303 	if (lang_plugin->assist)
304 	{
305 		g_object_unref (lang_plugin->assist);
306 		lang_plugin->assist = NULL;
307 	}
308 
309 	if (IANJUTA_IS_EDITOR_GLADE_SIGNAL (lang_plugin->current_editor))
310 	{
311 		g_signal_handlers_disconnect_by_func (lang_plugin->current_editor,
312 			                                  gtk_true, NULL);
313 		g_signal_handlers_disconnect_by_func (lang_plugin->current_editor,
314 			                                  on_glade_drop, lang_plugin);
315 	}
316 
317 	lang_plugin->support_installed = FALSE;
318 }
319 
320 static void
on_editor_language_changed(IAnjutaEditor * editor,const gchar * new_language,PythonPlugin * plugin)321 on_editor_language_changed (IAnjutaEditor *editor,
322 							const gchar *new_language,
323 							PythonPlugin *plugin)
324 {
325 	uninstall_support (plugin);
326 	install_support (plugin);
327 }
328 
329 static void
on_editor_added(AnjutaPlugin * plugin,const gchar * name,const GValue * value,gpointer data)330 on_editor_added (AnjutaPlugin *plugin, const gchar *name,
331                  const GValue *value, gpointer data)
332 {
333 	PythonPlugin *lang_plugin;
334 	IAnjutaDocument* doc = IANJUTA_DOCUMENT(g_value_get_object (value));
335 	lang_plugin = ANJUTA_PLUGIN_PYTHON(plugin);
336 
337 
338 	if (IANJUTA_IS_EDITOR(doc))
339 	{
340 		lang_plugin->current_editor = G_OBJECT(doc);
341 	}
342 	else
343 	{
344 		lang_plugin->current_editor = NULL;
345 		return;
346 	}
347 	if (lang_plugin->current_editor)
348 	{
349 		install_support (lang_plugin);
350 		g_signal_connect (lang_plugin->current_editor, "language-changed",
351 		                  G_CALLBACK (on_editor_language_changed),
352 		                  plugin);
353 	}
354 }
355 
356 static void
on_editor_removed(AnjutaPlugin * plugin,const gchar * name,gpointer data)357 on_editor_removed (AnjutaPlugin *plugin, const gchar *name,
358                  gpointer data)
359 {
360 	PythonPlugin *lang_plugin;
361 	lang_plugin = ANJUTA_PLUGIN_PYTHON (plugin);
362 
363 	if (lang_plugin->current_editor)
364 		g_signal_handlers_disconnect_by_func (lang_plugin->current_editor,
365 										  G_CALLBACK (on_editor_language_changed),
366 										  plugin);
367 
368 	uninstall_support (lang_plugin);
369 
370 	lang_plugin->current_editor = NULL;
371 	lang_plugin->current_language = NULL;
372 }
373 
374 static GtkActionEntry actions[] = {
375 	{
376 		"ActionMenuEdit",
377 		NULL, N_("_Edit"),
378 		NULL, NULL, NULL
379 	}
380 };
381 
382 static void
on_project_root_added(AnjutaPlugin * plugin,const gchar * name,const GValue * value,gpointer user_data)383 on_project_root_added (AnjutaPlugin *plugin, const gchar *name,
384 					   const GValue *value, gpointer user_data)
385 {
386 	PythonPlugin *python_plugin;
387 	gchar *project_root_uri;
388 	GFile *file;
389 
390 	python_plugin = ANJUTA_PLUGIN_PYTHON (plugin);
391 
392 	g_free (python_plugin->project_root_directory);
393 	project_root_uri = g_value_dup_string (value);
394 	file = g_file_new_for_uri (project_root_uri);
395 	python_plugin->project_root_directory = g_file_get_path (file);
396 	g_object_unref (file);
397 	g_free (project_root_uri);
398 }
399 
400 static void
on_project_root_removed(AnjutaPlugin * plugin,const gchar * name,gpointer user_data)401 on_project_root_removed (AnjutaPlugin *plugin, const gchar *name,
402 						 gpointer user_data)
403 {
404 	PythonPlugin *python_plugin;
405 
406 	python_plugin = ANJUTA_PLUGIN_PYTHON (plugin);
407 
408 	g_free (python_plugin->project_root_directory);
409 	python_plugin->project_root_directory = NULL;
410 }
411 
412 static gboolean
python_plugin_activate(AnjutaPlugin * plugin)413 python_plugin_activate (AnjutaPlugin *plugin)
414 {
415 	AnjutaUI *ui;
416 
417 	PythonPlugin *python_plugin;
418 
419 	python_plugin = (PythonPlugin*) plugin;
420 
421 	python_plugin->prefs = anjuta_shell_get_preferences (plugin->shell, NULL);
422 
423 	/* Add all UI actions and merge UI */
424 	ui = anjuta_shell_get_ui (plugin->shell, NULL);
425 
426 	python_plugin->action_group =
427 		anjuta_ui_add_action_group_entries (ui, "ActionGroupPythonAssist",
428 											_("Python Assistance"),
429 											actions,
430 											G_N_ELEMENTS (actions),
431 											GETTEXT_PACKAGE, TRUE,
432 											plugin);
433 	python_plugin->uiid = anjuta_ui_merge (ui, UI_FILE);
434 
435 	/* Add watches */
436 	python_plugin->project_root_watch_id = anjuta_plugin_add_watch (plugin,
437 									IANJUTA_PROJECT_MANAGER_PROJECT_ROOT_URI,
438 									on_project_root_added,
439 									on_project_root_removed,
440 									NULL);
441 
442 	python_plugin->editor_watch_id = anjuta_plugin_add_watch (plugin,
443 									IANJUTA_DOCUMENT_MANAGER_CURRENT_DOCUMENT,
444 									on_editor_added,
445 									on_editor_removed,
446 									NULL);
447 	return TRUE;
448 }
449 
450 static gboolean
python_plugin_deactivate(AnjutaPlugin * plugin)451 python_plugin_deactivate (AnjutaPlugin *plugin)
452 {
453 
454 	AnjutaUI *ui;
455 	PythonPlugin *lang_plugin;
456 	lang_plugin = (PythonPlugin*) (plugin);
457 
458 	anjuta_plugin_remove_watch (plugin,
459 								lang_plugin->editor_watch_id,
460 								TRUE);
461 	anjuta_plugin_remove_watch (plugin,
462 								lang_plugin->project_root_watch_id,
463 								TRUE);
464 
465 
466 	ui = anjuta_shell_get_ui (plugin->shell, NULL);
467 	anjuta_ui_remove_action_group (ui, ANJUTA_PLUGIN_PYTHON(plugin)->action_group);
468 	anjuta_ui_unmerge (ui, ANJUTA_PLUGIN_PYTHON(plugin)->uiid);
469 
470 	return TRUE;
471 }
472 
473 static void
python_plugin_finalize(GObject * obj)474 python_plugin_finalize (GObject *obj)
475 {
476 	/* Finalization codes here */
477 	G_OBJECT_CLASS (parent_class)->finalize (obj);
478 }
479 
480 static void
python_plugin_dispose(GObject * obj)481 python_plugin_dispose (GObject *obj)
482 {
483 	/* Disposition codes */
484 	PythonPlugin *plugin = (PythonPlugin*)obj;
485 
486 	if (plugin->settings)
487 		g_object_unref (plugin->settings);
488 	plugin->settings = NULL;
489 
490 	G_OBJECT_CLASS (parent_class)->dispose (obj);
491 }
492 
493 static void
python_plugin_instance_init(GObject * obj)494 python_plugin_instance_init (GObject *obj)
495 {
496 	PythonPlugin *plugin = (PythonPlugin*)obj;
497 	plugin->action_group = NULL;
498 	plugin->current_editor = NULL;
499 	plugin->current_language = NULL;
500 	plugin->editor_watch_id = 0;
501 	plugin->uiid = 0;
502 	plugin->assist = NULL;
503 	plugin->settings = g_settings_new (PREF_SCHEMA);
504 }
505 
506 static void
python_plugin_class_init(GObjectClass * klass)507 python_plugin_class_init (GObjectClass *klass)
508 {
509 	AnjutaPluginClass *plugin_class = ANJUTA_PLUGIN_CLASS (klass);
510 
511 	parent_class = g_type_class_peek_parent (klass);
512 
513 	plugin_class->activate = python_plugin_activate;
514 	plugin_class->deactivate = python_plugin_deactivate;
515 	klass->finalize = python_plugin_finalize;
516 	klass->dispose = python_plugin_dispose;
517 }
518 
519 #define PREF_WIDGET_SPACE "preferences:completion-space-after-func"
520 #define PREF_WIDGET_BRACE "preferences:completion-brace-after-func"
521 #define PREF_WIDGET_CLOSEBRACE "preferences:completion-closebrace-after-func"
522 #define PREF_WIDGET_AUTO "preferences:completion-enable"
523 
524 static void
on_autocompletion_toggled(GtkToggleButton * button,PythonPlugin * plugin)525 on_autocompletion_toggled (GtkToggleButton* button,
526                            PythonPlugin* plugin)
527 {
528     GtkWidget* widget;
529     gboolean sensitive = gtk_toggle_button_get_active (button);
530 
531     widget = GTK_WIDGET (gtk_builder_get_object (plugin->bxml, PREF_WIDGET_SPACE));
532     gtk_widget_set_sensitive (widget, sensitive);
533     widget = GTK_WIDGET (gtk_builder_get_object (plugin->bxml, PREF_WIDGET_BRACE));
534     gtk_widget_set_sensitive (widget, sensitive);
535     widget = GTK_WIDGET (gtk_builder_get_object (plugin->bxml, PREF_WIDGET_CLOSEBRACE));
536     gtk_widget_set_sensitive (widget, sensitive);
537 }
538 
539 static void
ipreferences_merge(IAnjutaPreferences * ipref,AnjutaPreferences * prefs,GError ** e)540 ipreferences_merge (IAnjutaPreferences* ipref, AnjutaPreferences* prefs,
541 					GError** e)
542 {
543 	/* Add preferences */
544 	GError* error = NULL;
545 	PythonPlugin* plugin = ANJUTA_PLUGIN_PYTHON (ipref);
546 	plugin->bxml = gtk_builder_new ();
547     GtkWidget* toggle;
548 
549 	if (!gtk_builder_add_from_file (plugin->bxml, PROPERTIES_FILE_UI, &error))
550 	{
551         g_warning ("Couldn't load builder file: %s", error->message);
552         g_error_free (error);
553 	}
554 	anjuta_preferences_add_from_builder (prefs,
555 	                                     plugin->bxml,
556 	                                     plugin->settings,
557 	                                     "preferences", _("Python"),
558 	                                     ICON_FILE);
559     toggle = GTK_WIDGET (gtk_builder_get_object (plugin->bxml, PREF_WIDGET_AUTO));
560     g_signal_connect (toggle, "toggled", G_CALLBACK (on_autocompletion_toggled),
561                       plugin);
562     on_autocompletion_toggled (GTK_TOGGLE_BUTTON (toggle), plugin);
563 }
564 
565 static void
ipreferences_unmerge(IAnjutaPreferences * ipref,AnjutaPreferences * prefs,GError ** e)566 ipreferences_unmerge (IAnjutaPreferences* ipref, AnjutaPreferences* prefs,
567 					  GError** e)
568 {
569 	PythonPlugin* plugin = ANJUTA_PLUGIN_PYTHON (ipref);
570 	anjuta_preferences_remove_page(prefs, _("Python"));
571 	g_object_unref (plugin->bxml);
572 }
573 
574 static void
ipreferences_iface_init(IAnjutaPreferencesIface * iface)575 ipreferences_iface_init (IAnjutaPreferencesIface* iface)
576 {
577 	iface->merge = ipreferences_merge;
578 	iface->unmerge = ipreferences_unmerge;
579 }
580 
581 ANJUTA_PLUGIN_BEGIN (PythonPlugin, python_plugin);
582 ANJUTA_PLUGIN_ADD_INTERFACE(ipreferences, IANJUTA_TYPE_PREFERENCES);
583 ANJUTA_PLUGIN_END;
584 
585 ANJUTA_SIMPLE_PLUGIN (PythonPlugin, python_plugin);
586