1 /*
2 * e-html-editor-spell-dialog.c
3 *
4 * Copyright (C) 2012 Dan Vrátil <dvratil@redhat.com>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of version 2 of the GNU Lesser General Public
8 * License as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
19 */
20
21 #include "evolution-config.h"
22
23 #include "e-html-editor-spell-check-dialog.h"
24
25 #include <glib/gi18n-lib.h>
26 #include <enchant.h>
27
28 #include "e-spell-checker.h"
29 #include "e-spell-dictionary.h"
30
31 #include "e-dialog-widgets.h"
32
33 #define E_HTML_EDITOR_SPELL_CHECK_DIALOG_GET_PRIVATE(obj) \
34 (G_TYPE_INSTANCE_GET_PRIVATE \
35 ((obj), E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG, EHTMLEditorSpellCheckDialogPrivate))
36
37 struct _EHTMLEditorSpellCheckDialogPrivate {
38 GtkWidget *add_word_button;
39 GtkWidget *back_button;
40 GtkWidget *dictionary_combo;
41 GtkWidget *ignore_button;
42 GtkWidget *replace_button;
43 GtkWidget *replace_all_button;
44 GtkWidget *skip_button;
45 GtkWidget *suggestion_label;
46 GtkWidget *tree_view;
47
48 gchar *word;
49 ESpellDictionary *current_dict;
50 };
51
52 enum {
53 COLUMN_NAME,
54 COLUMN_DICTIONARY,
55 NUM_COLUMNS
56 };
57
G_DEFINE_TYPE(EHTMLEditorSpellCheckDialog,e_html_editor_spell_check_dialog,E_TYPE_HTML_EDITOR_DIALOG)58 G_DEFINE_TYPE (
59 EHTMLEditorSpellCheckDialog,
60 e_html_editor_spell_check_dialog,
61 E_TYPE_HTML_EDITOR_DIALOG)
62
63 static void
64 html_editor_spell_check_dialog_set_word (EHTMLEditorSpellCheckDialog *dialog,
65 const gchar *word)
66 {
67 EHTMLEditor *editor;
68 EContentEditor *cnt_editor;
69 GtkTreeView *tree_view;
70 GtkListStore *store;
71 gchar *markup;
72 GList *list, *link;
73 gboolean empty;
74
75 if (word == NULL)
76 return;
77
78 if (dialog->priv->word != word) {
79 g_free (dialog->priv->word);
80 dialog->priv->word = g_strdup (word);
81 }
82
83 markup = g_strdup_printf (_("<b>Suggestions for “%s”</b>"), word);
84 gtk_label_set_markup (
85 GTK_LABEL (dialog->priv->suggestion_label), markup);
86 g_free (markup);
87
88 tree_view = GTK_TREE_VIEW (dialog->priv->tree_view);
89 store = GTK_LIST_STORE (gtk_tree_view_get_model (tree_view));
90 gtk_list_store_clear (store);
91
92 list = e_spell_dictionary_get_suggestions (
93 dialog->priv->current_dict, word, -1);
94
95 empty = list == NULL;
96
97 for (link = list; link != NULL; link = g_list_next (link)) {
98 GtkTreeIter iter;
99 gchar *suggestion = link->data;
100
101 gtk_list_store_append (store, &iter);
102 gtk_list_store_set (store, &iter, 0, suggestion, -1);
103 }
104
105 gtk_widget_set_sensitive (dialog->priv->replace_button, !empty);
106 gtk_widget_set_sensitive (dialog->priv->replace_all_button, !empty);
107
108 if (!empty) {
109 GtkTreeSelection *tree_selection;
110
111 /* Select the first suggestion */
112 tree_selection = gtk_tree_view_get_selection (
113 GTK_TREE_VIEW (dialog->priv->tree_view));
114 gtk_tree_selection_select_path (tree_selection, gtk_tree_path_new_first ());
115 }
116
117 g_list_free_full (list, (GDestroyNotify) g_free);
118
119 /* We give focus to WebKit so that the currently selected word
120 * is highlited. Without focus selection is not visible (at
121 * least with my default color scheme). The focus in fact is not
122 * given to WebKit, because this dialog is modal, but it satisfies
123 * it in a way that it paints the selection :) */
124 editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
125 cnt_editor = e_html_editor_get_content_editor (editor);
126 gtk_widget_grab_focus (GTK_WIDGET (cnt_editor));
127 }
128
129 static gboolean
html_editor_spell_check_dialog_next(EHTMLEditorSpellCheckDialog * dialog)130 html_editor_spell_check_dialog_next (EHTMLEditorSpellCheckDialog *dialog)
131 {
132 EHTMLEditor *editor;
133 EContentEditor *cnt_editor;
134 gchar *next_word;
135
136 editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
137 cnt_editor = e_html_editor_get_content_editor (editor);
138
139 next_word = e_content_editor_spell_check_next_word (cnt_editor, dialog->priv->word);
140 if (next_word && *next_word) {
141 html_editor_spell_check_dialog_set_word (dialog, next_word);
142 g_free (next_word);
143 return TRUE;
144 }
145 g_free (next_word);
146
147 /* Close the dialog */
148 gtk_widget_hide (GTK_WIDGET (dialog));
149 return FALSE;
150 }
151
152 static gboolean
html_editor_spell_check_dialog_prev(EHTMLEditorSpellCheckDialog * dialog)153 html_editor_spell_check_dialog_prev (EHTMLEditorSpellCheckDialog *dialog)
154 {
155 EHTMLEditor *editor;
156 EContentEditor *cnt_editor;
157 gchar *prev_word;
158
159 editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
160 cnt_editor = e_html_editor_get_content_editor (editor);
161
162 prev_word = e_content_editor_spell_check_prev_word (cnt_editor, dialog->priv->word);
163 if (prev_word && *prev_word) {
164 html_editor_spell_check_dialog_set_word (dialog, prev_word);
165 g_free (prev_word);
166 return TRUE;
167 }
168 g_free (prev_word);
169
170 /* Close the dialog */
171 gtk_widget_hide (GTK_WIDGET (dialog));
172 return FALSE;
173 }
174
175 static gboolean
html_editor_spell_check_dialog_next_idle_cb(gpointer user_data)176 html_editor_spell_check_dialog_next_idle_cb (gpointer user_data)
177 {
178 EHTMLEditorSpellCheckDialog *dialog = user_data;
179
180 g_return_val_if_fail (E_IS_HTML_EDITOR_SPELL_CHECK_DIALOG (dialog), FALSE);
181
182 html_editor_spell_check_dialog_next (dialog);
183 g_object_unref (dialog);
184
185 return FALSE;
186 }
187
188 static void
html_editor_spell_check_dialog_replace(EHTMLEditorSpellCheckDialog * dialog)189 html_editor_spell_check_dialog_replace (EHTMLEditorSpellCheckDialog *dialog)
190 {
191 EHTMLEditor *editor;
192 EContentEditor *cnt_editor;
193 GtkTreeModel *model;
194 GtkTreeSelection *selection;
195 GtkTreeIter iter;
196 gchar *replacement;
197
198 editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
199 cnt_editor = e_html_editor_get_content_editor (editor);
200
201 selection = gtk_tree_view_get_selection (
202 GTK_TREE_VIEW (dialog->priv->tree_view));
203 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
204 return;
205 gtk_tree_model_get (model, &iter, 0, &replacement, -1);
206
207 e_content_editor_replace (cnt_editor, replacement);
208
209 g_free (replacement);
210
211 g_idle_add (html_editor_spell_check_dialog_next_idle_cb, g_object_ref (dialog));
212 }
213
214 static void
html_editor_spell_check_dialog_replace_all(EHTMLEditorSpellCheckDialog * dialog)215 html_editor_spell_check_dialog_replace_all (EHTMLEditorSpellCheckDialog *dialog)
216 {
217 EHTMLEditor *editor;
218 EContentEditor *cnt_editor;
219 GtkTreeModel *model;
220 GtkTreeSelection *selection;
221 GtkTreeIter iter;
222 gchar *replacement;
223
224 selection = gtk_tree_view_get_selection (
225 GTK_TREE_VIEW (dialog->priv->tree_view));
226 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
227 return;
228 gtk_tree_model_get (model, &iter, 0, &replacement, -1);
229
230 editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
231 cnt_editor = e_html_editor_get_content_editor (editor);
232
233 e_content_editor_replace_all (
234 cnt_editor,
235 E_CONTENT_EDITOR_FIND_CASE_INSENSITIVE |
236 E_CONTENT_EDITOR_FIND_WRAP_AROUND,
237 dialog->priv->word,
238 replacement);
239
240 g_free (replacement);
241
242 g_idle_add (html_editor_spell_check_dialog_next_idle_cb, g_object_ref (dialog));
243 }
244
245 static void
html_editor_spell_check_dialog_ignore(EHTMLEditorSpellCheckDialog * dialog)246 html_editor_spell_check_dialog_ignore (EHTMLEditorSpellCheckDialog *dialog)
247 {
248 if (dialog->priv->word == NULL)
249 return;
250 e_spell_dictionary_ignore_word (
251 dialog->priv->current_dict, dialog->priv->word, -1);
252
253 html_editor_spell_check_dialog_next (dialog);
254 }
255
256 static void
html_editor_spell_check_dialog_learn(EHTMLEditorSpellCheckDialog * dialog)257 html_editor_spell_check_dialog_learn (EHTMLEditorSpellCheckDialog *dialog)
258 {
259 if (dialog->priv->word == NULL)
260 return;
261
262 e_spell_dictionary_learn_word (dialog->priv->current_dict, dialog->priv->word, -1);
263
264 html_editor_spell_check_dialog_next (dialog);
265 }
266
267 static void
html_editor_spell_check_dialog_set_dictionary(EHTMLEditorSpellCheckDialog * dialog)268 html_editor_spell_check_dialog_set_dictionary (EHTMLEditorSpellCheckDialog *dialog)
269 {
270 GtkComboBox *combo_box;
271 GtkTreeModel *model;
272 GtkTreeIter iter;
273 ESpellDictionary *dictionary;
274
275 combo_box = GTK_COMBO_BOX (dialog->priv->dictionary_combo);
276 if (gtk_combo_box_get_active_iter (combo_box, &iter)) {
277 model = gtk_combo_box_get_model (combo_box);
278
279 gtk_tree_model_get (model, &iter, 1, &dictionary, -1);
280
281 dialog->priv->current_dict = dictionary;
282
283 /* Update suggestions */
284 html_editor_spell_check_dialog_set_word (dialog, dialog->priv->word);
285 }
286 }
287
288 static void
html_editor_spell_check_dialog_show(GtkWidget * widget)289 html_editor_spell_check_dialog_show (GtkWidget *widget)
290 {
291 EHTMLEditor *editor;
292 EContentEditor *cnt_editor;
293 EHTMLEditorSpellCheckDialog *dialog;
294
295 dialog = E_HTML_EDITOR_SPELL_CHECK_DIALOG (widget);
296 editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
297 cnt_editor = e_html_editor_get_content_editor (editor);
298
299 g_free (dialog->priv->word);
300 dialog->priv->word = NULL;
301
302 e_content_editor_on_dialog_open (cnt_editor, E_CONTENT_EDITOR_DIALOG_SPELLCHECK);
303
304 /* Select the first word or quit */
305 if (html_editor_spell_check_dialog_next (dialog)) {
306 GTK_WIDGET_CLASS (e_html_editor_spell_check_dialog_parent_class)->show (widget);
307 } else {
308 e_content_editor_on_dialog_close (cnt_editor, E_CONTENT_EDITOR_DIALOG_SPELLCHECK);
309 }
310 }
311
312 static void
html_editor_spell_check_dialog_hide(GtkWidget * widget)313 html_editor_spell_check_dialog_hide (GtkWidget *widget)
314 {
315 EContentEditor *cnt_editor;
316 EHTMLEditor *editor;
317 EHTMLEditorSpellCheckDialog *dialog = E_HTML_EDITOR_SPELL_CHECK_DIALOG (widget);
318
319 editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
320 cnt_editor = e_html_editor_get_content_editor (editor);
321
322 e_content_editor_on_dialog_close (cnt_editor, E_CONTENT_EDITOR_DIALOG_SPELLCHECK);
323
324 /* Chain up to parent implementation */
325 GTK_WIDGET_CLASS (e_html_editor_spell_check_dialog_parent_class)->hide (widget);
326 }
327
328 static void
html_editor_spell_check_dialog_finalize(GObject * object)329 html_editor_spell_check_dialog_finalize (GObject *object)
330 {
331 EHTMLEditorSpellCheckDialogPrivate *priv;
332
333 priv = E_HTML_EDITOR_SPELL_CHECK_DIALOG_GET_PRIVATE (object);
334
335 g_free (priv->word);
336
337 /* Chain up to parent's finalize() method. */
338 G_OBJECT_CLASS (e_html_editor_spell_check_dialog_parent_class)->finalize (object);
339 }
340
341 static void
html_editor_spell_check_dialog_constructed(GObject * object)342 html_editor_spell_check_dialog_constructed (GObject *object)
343 {
344 EHTMLEditorSpellCheckDialog *dialog;
345
346 /* Chain up to parent's constructed() method. */
347 G_OBJECT_CLASS (e_html_editor_spell_check_dialog_parent_class)->constructed (object);
348
349 dialog = E_HTML_EDITOR_SPELL_CHECK_DIALOG (object);
350
351 e_html_editor_spell_check_dialog_update_dictionaries (dialog);
352 }
353
354 static void
e_html_editor_spell_check_dialog_class_init(EHTMLEditorSpellCheckDialogClass * class)355 e_html_editor_spell_check_dialog_class_init (EHTMLEditorSpellCheckDialogClass *class)
356 {
357 GtkWidgetClass *widget_class;
358 GObjectClass *object_class;
359
360 g_type_class_add_private (
361 class, sizeof (EHTMLEditorSpellCheckDialogPrivate));
362
363 object_class = G_OBJECT_CLASS (class);
364 object_class->finalize = html_editor_spell_check_dialog_finalize;
365 object_class->constructed = html_editor_spell_check_dialog_constructed;
366
367 widget_class = GTK_WIDGET_CLASS (class);
368 widget_class->show = html_editor_spell_check_dialog_show;
369 widget_class->hide = html_editor_spell_check_dialog_hide;
370 }
371
372 static void
e_html_editor_spell_check_dialog_init(EHTMLEditorSpellCheckDialog * dialog)373 e_html_editor_spell_check_dialog_init (EHTMLEditorSpellCheckDialog *dialog)
374 {
375 GtkWidget *widget;
376 GtkGrid *main_layout;
377 GtkListStore *store;
378 GtkTreeViewColumn *column;
379 GtkCellRenderer *renderer;
380
381 dialog->priv = E_HTML_EDITOR_SPELL_CHECK_DIALOG_GET_PRIVATE (dialog);
382
383 main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog));
384
385 /* == Suggestions == */
386 widget = gtk_label_new ("");
387 gtk_label_set_markup (GTK_LABEL (widget), _("<b>Suggestions</b>"));
388 gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
389 gtk_grid_attach (main_layout, widget, 0, 0, 2, 1);
390 dialog->priv->suggestion_label = widget;
391
392 /* Tree view */
393 widget = gtk_tree_view_new ();
394 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (widget), FALSE);
395 gtk_widget_set_vexpand (widget, TRUE);
396 gtk_widget_set_hexpand (widget, TRUE);
397 dialog->priv->tree_view = widget;
398
399 /* Column */
400 column = gtk_tree_view_column_new_with_attributes (
401 "", gtk_cell_renderer_text_new (), "text", 0, NULL);
402 gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column);
403
404 /* Store */
405 store = gtk_list_store_new (1, G_TYPE_STRING);
406 gtk_tree_view_set_model (
407 GTK_TREE_VIEW (widget), GTK_TREE_MODEL (store));
408
409 /* Scrolled Window */
410 widget = gtk_scrolled_window_new (NULL, NULL);
411 gtk_widget_set_size_request (widget, 150, -1);
412 gtk_scrolled_window_set_policy (
413 GTK_SCROLLED_WINDOW (widget),
414 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
415 gtk_scrolled_window_set_shadow_type (
416 GTK_SCROLLED_WINDOW (widget),
417 GTK_SHADOW_ETCHED_IN);
418 gtk_container_add (GTK_CONTAINER (widget), dialog->priv->tree_view);
419 gtk_grid_attach (main_layout, widget, 0, 1, 1, 5);
420
421 /* Replace */
422 widget = e_dialog_button_new_with_icon ("edit-find-replace", _("Replace"));
423 gtk_grid_attach (main_layout, widget, 1, 1, 1, 1);
424 dialog->priv->replace_button = widget;
425
426 g_signal_connect_swapped (
427 widget, "clicked",
428 G_CALLBACK (html_editor_spell_check_dialog_replace), dialog);
429
430 /* Replace All */
431 widget = gtk_button_new_with_mnemonic (_("Replace All"));
432 gtk_grid_attach (main_layout, widget, 1, 2, 1, 1);
433 dialog->priv->replace_all_button = widget;
434
435 g_signal_connect_swapped (
436 widget, "clicked",
437 G_CALLBACK (html_editor_spell_check_dialog_replace_all), dialog);
438
439 /* Ignore */
440 widget = e_dialog_button_new_with_icon ("edit-clear", _("Ignore"));
441 gtk_grid_attach (main_layout, widget, 1, 3, 1, 1);
442 dialog->priv->ignore_button = widget;
443
444 g_signal_connect_swapped (
445 widget, "clicked",
446 G_CALLBACK (html_editor_spell_check_dialog_ignore), dialog);
447
448 /* Skip */
449 widget = e_dialog_button_new_with_icon ("go-next", _("Skip"));
450 gtk_grid_attach (main_layout, widget, 1, 4, 1, 1);
451 dialog->priv->skip_button = widget;
452
453 g_signal_connect_swapped (
454 widget, "clicked",
455 G_CALLBACK (html_editor_spell_check_dialog_next), dialog);
456
457 /* Back */
458 widget = e_dialog_button_new_with_icon ("go-previous", _("Back"));
459 gtk_grid_attach (main_layout, widget, 1, 5, 1, 1);
460
461 g_signal_connect_swapped (
462 widget, "clicked",
463 G_CALLBACK (html_editor_spell_check_dialog_prev), dialog);
464
465 /* Dictionary label */
466 widget = gtk_label_new ("");
467 gtk_label_set_markup (GTK_LABEL (widget), _("<b>Dictionary</b>"));
468 gtk_misc_set_alignment (GTK_MISC (widget), 0, 0);
469 gtk_grid_attach (main_layout, widget, 0, 6, 2, 1);
470
471 /* Dictionaries combo */
472 widget = gtk_combo_box_new ();
473 gtk_grid_attach (main_layout, widget, 0, 7, 1, 1);
474 dialog->priv->dictionary_combo = widget;
475
476 renderer = gtk_cell_renderer_text_new ();
477 gtk_cell_layout_pack_start (
478 GTK_CELL_LAYOUT (widget), renderer, TRUE);
479 gtk_cell_layout_add_attribute (
480 GTK_CELL_LAYOUT (widget), renderer, "text", 0);
481
482 g_signal_connect_swapped (
483 widget, "changed",
484 G_CALLBACK (html_editor_spell_check_dialog_set_dictionary), dialog);
485
486 /* Add Word button */
487 widget = e_dialog_button_new_with_icon ("list-add", _("Add word"));
488 gtk_grid_attach (main_layout, widget, 1, 7, 1, 1);
489 dialog->priv->add_word_button = widget;
490
491 g_signal_connect_swapped (
492 widget, "clicked",
493 G_CALLBACK (html_editor_spell_check_dialog_learn), dialog);
494
495 gtk_widget_show_all (GTK_WIDGET (main_layout));
496 }
497
498 GtkWidget *
e_html_editor_spell_check_dialog_new(EHTMLEditor * editor)499 e_html_editor_spell_check_dialog_new (EHTMLEditor *editor)
500 {
501 return g_object_new (
502 E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG,
503 "editor", editor,
504 "title", _("Spell Checking"),
505 NULL);
506 }
507
508 void
e_html_editor_spell_check_dialog_update_dictionaries(EHTMLEditorSpellCheckDialog * dialog)509 e_html_editor_spell_check_dialog_update_dictionaries (EHTMLEditorSpellCheckDialog *dialog)
510 {
511 EHTMLEditor *editor;
512 EContentEditor *cnt_editor;
513 ESpellChecker *spell_checker;
514 GtkComboBox *combo_box;
515 GtkListStore *store = NULL;
516 GQueue queue = G_QUEUE_INIT;
517 gchar **languages;
518 guint n_languages = 0;
519 guint ii;
520
521 g_return_if_fail (E_IS_HTML_EDITOR_SPELL_CHECK_DIALOG (dialog));
522
523 editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
524 cnt_editor = e_html_editor_get_content_editor (editor);
525 spell_checker = e_content_editor_ref_spell_checker (cnt_editor);
526
527 languages = e_spell_checker_list_active_languages (
528 spell_checker, &n_languages);
529 for (ii = 0; ii < n_languages; ii++) {
530 ESpellDictionary *dictionary;
531
532 dictionary = e_spell_checker_ref_dictionary (
533 spell_checker, languages[ii]);
534 if (dictionary != NULL)
535 g_queue_push_tail (&queue, dictionary);
536 else
537 g_warning (
538 "%s: No '%s' dictionary found",
539 G_STRFUNC, languages[ii]);
540 }
541 g_strfreev (languages);
542
543 /* Populate a list store for the combo box. */
544 store = gtk_list_store_new (
545 NUM_COLUMNS,
546 G_TYPE_STRING, /* COLUMN_NAME */
547 E_TYPE_SPELL_DICTIONARY); /* COLUMN_DICTIONARY */
548
549 while (!g_queue_is_empty (&queue)) {
550 ESpellDictionary *dictionary;
551 GtkTreeIter iter;
552 const gchar *name = NULL;
553
554 dictionary = g_queue_pop_head (&queue);
555 name = e_spell_dictionary_get_name (dictionary);
556
557 gtk_list_store_append (store, &iter);
558 gtk_list_store_set (
559 store, &iter,
560 COLUMN_NAME, name,
561 COLUMN_DICTIONARY, dictionary,
562 -1);
563
564 g_object_unref (dictionary);
565 }
566
567 /* FIXME Try to restore selection. */
568 combo_box = GTK_COMBO_BOX (dialog->priv->dictionary_combo);
569 gtk_combo_box_set_model (combo_box, GTK_TREE_MODEL (store));
570 gtk_combo_box_set_active (combo_box, 0);
571
572 g_object_unref (store);
573 g_clear_object (&spell_checker);
574 }
575
576