1 /*
2  *      scplugin.c - this file is part of Spellcheck, a Geany plugin
3  *
4  *      Copyright 2008-2011 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5  *      Copyright 2008-2010 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
6  *
7  *      This program is free software; you can redistribute it and/or modify
8  *      it under the terms of the GNU General Public License as published by
9  *      the Free Software Foundation; either version 2 of the License, or
10  *      (at your option) any later version.
11  *
12  *      This program is distributed in the hope that it will be useful,
13  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *      GNU General Public License for more details.
16  *
17  *      You should have received a copy of the GNU General Public License
18  *      along with this program; if not, write to the Free Software
19  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20  *      MA 02110-1301, USA.
21  *
22  * $Id$
23  */
24 
25 
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29 
30 #include <geanyplugin.h>
31 
32 
33 #include "scplugin.h"
34 #include "gui.h"
35 #include "speller.h"
36 
37 
38 GeanyPlugin		*geany_plugin;
39 GeanyData		*geany_data;
40 
41 
42 PLUGIN_VERSION_CHECK(224)
43 PLUGIN_SET_TRANSLATABLE_INFO(
44 	LOCALEDIR,
45 	GETTEXT_PACKAGE,
46 	_("Spell Check"),
47 	_("Checks the spelling of the current document."),
48 	VERSION,
49 	"The Geany developer team")
50 
51 
52 SpellCheck *sc_info = NULL;
53 
54 
55 
56 /* Keybinding(s) */
57 enum
58 {
59 	KB_SPELL_CHECK,
60 	KB_SPELL_TOOGLE_TYPING,
61 	KB_COUNT
62 };
63 
64 
65 
66 
67 PluginCallback plugin_callbacks[] =
68 {
69 	{ "update-editor-menu", (GCallback) &sc_gui_update_editor_menu_cb, FALSE, NULL },
70 	{ "editor-notify", (GCallback) &sc_gui_editor_notify, FALSE, NULL },
71 	{ "document-open", (GCallback) &sc_gui_document_open_cb, FALSE, NULL },
72 	{ "document-reload", (GCallback) &sc_gui_document_open_cb, FALSE, NULL },
73 	{ NULL, NULL, FALSE, NULL }
74 };
75 
76 
populate_dict_combo(GtkComboBox * combo)77 static void populate_dict_combo(GtkComboBox *combo)
78 {
79 	guint i;
80 	GtkTreeModel *model = gtk_combo_box_get_model(combo);
81 
82 	gtk_list_store_clear(GTK_LIST_STORE(model));
83 	for (i = 0; i < sc_info->dicts->len; i++)
84 	{
85 		gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), g_ptr_array_index(sc_info->dicts, i));
86 
87 		if (utils_str_equal(g_ptr_array_index(sc_info->dicts, i), sc_info->default_language))
88 			gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
89 	}
90 	/* if the default language couldn't be selected, select the first available language */
91 	if (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)) == -1)
92 		gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
93 }
94 
95 
save_config(void)96 static void save_config(void)
97 {
98 		GKeyFile *config = g_key_file_new();
99 		gchar *data;
100 		gchar *config_dir = g_path_get_dirname(sc_info->config_file);
101 
102 		g_key_file_load_from_file(config, sc_info->config_file, G_KEY_FILE_NONE, NULL);
103 		if (sc_info->default_language != NULL) /* lang may be NULL */
104 			g_key_file_set_string(config, "spellcheck", "language", sc_info->default_language);
105 		g_key_file_set_boolean(config, "spellcheck", "check_while_typing",
106 			sc_info->check_while_typing);
107 		g_key_file_set_boolean(config, "spellcheck", "check_on_document_open",
108 			sc_info->check_on_document_open);
109 		g_key_file_set_boolean(config, "spellcheck", "use_msgwin",
110 			sc_info->use_msgwin);
111 		g_key_file_set_boolean(config, "spellcheck", "show_toolbar_item",
112 			sc_info->show_toolbar_item);
113 		g_key_file_set_boolean(config, "spellcheck", "show_editor_menu_item",
114 			sc_info->show_editor_menu_item);
115 		g_key_file_set_boolean(config, "spellcheck", "show_editor_menu_item_sub_menu",
116 			sc_info->show_editor_menu_item_sub_menu);
117 		if (sc_info->dictionary_dir != NULL)
118 			g_key_file_set_string(config, "spellcheck", "dictionary_dir",
119 				sc_info->dictionary_dir);
120 
121 		if (! g_file_test(config_dir, G_FILE_TEST_IS_DIR) && utils_mkdir(config_dir, TRUE) != 0)
122 		{
123 			dialogs_show_msgbox(GTK_MESSAGE_ERROR,
124 				_("Plugin configuration directory could not be created."));
125 		}
126 		else
127 		{
128 			/* write config to file */
129 			data = g_key_file_to_data(config, NULL, NULL);
130 			utils_write_file(sc_info->config_file, data);
131 			g_free(data);
132 		}
133 		g_free(config_dir);
134 		g_key_file_free(config);
135 }
136 
137 
configure_response_cb(GtkDialog * dialog,gint response,gpointer user_data)138 static void configure_response_cb(GtkDialog *dialog, gint response, gpointer user_data)
139 {
140 	if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY)
141 	{
142 		GtkComboBox *combo = GTK_COMBO_BOX(g_object_get_data(G_OBJECT(dialog), "combo"));
143 
144 		setptr(sc_info->default_language, gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo)));
145 #ifdef HAVE_ENCHANT_1_5
146 		setptr(sc_info->dictionary_dir, g_strdup(gtk_entry_get_text(GTK_ENTRY(
147 			g_object_get_data(G_OBJECT(dialog), "dict_dir")))));
148 #endif
149 		sc_speller_reinit_enchant_dict();
150 
151 		sc_info->check_while_typing = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
152 			g_object_get_data(G_OBJECT(dialog), "check_type"))));
153 
154 		sc_info->check_on_document_open = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
155 			g_object_get_data(G_OBJECT(dialog), "check_on_open"))));
156 
157 		sc_info->use_msgwin = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
158 			g_object_get_data(G_OBJECT(dialog), "check_msgwin"))));
159 
160 		sc_info->show_toolbar_item = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
161 			g_object_get_data(G_OBJECT(dialog), "check_toolbar"))));
162 
163 		sc_info->show_editor_menu_item = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
164 			g_object_get_data(G_OBJECT(dialog), "check_editor_menu"))));
165 
166 		sc_info->show_editor_menu_item_sub_menu = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
167 			g_object_get_data(G_OBJECT(dialog), "check_editor_menu_sub_menu"))));
168 
169 		save_config();
170 
171 		sc_gui_recreate_editor_menu();
172 		sc_gui_update_toolbar();
173 		sc_gui_update_menu();
174 		populate_dict_combo(combo);
175 	}
176 }
177 
178 
plugin_init(GeanyData * data)179 void plugin_init(GeanyData *data)
180 {
181 	GeanyKeyGroup *key_group;
182 	GKeyFile *config = g_key_file_new();
183 	gchar *default_lang;
184 
185 	default_lang = sc_speller_get_default_lang();
186 	sc_info = g_new0(SpellCheck, 1);
187 
188 	sc_info->config_file = g_strconcat(geany->app->configdir,
189 		G_DIR_SEPARATOR_S, "plugins", G_DIR_SEPARATOR_S,
190 		"spellcheck", G_DIR_SEPARATOR_S, "spellcheck.conf", NULL);
191 
192 	g_key_file_load_from_file(config, sc_info->config_file, G_KEY_FILE_NONE, NULL);
193 	sc_info->default_language = utils_get_setting_string(config,
194 		"spellcheck", "language", default_lang);
195 	sc_info->check_while_typing = utils_get_setting_boolean(config,
196 		"spellcheck", "check_while_typing", FALSE);
197 	sc_info->check_on_document_open = utils_get_setting_boolean(config,
198 		"spellcheck", "check_on_document_open", FALSE);
199 	sc_info->show_toolbar_item = utils_get_setting_boolean(config,
200 		"spellcheck", "show_toolbar_item", TRUE);
201 	sc_info->show_editor_menu_item = utils_get_setting_boolean(config,
202 		"spellcheck", "show_editor_menu_item", TRUE);
203 	sc_info->show_editor_menu_item_sub_menu = utils_get_setting_boolean(config,
204 		"spellcheck", "show_editor_menu_item_sub_menu", TRUE);
205 	sc_info->dictionary_dir = utils_get_setting_string(config,
206 		"spellcheck", "dictionary_dir", NULL);
207 	sc_info->use_msgwin = utils_get_setting_boolean(config, "spellcheck", "use_msgwin", FALSE);
208 	g_key_file_free(config);
209 	g_free(default_lang);
210 
211 	sc_info->menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_SPELL_CHECK, NULL);
212 	ui_add_document_sensitive(sc_info->menu_item);
213 
214 	sc_gui_update_toolbar();
215 
216 	sc_gui_init();
217 	sc_speller_init();
218 
219 	sc_gui_update_menu();
220 	gtk_widget_show_all(sc_info->menu_item);
221 
222 	/* setup keybindings */
223 	key_group = plugin_set_key_group(geany_plugin, "spellcheck", KB_COUNT, NULL);
224 	keybindings_set_item(key_group, KB_SPELL_CHECK, sc_gui_kb_run_activate_cb,
225 		0, 0, "spell_check", _("Run Spell Check"), sc_info->submenu_item_default);
226 	keybindings_set_item(key_group, KB_SPELL_TOOGLE_TYPING,
227 		sc_gui_kb_toggle_typing_activate_cb, 0, 0, "spell_toggle_typing",
228 		_("Toggle Check While Typing"), NULL);
229 }
230 
231 
232 #ifdef HAVE_ENCHANT_1_5
dictionary_dir_button_clicked_cb(GtkButton * button,gpointer item)233 static void dictionary_dir_button_clicked_cb(GtkButton *button, gpointer item)
234 {
235 	GtkWidget *dialog;
236 	gchar *text;
237 
238 	/* initialise the dialog */
239 	dialog = gtk_file_chooser_dialog_new(_("Select Directory"), NULL,
240 					GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
241 					GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
242 					GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
243 
244 	text = utils_get_locale_from_utf8(gtk_entry_get_text(GTK_ENTRY(item)));
245 	if (!EMPTY(text))
246 		gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), text);
247 
248 	/* run it */
249 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
250 	{
251 		gchar *utf8_filename, *tmp;
252 
253 		tmp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
254 		utf8_filename = utils_get_utf8_from_locale(tmp);
255 
256 		gtk_entry_set_text(GTK_ENTRY(item), utf8_filename);
257 
258 		g_free(utf8_filename);
259 		g_free(tmp);
260 	}
261 
262 	gtk_widget_destroy(dialog);
263 }
264 #endif
265 
266 
configure_frame_editor_menu_toggled_cb(GtkToggleButton * togglebutton,gpointer data)267 static void configure_frame_editor_menu_toggled_cb(GtkToggleButton *togglebutton, gpointer data)
268 {
269 	gboolean sens = gtk_toggle_button_get_active(togglebutton);
270 
271 	gtk_widget_set_sensitive(g_object_get_data(G_OBJECT(data),
272 		"check_editor_menu_sub_menu"), sens);
273 }
274 
275 
plugin_configure(GtkDialog * dialog)276 GtkWidget *plugin_configure(GtkDialog *dialog)
277 {
278 	GtkWidget *label_language, *vbox;
279 	GtkWidget *combo, *check_type, *check_on_open, *check_msgwin, *check_toolbar;
280 	GtkWidget *frame_editor_menu, *check_editor_menu;
281 	GtkWidget *check_editor_menu_sub_menu, *align_editor_menu_sub_menu;
282 	GtkWidget *vbox_interface, *frame_interface, *label_interface;
283 	GtkWidget *vbox_behavior, *frame_behavior, *label_behavior;
284 #ifdef HAVE_ENCHANT_1_5
285 	GtkWidget *entry_dir, *label_dir, *hbox, *button, *image;
286 #endif
287 
288 	vbox = gtk_vbox_new(FALSE, 6);
289 
290 	check_toolbar = gtk_check_button_new_with_label(
291 		_("Show toolbar item to toggle spell checking"));
292 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_toolbar), sc_info->show_toolbar_item);
293 
294 	check_editor_menu = gtk_check_button_new_with_label(
295 		_("Show editor menu item to show spelling suggestions"));
296 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_editor_menu),
297 		sc_info->show_editor_menu_item);
298 
299 	check_editor_menu_sub_menu = gtk_check_button_new_with_label(
300 		_("Show suggestions in a sub menu of the editor menu"));
301 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_editor_menu_sub_menu),
302 		sc_info->show_editor_menu_item_sub_menu);
303 	align_editor_menu_sub_menu = gtk_alignment_new(0.5, 0.5, 1, 1);
304 	gtk_alignment_set_padding(GTK_ALIGNMENT(align_editor_menu_sub_menu), 0, 0, 9, 0);
305 	gtk_container_add(GTK_CONTAINER(align_editor_menu_sub_menu), check_editor_menu_sub_menu);
306 
307 	frame_editor_menu = gtk_frame_new(NULL);
308 	gtk_frame_set_label_widget(GTK_FRAME(frame_editor_menu), check_editor_menu);
309 	gtk_container_set_border_width(GTK_CONTAINER(frame_editor_menu), 3);
310 	gtk_container_add(GTK_CONTAINER(frame_editor_menu), align_editor_menu_sub_menu);
311 	g_signal_connect(check_editor_menu, "toggled",
312 		G_CALLBACK(configure_frame_editor_menu_toggled_cb), dialog);
313 
314 	check_msgwin = gtk_check_button_new_with_label(
315 		_("Print misspelled words and suggestions in the messages window"));
316 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_msgwin), sc_info->use_msgwin);
317 
318 	vbox_interface = gtk_vbox_new(FALSE, 0);
319 	gtk_box_pack_start(GTK_BOX(vbox_interface), check_toolbar, FALSE, FALSE, 3);
320 	gtk_box_pack_start(GTK_BOX(vbox_interface), frame_editor_menu, TRUE, TRUE, 3);
321 	gtk_box_pack_start(GTK_BOX(vbox_interface), check_msgwin, TRUE, TRUE, 3);
322 
323 	label_interface = gtk_label_new(NULL);
324 	gtk_label_set_use_markup(GTK_LABEL(label_interface), TRUE);
325 	gtk_label_set_markup(GTK_LABEL(label_interface), _("<b>Interface</b>"));
326 
327 	frame_interface = gtk_frame_new(NULL);
328 	gtk_frame_set_label_widget(GTK_FRAME(frame_interface), label_interface);
329 	gtk_container_add(GTK_CONTAINER(frame_interface), vbox_interface);
330 	gtk_box_pack_start(GTK_BOX(vbox), frame_interface, FALSE, FALSE, 3);
331 
332 
333 	check_type = gtk_check_button_new_with_label(_("Check spelling while typing"));
334 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_type), sc_info->check_while_typing);
335 
336 	check_on_open = gtk_check_button_new_with_label(_("Check spelling when opening a document"));
337 	gtk_widget_set_tooltip_text(check_on_open,
338 		_("Enabling this option will check every document after it is opened in Geany. "
339 		  "Reloading a document will also trigger a re-check."));
340 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_on_open), sc_info->check_on_document_open);
341 
342 	label_language = gtk_label_new(_("Language to use for the spell check:"));
343 	gtk_misc_set_alignment(GTK_MISC(label_language), 0, 0.5);
344 
345 	combo = gtk_combo_box_text_new();
346 	populate_dict_combo(GTK_COMBO_BOX(combo));
347 
348 	if (sc_info->dicts->len > 20)
349 		gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(combo), 3);
350 	else if (sc_info->dicts->len > 10)
351 		gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(combo), 2);
352 
353 #ifdef HAVE_ENCHANT_1_5
354 	label_dir = gtk_label_new_with_mnemonic(_("_Directory to look for dictionary files:"));
355 	gtk_misc_set_alignment(GTK_MISC(label_dir), 0, 0.5);
356 
357 	entry_dir = gtk_entry_new();
358 	ui_entry_add_clear_icon(GTK_ENTRY(entry_dir));
359 	gtk_label_set_mnemonic_widget(GTK_LABEL(label_dir), entry_dir);
360 	gtk_widget_set_tooltip_text(entry_dir,
361 		_("Read additional dictionary files from this directory. "
362 		  "For now, this only works with hunspell dictionaries. "
363 		  "With Enchant 2.0 or later, the dictionaries are searched "
364 		  "in a subfolder called \"hunspell\". See the plugin's Help for details."));
365 	if (! EMPTY(sc_info->dictionary_dir))
366 		gtk_entry_set_text(GTK_ENTRY(entry_dir), sc_info->dictionary_dir);
367 
368 	button = gtk_button_new();
369 	g_signal_connect(button, "clicked",
370 		G_CALLBACK(dictionary_dir_button_clicked_cb), entry_dir);
371 
372 	image = gtk_image_new_from_stock("gtk-open", GTK_ICON_SIZE_BUTTON);
373 	gtk_container_add(GTK_CONTAINER(button), image);
374 
375 	hbox = gtk_hbox_new(FALSE, 6);
376 	gtk_box_pack_start(GTK_BOX(hbox), entry_dir, TRUE, TRUE, 0);
377 	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
378 
379 	g_object_set_data(G_OBJECT(dialog), "dict_dir", entry_dir);
380 #endif
381 
382 	vbox_behavior = gtk_vbox_new(FALSE, 0);
383 	gtk_box_pack_start(GTK_BOX(vbox_behavior), check_type, FALSE, FALSE, 3);
384 	gtk_box_pack_start(GTK_BOX(vbox_behavior), check_on_open, TRUE, TRUE, 3);
385 	gtk_box_pack_start(GTK_BOX(vbox_behavior), label_language, TRUE, TRUE, 3);
386 	gtk_box_pack_start(GTK_BOX(vbox_behavior), combo, TRUE, TRUE, 3);
387 #ifdef HAVE_ENCHANT_1_5
388 	gtk_box_pack_start(GTK_BOX(vbox_behavior), label_dir, TRUE, TRUE, 3);
389 	gtk_box_pack_start(GTK_BOX(vbox_behavior), hbox, TRUE, TRUE, 3);
390 #endif
391 
392 	label_behavior = gtk_label_new(NULL);
393 	gtk_label_set_use_markup(GTK_LABEL(label_behavior), TRUE);
394 	gtk_label_set_markup(GTK_LABEL(label_behavior), _("<b>Behavior</b>"));
395 
396 	frame_behavior = gtk_frame_new(NULL);
397 	gtk_frame_set_label_widget(GTK_FRAME(frame_behavior), label_behavior);
398 	gtk_container_add(GTK_CONTAINER(frame_behavior), vbox_behavior);
399 	gtk_box_pack_start(GTK_BOX(vbox), frame_behavior, FALSE, FALSE, 3);
400 
401 	g_object_set_data(G_OBJECT(dialog), "combo", combo);
402 	g_object_set_data(G_OBJECT(dialog), "check_type", check_type);
403 	g_object_set_data(G_OBJECT(dialog), "check_on_open", check_on_open);
404 	g_object_set_data(G_OBJECT(dialog), "check_msgwin", check_msgwin);
405 	g_object_set_data(G_OBJECT(dialog), "check_toolbar", check_toolbar);
406 	g_object_set_data(G_OBJECT(dialog), "check_editor_menu", check_editor_menu);
407 	g_object_set_data(G_OBJECT(dialog), "check_editor_menu_sub_menu", check_editor_menu_sub_menu);
408 	g_signal_connect(dialog, "response", G_CALLBACK(configure_response_cb), NULL);
409 
410 	configure_frame_editor_menu_toggled_cb(GTK_TOGGLE_BUTTON(check_editor_menu), dialog);
411 	gtk_widget_show_all(vbox);
412 
413 	return vbox;
414 }
415 
416 
plugin_help(void)417 void plugin_help(void)
418 {
419 /*
420 	gchar *readme = g_build_filename(DOCDIR, "README", NULL);
421 
422 	if (g_file_test(readme, G_FILE_TEST_EXISTS))
423 	{
424 		document_open_file(readme, FALSE, filetypes_index(GEANY_FILETYPES_REST), NULL);
425 	}
426 	else
427 	{
428 		utils_open_browser("http://plugins.geany.org/spellcheck/");
429 	}
430 	g_free(readme);
431 */
432 	utils_open_browser("http://plugins.geany.org/spellcheck.html");
433 }
434 
435 
plugin_cleanup(void)436 void plugin_cleanup(void)
437 {
438 	save_config();
439 
440 	sc_gui_free();
441 	sc_speller_free();
442 
443 	g_free(sc_info->dictionary_dir);
444 	g_free(sc_info->default_language);
445 	g_free(sc_info->config_file);
446 	gtk_widget_destroy(sc_info->menu_item);
447 	g_free(sc_info);
448 }
449