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