1 /*
2  *      gui.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 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28 
29 #include <geanyplugin.h>
30 
31 #include <ctype.h>
32 #include <string.h>
33 
34 
35 #include "gui.h"
36 #include "scplugin.h"
37 #include "speller.h"
38 
39 
40 
41 typedef struct
42 {
43 	gint pos;
44 	GeanyDocument *doc;
45 	/* static storage for the misspelled word under the cursor when using the editing menu */
46 	gchar *word;
47 } SpellClickInfo;
48 static SpellClickInfo clickinfo;
49 
50 typedef struct
51 {
52 	GeanyDocument *doc;
53 	gint line_number;
54 	gint line_count;
55 	guint check_while_typing_idle_source_id;
56 } CheckLineData;
57 static CheckLineData check_line_data;
58 
59 /* Flag to indicate that a callback function will be triggered by generating the appropriate event
60  * but the callback should be ignored. */
61 static gboolean sc_ignore_callback = FALSE;
62 
63 
64 static void perform_check(GeanyDocument *doc);
65 
66 
clear_spellcheck_error_markers(GeanyDocument * doc)67 static void clear_spellcheck_error_markers(GeanyDocument *doc)
68 {
69 	editor_indicator_clear(doc->editor, GEANY_INDICATOR_ERROR);
70 }
71 
72 
print_typing_changed_message(void)73 static void print_typing_changed_message(void)
74 {
75 	if (sc_info->check_while_typing)
76 		ui_set_statusbar(FALSE, _("Spell checking while typing is now enabled"));
77 	else
78 		ui_set_statusbar(FALSE, _("Spell checking while typing is now disabled"));
79 }
80 
81 
toolbar_item_toggled_cb(GtkToggleToolButton * button,gpointer user_data)82 static void toolbar_item_toggled_cb(GtkToggleToolButton *button, gpointer user_data)
83 {
84 	gboolean check_while_typing_changed, check_while_typing;
85 
86 	if (sc_ignore_callback)
87 		return;
88 
89 	check_while_typing = gtk_toggle_tool_button_get_active(button);
90 	check_while_typing_changed = check_while_typing != sc_info->check_while_typing;
91 	sc_info->check_while_typing = check_while_typing;
92 
93 	print_typing_changed_message();
94 
95 	/* force a rescan of the document if 'check while typing' has been turned on and clean
96 	 * errors if it has been turned off */
97 	if (check_while_typing_changed)
98 	{
99 		GeanyDocument *doc = document_get_current();
100 		if (sc_info->check_while_typing)
101 			perform_check(doc);
102 		else
103 			clear_spellcheck_error_markers(doc);
104 	}
105 }
106 
107 
sc_gui_update_toolbar(void)108 void sc_gui_update_toolbar(void)
109 {
110 	/* toolbar item is not requested, so remove the item if it exists */
111 	if (! sc_info->show_toolbar_item)
112 	{
113 		if (sc_info->toolbar_button != NULL)
114 		{
115 			gtk_widget_hide(GTK_WIDGET(sc_info->toolbar_button));
116 		}
117 	}
118 	else
119 	{
120 		if (sc_info->toolbar_button == NULL)
121 		{
122 			sc_info->toolbar_button = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_SPELL_CHECK);
123 
124 			plugin_add_toolbar_item(geany_plugin, sc_info->toolbar_button);
125 			ui_add_document_sensitive(GTK_WIDGET(sc_info->toolbar_button));
126 
127 			g_signal_connect(sc_info->toolbar_button, "toggled",
128 				G_CALLBACK(toolbar_item_toggled_cb), NULL);
129 		}
130 		gtk_widget_show(GTK_WIDGET(sc_info->toolbar_button));
131 
132 		sc_ignore_callback = TRUE;
133 		gtk_toggle_tool_button_set_active(
134 			GTK_TOGGLE_TOOL_BUTTON(sc_info->toolbar_button), sc_info->check_while_typing);
135 		sc_ignore_callback = FALSE;
136 	}
137 }
138 
139 
menu_suggestion_item_activate_cb(GtkMenuItem * menuitem,gpointer gdata)140 static void menu_suggestion_item_activate_cb(GtkMenuItem *menuitem, gpointer gdata)
141 {
142 	const gchar *sugg;
143 	gint startword, endword;
144 	ScintillaObject *sci = clickinfo.doc->editor->sci;
145 
146 	g_return_if_fail(clickinfo.doc != NULL && clickinfo.pos != -1);
147 
148 	startword = scintilla_send_message(sci, SCI_WORDSTARTPOSITION, clickinfo.pos, 0);
149 	endword = scintilla_send_message(sci, SCI_WORDENDPOSITION, clickinfo.pos, 0);
150 
151 	if (startword != endword)
152 	{
153 		gchar *word;
154 
155 		sci_set_selection_start(sci, startword);
156 		sci_set_selection_end(sci, endword);
157 
158 		/* retrieve the old text */
159 		word = sci_get_selection_contents(sci);
160 
161 		/* retrieve the new text */
162 		sugg = gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem))));
163 
164 		/* replace the misspelled word with the chosen suggestion */
165 		sci_replace_sel(sci, sugg);
166 
167 		/* store the replacement for future checks */
168 		sc_speller_store_replacement(word, sugg);
169 
170 		/* remove indicator */
171 		sci_indicator_clear(sci, startword, endword - startword);
172 
173 		g_free(word);
174 	}
175 }
176 
177 
menu_addword_item_activate_cb(GtkMenuItem * menuitem,gpointer gdata)178 static void menu_addword_item_activate_cb(GtkMenuItem *menuitem, gpointer gdata)
179 {
180 	gint startword, endword, i, doc_len;
181 	ScintillaObject *sci;
182 	gboolean ignore = GPOINTER_TO_INT(gdata);
183 	gint click_word_len;
184 
185 	if (clickinfo.doc == NULL || clickinfo.word == NULL || clickinfo.pos == -1)
186 		return;
187 
188 	/* if we ignore the word, we add it to the current session, to ignore it
189 	 * also for further checks*/
190 	if (ignore)
191 		sc_speller_add_word_to_session(clickinfo.word);
192 	/* if we do not ignore the word, we add the word to the personal dictionary */
193 	else
194 		sc_speller_add_word(clickinfo.word);
195 
196 	/* Remove all indicators on the added/ignored word */
197 	sci = clickinfo.doc->editor->sci;
198 	click_word_len = (gint) strlen(clickinfo.word);
199 	doc_len = sci_get_length(sci);
200 	for (i = 0; i < doc_len; i++)
201 	{
202 		startword = scintilla_send_message(sci, SCI_INDICATORSTART, 0, i);
203 		if (startword >= 0)
204 		{
205 			endword = scintilla_send_message(sci, SCI_INDICATOREND, 0, startword);
206 			if (startword == endword)
207 				continue;
208 
209 			if (click_word_len == endword - startword)
210 			{
211 				const gchar *ptr = (const gchar *) scintilla_send_message(sci,
212 					SCI_GETRANGEPOINTER, startword, endword - startword);
213 
214 				if (strncmp(ptr, clickinfo.word, click_word_len) == 0)
215 					sci_indicator_clear(sci, startword, endword - startword);
216 			}
217 
218 			i = endword;
219 		}
220 	}
221 }
222 
223 
224 /* Create a @c GtkImageMenuItem with a stock image and a custom label.
225  * @param stock_id Stock image ID, e.g. @c GTK_STOCK_OPEN.
226  * @param label Menu item label.
227  * @return The new @c GtkImageMenuItem. */
image_menu_item_new(const gchar * stock_id,const gchar * label)228 static GtkWidget *image_menu_item_new(const gchar *stock_id, const gchar *label)
229 {
230 	GtkWidget *item = gtk_image_menu_item_new_with_label(label);
231 	GtkWidget *image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_MENU);
232 
233 	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
234 	gtk_widget_show(image);
235 	return item;
236 }
237 
238 
init_editor_submenu(void)239 static GtkWidget *init_editor_submenu(void)
240 {
241 	if (sc_info->show_editor_menu_item_sub_menu)
242 	{
243 		if (sc_info->edit_menu_sub != NULL && GTK_IS_WIDGET(sc_info->edit_menu_sub))
244 			gtk_widget_destroy(sc_info->edit_menu_sub);
245 
246 		sc_info->edit_menu_sub = gtk_menu_new();
247 		gtk_menu_item_set_submenu(GTK_MENU_ITEM(sc_info->edit_menu), sc_info->edit_menu_sub);
248 
249 		gtk_widget_show(sc_info->edit_menu);
250 		gtk_widget_show(sc_info->edit_menu_sep);
251 		gtk_widget_show(sc_info->edit_menu_sub);
252 
253 		return sc_info->edit_menu_sub;
254 	}
255 	else
256 	{
257 		return geany->main_widgets->editor_menu;
258 	}
259 }
260 
261 
perform_check(GeanyDocument * doc)262 static void perform_check(GeanyDocument *doc)
263 {
264 	clear_spellcheck_error_markers(doc);
265 
266 	if (sc_info->use_msgwin)
267 	{
268 		msgwin_clear_tab(MSG_MESSAGE);
269 		msgwin_switch_tab(MSG_MESSAGE, FALSE);
270 	}
271 
272 	sc_speller_check_document(doc);
273 }
274 
275 
perform_spell_check_cb(GtkWidget * menu_item,GeanyDocument * doc)276 static void perform_spell_check_cb(GtkWidget *menu_item, GeanyDocument *doc)
277 {
278 	perform_check(doc);
279 }
280 
281 
perform_check_delayed_cb(gpointer doc)282 static gboolean perform_check_delayed_cb(gpointer doc)
283 {
284 	perform_check((GeanyDocument*)doc);
285 	return FALSE;
286 }
287 
288 
sc_gui_document_open_cb(GObject * obj,GeanyDocument * doc,gpointer user_data)289 void sc_gui_document_open_cb(GObject *obj, GeanyDocument *doc, gpointer user_data)
290 {
291 	if (sc_info->check_on_document_open && main_is_realized())
292 		g_idle_add(perform_check_delayed_cb, doc);
293 }
294 
295 
menu_item_ref(GtkWidget * menu_item)296 static void menu_item_ref(GtkWidget *menu_item)
297 {
298 	if (! sc_info->show_editor_menu_item_sub_menu)
299 		sc_info->edit_menu_items = g_slist_append(sc_info->edit_menu_items, menu_item);
300 }
301 
302 
update_editor_menu_items(const gchar * search_word,const gchar ** suggs,gsize n_suggs)303 static void update_editor_menu_items(const gchar *search_word, const gchar **suggs, gsize n_suggs)
304 {
305 	GtkWidget *menu_item, *menu, *sub_menu;
306 	GSList *node = NULL;
307 	gchar *label;
308 	gsize i;
309 
310 	menu = init_editor_submenu();
311 	sub_menu = menu;
312 
313 	/* display 5 suggestions on top level, 20 more in sub menu */
314 	for (i = 0; i < MIN(n_suggs, 25); i++)
315 	{
316 		if (i >= 5 && menu == sub_menu)
317 		{
318 			/* create "More..." sub menu */
319 			if (sc_info->show_editor_menu_item_sub_menu)
320 			{
321 				menu_item = gtk_separator_menu_item_new();
322 				gtk_widget_show(menu_item);
323 				gtk_menu_shell_append(GTK_MENU_SHELL(sub_menu), menu_item);
324 			}
325 
326 			menu_item = gtk_menu_item_new_with_label(_("More..."));
327 			gtk_widget_show(menu_item);
328 			gtk_menu_shell_append(GTK_MENU_SHELL(sub_menu), menu_item);
329 			menu_item_ref(menu_item);
330 
331 			sub_menu = gtk_menu_new();
332 			gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), sub_menu);
333 		}
334 		menu_item = gtk_menu_item_new_with_label(suggs[i]);
335 		gtk_widget_show(menu_item);
336 		gtk_container_add(GTK_CONTAINER(sub_menu), menu_item);
337 		if (menu == sub_menu)
338 		{
339 			/* Remember menu items to delete only for the top-level, the nested menu items are
340 			 * destroyed recursively via the sub menu */
341 			menu_item_ref(menu_item);
342 		}
343 		g_signal_connect(menu_item, "activate", G_CALLBACK(menu_suggestion_item_activate_cb), NULL);
344 	}
345 	if (suggs == NULL)
346 	{
347 		menu_item = gtk_menu_item_new_with_label(_("(No Suggestions)"));
348 		gtk_widget_set_sensitive(menu_item, FALSE);
349 		gtk_widget_show(menu_item);
350 		gtk_container_add(GTK_CONTAINER(menu), menu_item);
351 		menu_item_ref(menu_item);
352 	}
353 	if (sc_info->show_editor_menu_item_sub_menu)
354 	{
355 		menu_item = gtk_separator_menu_item_new();
356 		gtk_widget_show(menu_item);
357 		gtk_container_add(GTK_CONTAINER(menu), menu_item);
358 	}
359 
360 	label = g_strdup_printf(_("Add \"%s\" to Dictionary"), search_word);
361 	menu_item = image_menu_item_new(GTK_STOCK_ADD, label);
362 	gtk_widget_show(menu_item);
363 	gtk_container_add(GTK_CONTAINER(menu), menu_item);
364 	menu_item_ref(menu_item);
365 	g_signal_connect(menu_item, "activate",
366 		G_CALLBACK(menu_addword_item_activate_cb), GINT_TO_POINTER(FALSE));
367 
368 	menu_item = image_menu_item_new(GTK_STOCK_REMOVE, _("Ignore All"));
369 	gtk_widget_show(menu_item);
370 	gtk_container_add(GTK_CONTAINER(menu), menu_item);
371 	menu_item_ref(menu_item);
372 	g_signal_connect(menu_item, "activate",
373 		G_CALLBACK(menu_addword_item_activate_cb), GINT_TO_POINTER(TRUE));
374 
375 	g_free(label);
376 
377 	/* re-order menu items: above all menu items are append but for the top-level menu items
378 	 * we want them to appear at the top of the editor menu */
379 	if (! sc_info->show_editor_menu_item_sub_menu)
380 	{
381 		gpointer child;
382 		/* final separator */
383 		menu_item = gtk_separator_menu_item_new();
384 		gtk_widget_show(menu_item);
385 		gtk_container_add(GTK_CONTAINER(menu), menu_item);
386 		menu_item_ref(menu_item);
387 		/* re-order */
388 		i = 0;
389 		foreach_slist(node, sc_info->edit_menu_items)
390 		{
391 			child = node->data;
392 			gtk_menu_reorder_child(GTK_MENU(menu), GTK_WIDGET(child), i);
393 			i++;
394 		}
395 	}
396 }
397 
398 
sc_gui_update_editor_menu_cb(GObject * obj,const gchar * word,gint pos,GeanyDocument * doc,gpointer user_data)399 void sc_gui_update_editor_menu_cb(GObject *obj, const gchar *word, gint pos,
400 								  GeanyDocument *doc, gpointer user_data)
401 {
402 	gchar *search_word;
403 
404 	g_return_if_fail(doc != NULL && doc->is_valid);
405 
406 	/* hide the submenu in any case, we will reshow it again if we actually found something */
407 	if (sc_info->edit_menu != NULL)
408 		gtk_widget_hide(sc_info->edit_menu);
409 	if (sc_info->edit_menu_sep != NULL)
410 		gtk_widget_hide(sc_info->edit_menu_sep);
411 	/* clean previously added items to the editor menu */
412 	if (sc_info->edit_menu_items != NULL)
413 	{
414 		g_slist_free_full(sc_info->edit_menu_items, (GDestroyNotify) gtk_widget_destroy);
415 		sc_info->edit_menu_items = NULL;
416 	}
417 
418 	if (! sc_info->show_editor_menu_item)
419 		return;
420 
421 	/* if we have a selection, prefer it over the current word */
422 	if (sci_has_selection(doc->editor->sci))
423 		search_word = sci_get_selection_contents(doc->editor->sci);
424 	else
425 		search_word = g_strdup(word);
426 
427 	/* ignore numbers or words starting with digits and non-text */
428 	if (EMPTY(search_word) || isdigit(*search_word) || ! sc_speller_is_text(doc, pos))
429 	{
430 		g_free(search_word);
431 		return;
432 	}
433 
434 	/* ignore too long search words */
435 	if (strlen(search_word) > 100)
436 	{
437 		GtkWidget *menu_item, *menu;
438 
439 		menu = init_editor_submenu();
440 		menu_item = gtk_menu_item_new_with_label(
441 			_("Search term is too long to provide\nspelling suggestions in the editor menu."));
442 		gtk_widget_set_sensitive(menu_item, FALSE);
443 		gtk_widget_show(menu_item);
444 		gtk_container_add(GTK_CONTAINER(menu), menu_item);
445 		menu_item_ref(menu_item);
446 
447 		menu_item = gtk_menu_item_new_with_label(_("Perform Spell Check"));
448 		gtk_widget_show(menu_item);
449 		gtk_container_add(GTK_CONTAINER(menu), menu_item);
450 		menu_item_ref(menu_item);
451 		g_signal_connect(menu_item, "activate", G_CALLBACK(perform_spell_check_cb), doc);
452 
453 		g_free(search_word);
454 		return;
455 	}
456 
457 	if (sc_speller_dict_check(search_word) != 0)
458 	{
459 		gsize n_suggs;
460 		gchar **suggs;
461 
462 		suggs = sc_speller_dict_suggest(search_word, &n_suggs);
463 
464 		clickinfo.pos = pos;
465 		clickinfo.doc = doc;
466 		setptr(clickinfo.word, search_word);
467 
468 		update_editor_menu_items(search_word, (const gchar**) suggs, n_suggs);
469 
470 		if (suggs != NULL)
471 			sc_speller_dict_free_string_list(suggs);
472 	}
473 	else
474 	{
475 		g_free(search_word); /* search_word is free'd via clickinfo.word otherwise */
476 	}
477 }
478 
479 
indicator_clear_on_line(GeanyDocument * doc,gint line_number)480 static void indicator_clear_on_line(GeanyDocument *doc, gint line_number)
481 {
482 	gint start_pos, length;
483 
484 	g_return_if_fail(doc != NULL);
485 
486 	start_pos = sci_get_position_from_line(doc->editor->sci, line_number);
487 	length = sci_get_line_length(doc->editor->sci, line_number);
488 
489 	sci_indicator_set(doc->editor->sci, GEANY_INDICATOR_ERROR);
490 	sci_indicator_clear(doc->editor->sci, start_pos, length);
491 }
492 
493 
check_lines(gpointer data)494 static gboolean check_lines(gpointer data)
495 {
496 	GeanyDocument *doc = check_line_data.doc;
497 
498 	/* since we're in an timeout callback, the document may have been closed */
499 	if (DOC_VALID (doc))
500 	{
501 		gint line_number = check_line_data.line_number;
502 		gint line_count = check_line_data.line_count;
503 		gint i;
504 
505 		for (i = 0; i < line_count; i++)
506 		{
507 			indicator_clear_on_line(doc, line_number);
508 			if (sc_speller_process_line(doc, line_number) != 0)
509 			{
510 				if (sc_info->use_msgwin)
511 					msgwin_switch_tab(MSG_MESSAGE, FALSE);
512 			}
513 			line_number++;
514 		}
515 	}
516 	check_line_data.check_while_typing_idle_source_id = 0;
517 	return FALSE;
518 }
519 
520 
need_delay(void)521 static gboolean need_delay(void)
522 {
523 	static gint64 time_prev = 0; /* time in microseconds */
524 	gint64 time_now;
525 	GTimeVal t;
526 	const gint timeout = 500; /* delay in milliseconds */
527 	gboolean ret = FALSE;
528 
529 	g_get_current_time(&t);
530 
531 	time_now = ((gint64) t.tv_sec * G_USEC_PER_SEC) + t.tv_usec;
532 
533 	/* delay keypresses for 0.5 seconds */
534 	if (time_now < (time_prev + (timeout * 1000)))
535 		return TRUE;
536 
537 	if (check_line_data.check_while_typing_idle_source_id == 0)
538 	{
539 		check_line_data.check_while_typing_idle_source_id =
540 			plugin_timeout_add(geany_plugin, timeout, check_lines, NULL);
541 		ret = TRUE;
542 	}
543 
544 	/* set current time for the next key press */
545 	time_prev = time_now;
546 
547 	return ret;
548 }
549 
550 
check_on_text_changed(GeanyDocument * doc,gint position,gint lines_added)551 static void check_on_text_changed(GeanyDocument *doc, gint position, gint lines_added)
552 {
553 	gint line_number;
554 	gint line_count;
555 
556 	/* Iterating over all lines which changed as indicated by lines_added. lines_added is 0
557 	 * if only one line has changed, in this case set line_count to 1. Otherwise, iterating over all
558 	 * new lines makes spell checking work for pasted text. */
559 	line_count = MAX(1, lines_added);
560 
561 	line_number = sci_get_line_from_position(doc->editor->sci, position);
562 	/* TODO: storing these information in the global check_line_data struct isn't that good.
563 	 * The data gets overwritten when a new line is inserted and so there is a chance that the
564 	 * previous line is not checked to the end. One solution could be to simple maintain a list
565 	 * of line numbers which needs to be checked and do this in the timeout handler. */
566 	check_line_data.doc = doc;
567 	check_line_data.line_number = line_number;
568 	check_line_data.line_count = line_count;
569 
570 	/* check only once in a while */
571 	if (! need_delay())
572 		check_lines(NULL);
573 }
574 
575 
sc_gui_editor_notify(GObject * object,GeanyEditor * editor,SCNotification * nt,gpointer data)576 gboolean sc_gui_editor_notify(GObject *object, GeanyEditor *editor,
577 							  SCNotification *nt, gpointer data)
578 {
579 	if (! sc_info->check_while_typing)
580 		return FALSE;
581 
582 	if (nt->modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT))
583 	{
584 		check_on_text_changed(editor->document, nt->position, nt->linesAdded);
585 	}
586 
587 	return FALSE;
588 }
589 
590 
591 #if ! GTK_CHECK_VERSION(2, 16, 0)
gtk_menu_item_set_label(GtkMenuItem * menu_item,const gchar * label)592 static void gtk_menu_item_set_label(GtkMenuItem *menu_item, const gchar *label)
593 {
594 	if (GTK_BIN(menu_item)->child != NULL)
595 	{
596 		GtkWidget *child = GTK_BIN(menu_item)->child;
597 
598 		if (GTK_IS_LABEL(child))
599 			gtk_label_set_text(GTK_LABEL(child), label);
600 	}
601 }
602 #endif
603 
604 
update_labels(void)605 static void update_labels(void)
606 {
607 	gchar *label;
608 
609 	label = g_strdup_printf(_("Default (%s)"),
610 		(sc_info->default_language != NULL) ? sc_info->default_language : _("unknown"));
611 
612 	gtk_menu_item_set_label(GTK_MENU_ITEM(sc_info->submenu_item_default), label);
613 
614 	g_free(label);
615 
616 #if GTK_CHECK_VERSION(2, 12, 0)
617 	if (sc_info->toolbar_button != NULL)
618 	{
619 		gchar *text = g_strdup_printf(
620 			_("Toggle spell check while typing (current language: %s)"),
621 			(sc_info->default_language != NULL) ? sc_info->default_language : _("unknown"));
622 		gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(sc_info->toolbar_button), text);
623 		g_free(text);
624 	}
625 #endif
626 }
627 
628 
menu_item_toggled_cb(GtkCheckMenuItem * menuitem,gpointer gdata)629 static void menu_item_toggled_cb(GtkCheckMenuItem *menuitem, gpointer gdata)
630 {
631 	GeanyDocument *doc;
632 
633 	if (sc_ignore_callback)
634 		return;
635 
636 	if (menuitem != NULL &&
637 		GTK_IS_CHECK_MENU_ITEM(menuitem) &&
638 		! gtk_check_menu_item_get_active(menuitem))
639 	{
640 		return;
641 	}
642 	doc = document_get_current();
643 
644 	/* Another language was chosen from the menu item, so make it default for this session. */
645     if (gdata != NULL)
646 	{
647 		setptr(sc_info->default_language, g_strdup(gdata));
648 		sc_speller_reinit_enchant_dict();
649 		sc_gui_update_menu();
650 		update_labels();
651 	}
652 
653 	perform_check(doc);
654 }
655 
656 
sc_gui_kb_run_activate_cb(guint key_id)657 void sc_gui_kb_run_activate_cb(guint key_id)
658 {
659 	perform_check(document_get_current());
660 }
661 
662 
sc_gui_kb_toggle_typing_activate_cb(guint key_id)663 void sc_gui_kb_toggle_typing_activate_cb(guint key_id)
664 {
665 	sc_info->check_while_typing = ! sc_info->check_while_typing;
666 
667 	print_typing_changed_message();
668 
669 	sc_gui_update_toolbar();
670 }
671 
672 
free_editor_menu_items(void)673 static void free_editor_menu_items(void)
674 {
675 	if (sc_info->edit_menu != NULL)
676 	{
677 		gtk_widget_destroy(sc_info->edit_menu);
678 		sc_info->edit_menu = NULL;
679 	}
680 	if (sc_info->edit_menu_sep != NULL)
681 	{
682 		gtk_widget_destroy(sc_info->edit_menu_sep);
683 		sc_info->edit_menu_sep = NULL;
684 	}
685 	if (sc_info->edit_menu_items != NULL)
686 	{
687 		g_slist_free_full(sc_info->edit_menu_items, (GDestroyNotify) gtk_widget_destroy);
688 		sc_info->edit_menu_items = NULL;
689 	}
690 }
691 
692 
sc_gui_recreate_editor_menu(void)693 void sc_gui_recreate_editor_menu(void)
694 {
695 	free_editor_menu_items();
696 	if (sc_info->show_editor_menu_item_sub_menu)
697 	{
698 		sc_info->edit_menu = ui_image_menu_item_new(GTK_STOCK_SPELL_CHECK, _("Spelling Suggestions"));
699 		gtk_container_add(GTK_CONTAINER(geany->main_widgets->editor_menu), sc_info->edit_menu);
700 		gtk_menu_reorder_child(GTK_MENU(geany->main_widgets->editor_menu), sc_info->edit_menu, 0);
701 
702 		sc_info->edit_menu_sep = gtk_separator_menu_item_new();
703 		gtk_container_add(GTK_CONTAINER(geany->main_widgets->editor_menu), sc_info->edit_menu_sep);
704 		gtk_menu_reorder_child(GTK_MENU(geany->main_widgets->editor_menu), sc_info->edit_menu_sep, 1);
705 
706 		gtk_widget_show_all(sc_info->edit_menu);
707 	}
708 }
709 
710 
sc_gui_update_menu(void)711 void sc_gui_update_menu(void)
712 {
713 	GtkWidget *menu_item;
714 	guint i;
715 	static gboolean need_init = TRUE;
716 	GSList *group = NULL;
717 	gchar *label;
718 
719 	if (need_init)
720 	{
721 		gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu), sc_info->menu_item);
722 		need_init = FALSE;
723 	}
724 
725 	if (sc_info->main_menu != NULL)
726 		gtk_widget_destroy(sc_info->main_menu);
727 
728 	sc_info->main_menu = gtk_menu_new();
729 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(sc_info->menu_item), sc_info->main_menu);
730 
731 	sc_info->submenu_item_default = gtk_menu_item_new_with_label(NULL);
732 	gtk_container_add(GTK_CONTAINER(sc_info->main_menu), sc_info->submenu_item_default);
733 	g_signal_connect(sc_info->submenu_item_default, "activate",
734 		G_CALLBACK(menu_item_toggled_cb), NULL);
735 
736 	update_labels();
737 
738 	menu_item = gtk_separator_menu_item_new();
739 	gtk_container_add(GTK_CONTAINER(sc_info->main_menu), menu_item);
740 
741 	sc_ignore_callback = TRUE;
742 	for (i = 0; i < sc_info->dicts->len; i++)
743 	{
744 		label = g_ptr_array_index(sc_info->dicts, i);
745 		menu_item = gtk_radio_menu_item_new_with_label(group, label);
746 		group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menu_item));
747 		if (utils_str_equal(sc_info->default_language, label))
748 			gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item), TRUE);
749 		gtk_container_add(GTK_CONTAINER(sc_info->main_menu), menu_item);
750 		g_signal_connect(menu_item, "toggled", G_CALLBACK(menu_item_toggled_cb), label);
751 	}
752 	sc_ignore_callback = FALSE;
753 	gtk_widget_show_all(sc_info->main_menu);
754 }
755 
756 
sc_gui_init(void)757 void sc_gui_init(void)
758 {
759 	clickinfo.word = NULL;
760 	sc_info->edit_menu_items = NULL;
761 	sc_info->edit_menu = NULL;
762 	sc_info->edit_menu_sep = NULL;
763 	sc_info->edit_menu_items = NULL;
764 
765 	sc_gui_recreate_editor_menu();
766 }
767 
768 
sc_gui_free(void)769 void sc_gui_free(void)
770 {
771 	g_free(clickinfo.word);
772 	if (check_line_data.check_while_typing_idle_source_id != 0)
773 		g_source_remove(check_line_data.check_while_typing_idle_source_id);
774 	if (sc_info->toolbar_button != NULL)
775 		gtk_widget_destroy(GTK_WIDGET(sc_info->toolbar_button));
776 	free_editor_menu_items();
777 }
778