1 /** @file
2 * @brief Spellcheck dialog
3 */
4 /* Authors:
5 * bulia byak <bulia@users.sf.net>
6 * Jon A. Cruz <jon@joncruz.org>
7 * Abhishek Sharma
8 *
9 * Copyright (C) 2009 Authors
10 *
11 * Released under GNU GPL, read the file 'COPYING' for more information
12 */
14 #include "widgets/icon.h"
15 #include "message-stack.h"
17 #include <gtk/gtk.h>
19 #include <glibmm/i18n.h>
20 #include "helper/window.h"
21 #include "macros.h"
22 #include "inkscape.h"
23 #include "document.h"
24 #include "desktop.h"
25 #include "selection.h"
26 #include "desktop-handles.h"
27 #include "dialog-events.h"
28 #include "tools-switch.h"
29 #include "text-context.h"
30 #include "../interface.h"
31 #include "../preferences.h"
32 #include "../sp-text.h"
33 #include "../sp-flowtext.h"
34 #include "../text-editing.h"
35 #include "../sp-tspan.h"
36 #include "../sp-tref.h"
37 #include "../sp-defs.h"
38 #include "../selection-chemistry.h"
39 #include <xml/repr.h>
40 #include "display/canvas-bpath.h"
41 #include "display/curve.h"
43 #ifdef HAVE_ASPELL
44 #include <aspell.h>
46 #ifdef WIN32
47 #include <windows.h>
48 #endif
50 #define MIN_ONSCREEN_DISTANCE 50
52 static GtkWidget *dlg = NULL;
53 static win_data wd;
55 // impossible original values to make sure they are read from prefs
56 static gint x = -1000, y = -1000, w = 0, h = 0;
57 static Glib::ustring const prefs_path = "/dialogs/spellcheck/";
59 // C++ for the poor: instead of creating a formal C++ class, I just treat this entire file as a
60 // class, with the globals as its data fields. In such a simple case as this, when no inheritance
61 // or encapsulation are necessary, this is much simpler and less verbose, and mixes easily with
62 // plain-C GTK callbacks.
64 static SPDesktop *_desktop = NULL;
65 static SPObject *_root;
67 static AspellSpeller *_speller = NULL;
68 static AspellSpeller *_speller2 = NULL;
69 static AspellSpeller *_speller3 = NULL;
71 // list of canvasitems (currently just rects) that mark misspelled things on canvas
72 static GSList *_rects = NULL;
74 // list of text objects we have already checked in this session
75 static GSList *_seen_objects = NULL;
77 // the object currently being checked
78 static SPItem *_text = NULL;
79 // its layout
80 static Inkscape::Text::Layout const *_layout = NULL;
82 // iterators for the start and end of the current word
83 static Inkscape::Text::Layout::iterator _begin_w;
84 static Inkscape::Text::Layout::iterator _end_w;
86 // the word we're checking
87 static Glib::ustring _word;
89 // counters for the number of stops and dictionary adds
90 static int _stops = 0;
91 static int _adds = 0;
93 // true if we are in the middle of a check
94 static bool _working = false;
96 // connect to the object being checked in case it is modified or deleted by user
97 static sigc::connection *_modified_connection = NULL;
98 static sigc::connection *_release_connection = NULL;
100 // true if the spell checker dialog has changed text, to suppress modified callback
101 static bool _local_change = false;
103 static Inkscape::Preferences *_prefs = NULL;
105 static gchar *_lang = NULL;
106 static gchar *_lang2 = NULL;
107 static gchar *_lang3 = NULL;
110 void spellcheck_clear_rects()
111 {
112 for (GSList *it = _rects; it; it = it->next) {
113 sp_canvas_item_hide((SPCanvasItem*) it->data);
114 gtk_object_destroy((SPCanvasItem*) it->data);
115 }
116 g_slist_free(_rects);
117 _rects = NULL;
118 }
120 void spellcheck_clear_langs()
121 {
122 if (_lang) {
123 g_free(_lang);
124 _lang = NULL;
125 }
126 if (_lang2) {
127 g_free(_lang2);
128 _lang2 = NULL;
129 }
130 if (_lang3) {
131 g_free(_lang3);
132 _lang3 = NULL;
133 }
134 }
136 void
137 spellcheck_disconnect()
138 {
139 if (_release_connection) {
140 _release_connection->disconnect();
141 delete _release_connection;
142 _release_connection = NULL;
143 }
144 if (_modified_connection) {
145 _modified_connection->disconnect();
146 delete _modified_connection;
147 _modified_connection = NULL;
148 }
149 }
151 static void sp_spellcheck_dialog_destroy(GtkObject *object, gpointer)
152 {
153 spellcheck_clear_rects();
154 spellcheck_clear_langs();
155 spellcheck_disconnect();
157 sp_signal_disconnect_by_data (INKSCAPE, object);
158 wd.win = dlg = NULL;
159 wd.stop = 0;
160 }
163 static gboolean sp_spellcheck_dialog_delete(GtkObject *, GdkEvent *, gpointer /*data*/)
164 {
165 spellcheck_clear_rects();
166 spellcheck_clear_langs();
167 spellcheck_disconnect();
169 gtk_window_get_position (GTK_WINDOW (dlg), &x, &y);
170 gtk_window_get_size (GTK_WINDOW (dlg), &w, &h);
172 if (x<0) x=0;
173 if (y<0) y=0;
175 _prefs->setInt(prefs_path + "x", x);
176 _prefs->setInt(prefs_path + "y", y);
177 _prefs->setInt(prefs_path + "w", w);
178 _prefs->setInt(prefs_path + "h", h);
180 return FALSE; // which means, go ahead and destroy it
181 }
183 void
184 sp_spellcheck_new_button (GtkWidget *dlg, GtkWidget *hb, const gchar *label, GtkTooltips *tt, const gchar *tip, void (*function) (GObject *, GObject *), const gchar *cookie)
185 {
186 GtkWidget *b = gtk_button_new_with_mnemonic (label);
187 gtk_tooltips_set_tip (tt, b, tip, NULL);
188 gtk_box_pack_start (GTK_BOX (hb), b, TRUE, TRUE, 0);
189 g_signal_connect ( G_OBJECT (b), "clicked", G_CALLBACK (function), dlg );
190 gtk_object_set_data (GTK_OBJECT (dlg), cookie, b);
191 gtk_widget_show (b);
192 }
196 GSList *
197 all_text_items (SPObject *r, GSList *l, bool hidden, bool locked)
198 {
199 if (!_desktop)
200 return l; // no desktop to check
202 if (SP_IS_DEFS(r))
203 return l; // we're not interested in items in defs
205 if (!strcmp(r->getRepr()->name(), "svg:metadata")) {
206 return l; // we're not interested in metadata
207 }
209 for (SPObject *child = r->firstChild(); child; child = child->next) {
210 if (SP_IS_ITEM (child) && !SP_OBJECT_IS_CLONED (child) && !_desktop->isLayer(SP_ITEM(child))) {
211 if ((hidden || !_desktop->itemIsHidden(SP_ITEM(child))) && (locked || !SP_ITEM(child)->isLocked())) {
212 if (SP_IS_TEXT(child) || SP_IS_FLOWTEXT(child))
213 l = g_slist_prepend (l, child);
214 }
215 }
216 l = all_text_items (child, l, hidden, locked);
217 }
218 return l;
219 }
221 bool
222 spellcheck_text_is_valid (SPObject *root, SPItem *text)
223 {
224 GSList *l = NULL;
225 l = all_text_items (root, l, false, true);
226 for (GSList *i = l; i; i = i->next) {
227 SPItem *item = (SPItem *) i->data;
228 if (item == text) {
229 g_slist_free (l);
230 return true;
231 }
232 }
233 g_slist_free (l);
234 return false;
235 }
237 gint compare_text_bboxes (gconstpointer a, gconstpointer b)
238 {
239 SPItem *i1 = SP_ITEM(a);
240 SPItem *i2 = SP_ITEM(b);
242 Geom::OptRect bbox1 = i1->getBounds(i1->i2d_affine());
243 Geom::OptRect bbox2 = i2->getBounds(i2->i2d_affine());
244 if (!bbox1 || !bbox2) {
245 return 0;
246 }
248 // vector between top left corners
249 Geom::Point diff = Geom::Point(bbox2->min()[Geom::X], bbox2->max()[Geom::Y]) -
250 Geom::Point(bbox1->min()[Geom::X], bbox1->max()[Geom::Y]);
252 // sort top to bottom, left to right, but:
253 // if i2 is higher only 0.2 or less times it is righter than i1, put i1 first
254 if (diff[Geom::Y] > 0.2 * diff[Geom::X])
255 return 1;
256 else
257 return -1;
259 return 0;
260 }
262 // we regenerate and resort the list every time, because user could have changed it while the
263 // dialog was waiting
264 SPItem *spellcheck_get_text (SPObject *root)
265 {
266 GSList *l = NULL;
267 l = all_text_items (root, l, false, true);
268 l = g_slist_sort(l, compare_text_bboxes);
270 for (GSList *i = l; i; i = i->next) {
271 SPItem *item = (SPItem *) i->data;
272 if (!g_slist_find (_seen_objects, item)) {
273 _seen_objects = g_slist_prepend(_seen_objects, item);
274 g_slist_free(l);
275 return item;
276 }
277 }
279 g_slist_free(l);
280 return NULL;
281 }
283 void
284 spellcheck_sensitive (const gchar *cookie, gboolean gray)
285 {
286 GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), cookie));
287 gtk_widget_set_sensitive(l, gray);
288 }
290 static void spellcheck_enable_accept(GtkTreeSelection */*selection*/,
291 void */*??*/)
292 {
293 spellcheck_sensitive ("b_accept", TRUE);
294 }
296 static void spellcheck_obj_modified (SPObject *obj, guint /*flags*/, gpointer /*data*/);
297 static void spellcheck_obj_released (SPObject *obj, gpointer /*data*/);
299 void
300 spellcheck_next_text()
301 {
302 spellcheck_disconnect();
304 _text = spellcheck_get_text(_root);
305 if (_text) {
306 _release_connection = new sigc::connection (SP_OBJECT(_text)->connectRelease(
307 sigc::bind<1>(sigc::ptr_fun(&spellcheck_obj_released), dlg)));
309 _modified_connection = new sigc::connection (SP_OBJECT(_text)->connectModified(
310 sigc::bind<2>(sigc::ptr_fun(&spellcheck_obj_modified), dlg)));
312 _layout = te_get_layout (_text);
313 _begin_w = _layout->begin();
314 }
315 _end_w = _begin_w;
316 _word.clear();
317 }
319 bool
320 spellcheck_init(SPDesktop *desktop)
321 {
322 _desktop = desktop;
324 spellcheck_sensitive("suggestions", FALSE);
325 spellcheck_sensitive("b_accept", FALSE);
326 spellcheck_sensitive("b_ignore", FALSE);
327 spellcheck_sensitive("b_ignore_once", FALSE);
328 spellcheck_sensitive("b_add", FALSE);
329 spellcheck_sensitive("addto_langs", FALSE);
330 spellcheck_sensitive("b_start", FALSE);
332 #ifdef WIN32
333 // on windows, dictionaries are in a lib/aspell-0.60 subdir off inkscape's executable dir;
334 // this is some black magick to find out the executable path to give it to aspell
335 char exeName[MAX_PATH+1];
336 GetModuleFileName(NULL, exeName, MAX_PATH);
337 char *slashPos = strrchr(exeName, '\\');
338 if (slashPos)
339 *slashPos = '\0';
340 g_print ("%s\n", exeName);
341 #endif
343 _stops = 0;
344 _adds = 0;
345 spellcheck_clear_rects();
347 {
348 AspellConfig *config = new_aspell_config();
349 #ifdef WIN32
350 aspell_config_replace(config, "prefix", exeName);
351 #endif
352 aspell_config_replace(config, "lang", _lang);
353 aspell_config_replace(config, "encoding", "UTF-8");
354 AspellCanHaveError *ret = new_aspell_speller(config);
355 delete_aspell_config(config);
356 if (aspell_error(ret) != 0) {
357 g_warning("Error: %s\n", aspell_error_message(ret));
358 delete_aspell_can_have_error(ret);
359 return false;
360 }
361 _speller = to_aspell_speller(ret);
362 }
364 if (_lang2) {
365 AspellConfig *config = new_aspell_config();
366 #ifdef WIN32
367 aspell_config_replace(config, "prefix", exeName);
368 #endif
369 aspell_config_replace(config, "lang", _lang2);
370 aspell_config_replace(config, "encoding", "UTF-8");
371 AspellCanHaveError *ret = new_aspell_speller(config);
372 delete_aspell_config(config);
373 if (aspell_error(ret) != 0) {
374 g_warning("Error: %s\n", aspell_error_message(ret));
375 delete_aspell_can_have_error(ret);
376 return false;
377 }
378 _speller2 = to_aspell_speller(ret);
379 }
381 if (_lang3) {
382 AspellConfig *config = new_aspell_config();
383 #ifdef WIN32
384 aspell_config_replace(config, "prefix", exeName);
385 #endif
386 aspell_config_replace(config, "lang", _lang3);
387 aspell_config_replace(config, "encoding", "UTF-8");
388 AspellCanHaveError *ret = new_aspell_speller(config);
389 delete_aspell_config(config);
390 if (aspell_error(ret) != 0) {
391 g_warning("Error: %s\n", aspell_error_message(ret));
392 delete_aspell_can_have_error(ret);
393 return false;
394 }
395 _speller3 = to_aspell_speller(ret);
396 }
398 _root = sp_desktop_document(desktop)->getRoot();
400 // empty the list of objects we've checked
401 g_slist_free (_seen_objects);
402 _seen_objects = NULL;
404 // grab first text
405 spellcheck_next_text();
407 _working = true;
409 return true;
410 }
412 void
413 spellcheck_finished ()
414 {
415 aspell_speller_save_all_word_lists(_speller);
416 delete_aspell_speller(_speller);
417 _speller = NULL;
418 if (_speller2) {
419 aspell_speller_save_all_word_lists(_speller2);
420 delete_aspell_speller(_speller2);
421 _speller2 = NULL;
422 }
423 if (_speller3) {
424 aspell_speller_save_all_word_lists(_speller3);
425 delete_aspell_speller(_speller3);
426 _speller3 = NULL;
427 }
429 spellcheck_clear_rects();
430 spellcheck_disconnect();
432 _desktop->clearWaitingCursor();
434 spellcheck_sensitive("suggestions", FALSE);
435 spellcheck_sensitive("b_accept", FALSE);
436 spellcheck_sensitive("b_ignore", FALSE);
437 spellcheck_sensitive("b_ignore_once", FALSE);
438 spellcheck_sensitive("b_add", FALSE);
439 spellcheck_sensitive("addto_langs", FALSE);
440 spellcheck_sensitive("b_stop", FALSE);
441 spellcheck_sensitive("b_start", TRUE);
443 {
444 GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), "banner"));
445 gchar *label;
446 if (_stops)
447 label = g_strdup_printf(_("<b>Finished</b>, <b>%d</b> words added to dictionary"), _adds);
448 else
449 label = g_strdup_printf(_("<b>Finished</b>, nothing suspicious found"));
450 gtk_label_set_markup (GTK_LABEL(l), label);
451 g_free(label);
452 }
454 g_slist_free(_seen_objects);
455 _seen_objects = NULL;
457 _desktop = NULL;
458 _root = NULL;
460 _working = false;
461 }
463 bool
464 spellcheck_next_word()
465 {
466 if (!_working)
467 return false;
469 if (!_text) {
470 spellcheck_finished();
471 return false;
472 }
473 _word.clear();
475 while (_word.size() == 0) {
476 _begin_w = _end_w;
478 if (!_layout || _begin_w == _layout->end()) {
479 spellcheck_next_text();
480 return false;
481 }
483 if (!_layout->isStartOfWord(_begin_w)) {
484 _begin_w.nextStartOfWord();
485 }
487 _end_w = _begin_w;
488 _end_w.nextEndOfWord();
489 _word = sp_te_get_string_multiline (_text, _begin_w, _end_w);
490 }
492 // try to link this word with the next if separated by '
493 void *rawptr;
494 Glib::ustring::iterator text_iter;
495 _layout->getSourceOfCharacter(_end_w, &rawptr, &text_iter);
496 SPObject *char_item = SP_OBJECT(rawptr);
497 if (SP_IS_STRING(char_item)) {
498 int this_char = *text_iter;
499 if (this_char == '\'' || this_char == 0x2019) {
500 Inkscape::Text::Layout::iterator end_t = _end_w;
501 end_t.nextCharacter();
502 _layout->getSourceOfCharacter(end_t, &rawptr, &text_iter);
503 SPObject *char_item = SP_OBJECT(rawptr);
504 if (SP_IS_STRING(char_item)) {
505 int this_char = *text_iter;
506 if (g_ascii_isalpha(this_char)) { // 's
507 _end_w.nextEndOfWord();
508 _word = sp_te_get_string_multiline (_text, _begin_w, _end_w);
509 }
510 }
511 }
512 }
514 // skip words containing digits
515 if (_prefs->getInt(prefs_path + "ignorenumbers") != 0) {
516 bool digits = false;
517 for (unsigned int i = 0; i < _word.size(); i++) {
518 if (g_unichar_isdigit(_word[i])) {
519 digits = true;
520 break;
521 }
522 }
523 if (digits) {
524 return false;
525 }
526 }
528 // skip ALL-CAPS words
529 if (_prefs->getInt(prefs_path + "ignoreallcaps") != 0) {
530 bool allcaps = true;
531 for (unsigned int i = 0; i < _word.size(); i++) {
532 if (!g_unichar_isupper(_word[i])) {
533 allcaps = false;
534 break;
535 }
536 }
537 if (allcaps) {
538 return false;
539 }
540 }
542 // run it by all active spellers
543 int have = aspell_speller_check(_speller, _word.c_str(), -1);
544 if (_speller2)
545 have += aspell_speller_check(_speller2, _word.c_str(), -1);
546 if (_speller3)
547 have += aspell_speller_check(_speller3, _word.c_str(), -1);
549 if (have == 0) { // not found in any!
550 _stops ++;
552 _desktop->clearWaitingCursor();
554 // display it in window
555 {
556 GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), "banner"));
557 Glib::ustring langs = _lang;
558 if (_lang2)
559 langs = langs + ", " + _lang2;
560 if (_lang3)
561 langs = langs + ", " + _lang3;
562 gchar *label = g_strdup_printf(_("Not in dictionary (%s): <b>%s</b>"), langs.c_str(), _word.c_str());
563 gtk_label_set_markup (GTK_LABEL(l), label);
564 g_free(label);
565 }
567 spellcheck_sensitive("suggestions", TRUE);
568 spellcheck_sensitive("b_ignore", TRUE);
569 spellcheck_sensitive("b_ignore_once", TRUE);
570 spellcheck_sensitive("b_add", TRUE);
571 spellcheck_sensitive("addto_langs", TRUE);
572 spellcheck_sensitive("b_stop", TRUE);
574 // draw rect
575 std::vector<Geom::Point> points =
576 _layout->createSelectionShape(_begin_w, _end_w, _text->i2d_affine());
577 Geom::Point tl, br;
578 tl = br = points.front();
579 for (unsigned i = 0 ; i < points.size() ; i ++) {
580 if (points[i][Geom::X] < tl[Geom::X])
581 tl[Geom::X] = points[i][Geom::X];
582 if (points[i][Geom::Y] < tl[Geom::Y])
583 tl[Geom::Y] = points[i][Geom::Y];
584 if (points[i][Geom::X] > br[Geom::X])
585 br[Geom::X] = points[i][Geom::X];
586 if (points[i][Geom::Y] > br[Geom::Y])
587 br[Geom::Y] = points[i][Geom::Y];
588 }
590 // expand slightly
591 Geom::Rect area = Geom::Rect(tl, br);
592 double mindim = fabs(tl[Geom::Y] - br[Geom::Y]);
593 if (fabs(tl[Geom::X] - br[Geom::X]) < mindim)
594 mindim = fabs(tl[Geom::X] - br[Geom::X]);
595 area.expandBy(MAX(0.05 * mindim, 1));
597 // create canvas path rectangle, red stroke
598 SPCanvasItem *rect = sp_canvas_bpath_new(sp_desktop_sketch(_desktop), NULL);
599 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(rect), 0xff0000ff, 3.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
600 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(rect), 0, SP_WIND_RULE_NONZERO);
601 SPCurve *curve = new SPCurve();
602 curve->moveto(area.corner(0));
603 curve->lineto(area.corner(1));
604 curve->lineto(area.corner(2));
605 curve->lineto(area.corner(3));
606 curve->lineto(area.corner(0));
607 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(rect), curve);
608 sp_canvas_item_show(rect);
609 _rects = g_slist_prepend(_rects, rect);
611 // scroll to make it all visible
612 Geom::Point const center = _desktop->get_display_area().midpoint();
613 area.expandBy(0.5 * mindim);
614 Geom::Point scrollto;
615 double dist = 0;
616 for (unsigned corner = 0; corner < 4; corner ++) {
617 if (Geom::L2(area.corner(corner) - center) > dist) {
618 dist = Geom::L2(area.corner(corner) - center);
619 scrollto = area.corner(corner);
620 }
621 }
622 _desktop->scroll_to_point (scrollto, 1.0);
624 // select text; if in Text tool, position cursor to the beginning of word
625 // unless it is already in the word
626 if (_desktop->selection->singleItem() != _text)
627 _desktop->selection->set (_text);
628 if (tools_isactive(_desktop, TOOLS_TEXT)) {
629 Inkscape::Text::Layout::iterator *cursor =
630 sp_text_context_get_cursor_position(SP_TEXT_CONTEXT(_desktop->event_context), _text);
631 if (!cursor) // some other text is selected there
632 _desktop->selection->set (_text);
633 else if (*cursor <= _begin_w || *cursor >= _end_w)
634 sp_text_context_place_cursor (SP_TEXT_CONTEXT(_desktop->event_context), _text, _begin_w);
635 }
637 // get suggestions
638 {
639 GtkTreeView *tree_view =
640 GTK_TREE_VIEW(gtk_object_get_data (GTK_OBJECT (dlg), "suggestions"));
641 GtkListStore *model = gtk_list_store_new (1, G_TYPE_STRING);
642 gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (model));
644 {
645 const AspellWordList *wl = aspell_speller_suggest(_speller, _word.c_str(), -1);
646 AspellStringEnumeration * els = aspell_word_list_elements(wl);
647 const char *sugg;
648 GtkTreeIter iter;
649 while ((sugg = aspell_string_enumeration_next(els)) != 0) {
650 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
651 gtk_list_store_set (GTK_LIST_STORE (model),
652 &iter,
653 0, sugg,
654 -1);
655 }
656 delete_aspell_string_enumeration(els);
657 }
659 if (_speller2) {
660 const AspellWordList *wl = aspell_speller_suggest(_speller2, _word.c_str(), -1);
661 AspellStringEnumeration * els = aspell_word_list_elements(wl);
662 const char *sugg;
663 GtkTreeIter iter;
664 while ((sugg = aspell_string_enumeration_next(els)) != 0) {
665 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
666 gtk_list_store_set (GTK_LIST_STORE (model),
667 &iter,
668 0, sugg,
669 -1);
670 }
671 delete_aspell_string_enumeration(els);
672 }
674 if (_speller3) {
675 const AspellWordList *wl = aspell_speller_suggest(_speller3, _word.c_str(), -1);
676 AspellStringEnumeration * els = aspell_word_list_elements(wl);
677 const char *sugg;
678 GtkTreeIter iter;
679 while ((sugg = aspell_string_enumeration_next(els)) != 0) {
680 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
681 gtk_list_store_set (GTK_LIST_STORE (model),
682 &iter,
683 0, sugg,
684 -1);
685 }
686 delete_aspell_string_enumeration(els);
687 }
689 spellcheck_sensitive("b_accept", FALSE); // gray it out until something is chosen
690 }
692 return true;
694 }
695 return false;
696 }
700 void
701 spellcheck_delete_last_rect ()
702 {
703 if (_rects) {
704 sp_canvas_item_hide(SP_CANVAS_ITEM(_rects->data));
705 gtk_object_destroy(GTK_OBJECT(_rects->data));
706 _rects = _rects->next; // pop latest-prepended rect
707 }
708 }
710 void
711 do_spellcheck ()
712 {
713 GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), "banner"));
714 gtk_label_set_markup (GTK_LABEL(l), _("<i>Checking...</i>"));
715 gtk_widget_queue_draw(GTK_WIDGET(dlg));
716 gdk_window_process_updates(GTK_WIDGET(dlg)->window, TRUE);
718 _desktop->setWaitingCursor();
720 while (_working)
721 if (spellcheck_next_word())
722 break;
723 }
725 static void
726 spellcheck_obj_modified (SPObject */*obj*/, guint /*flags*/, gpointer /*data*/)
727 {
728 if (_local_change) { // this was a change by this dialog, i.e. an Accept, skip it
729 _local_change = false;
730 return;
731 }
733 if (_working && _root) {
734 // user may have edited the text we're checking; try to do the most sensible thing in this
735 // situation
737 // just in case, re-get text's layout
738 _layout = te_get_layout (_text);
740 // re-get the word
741 _layout->validateIterator(&_begin_w);
742 _end_w = _begin_w;
743 _end_w.nextEndOfWord();
744 Glib::ustring word_new = sp_te_get_string_multiline (_text, _begin_w, _end_w);
745 if (word_new != _word) {
746 _end_w = _begin_w;
747 spellcheck_delete_last_rect ();
748 do_spellcheck (); // recheck this word and go ahead if it's ok
749 }
750 }
751 }
753 static void
754 spellcheck_obj_released (SPObject */*obj*/, gpointer /*data*/)
755 {
756 if (_working && _root) {
757 // the text object was deleted
758 spellcheck_delete_last_rect ();
759 spellcheck_next_text();
760 do_spellcheck (); // get next text and continue
761 }
762 }
764 void
765 sp_spellcheck_accept (GObject *, GObject *dlg)
766 {
767 // insert chosen suggestion
768 GtkTreeView *tv =
769 GTK_TREE_VIEW(gtk_object_get_data (GTK_OBJECT (dlg), "suggestions"));
770 GtkTreeSelection *ts = gtk_tree_view_get_selection(tv);
771 GtkTreeModel *model = 0;
772 GtkTreeIter iter;
773 if (gtk_tree_selection_get_selected(ts, &model, &iter)) {
774 gchar *sugg;
775 gtk_tree_model_get (model, &iter, 0, &sugg, -1);
776 if (sugg) {
777 //g_print("chosen: %s\n", sugg);
778 _local_change = true;
779 sp_te_replace(_text, _begin_w, _end_w, sugg);
780 // find the end of the word anew
781 _end_w = _begin_w;
782 _end_w.nextEndOfWord();
783 SPDocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_TEXT,
784 _("Fix spelling"));
785 }
786 }
788 spellcheck_delete_last_rect ();
790 do_spellcheck(); // next word or end
791 }
793 void
794 sp_spellcheck_ignore (GObject */*obj*/, GObject */*dlg*/)
795 {
796 aspell_speller_add_to_session(_speller, _word.c_str(), -1);
797 if (_speller2)
798 aspell_speller_add_to_session(_speller2, _word.c_str(), -1);
799 if (_speller3)
800 aspell_speller_add_to_session(_speller3, _word.c_str(), -1);
801 spellcheck_delete_last_rect ();
803 do_spellcheck(); // next word or end
804 }
806 void
807 sp_spellcheck_ignore_once (GObject */*obj*/, GObject */*dlg*/)
808 {
809 spellcheck_delete_last_rect ();
811 do_spellcheck(); // next word or end
812 }
814 void
815 sp_spellcheck_add (GObject */*obj*/, GObject */*dlg*/)
816 {
817 _adds++;
818 GtkComboBox *cbox =
819 GTK_COMBO_BOX(gtk_object_get_data (GTK_OBJECT (dlg), "addto_langs"));
820 gint num = gtk_combo_box_get_active(cbox);
821 switch (num) {
822 case 0:
823 aspell_speller_add_to_personal(_speller, _word.c_str(), -1);
824 break;
825 case 1:
826 if (_speller2)
827 aspell_speller_add_to_personal(_speller2, _word.c_str(), -1);
828 break;
829 case 2:
830 if (_speller3)
831 aspell_speller_add_to_personal(_speller3, _word.c_str(), -1);
832 break;
833 default:
834 break;
835 }
837 spellcheck_delete_last_rect ();
839 do_spellcheck(); // next word or end
840 }
842 void
843 sp_spellcheck_stop (GObject */*obj*/, GObject */*dlg*/)
844 {
845 spellcheck_finished();
846 }
848 void
849 sp_spellcheck_start (GObject *, GObject *)
850 {
851 if (spellcheck_init (SP_ACTIVE_DESKTOP))
852 do_spellcheck(); // next word or end
853 }
855 static gboolean spellcheck_desktop_deactivated(Inkscape::Application */*application*/, SPDesktop *desktop, void */*data*/)
856 {
857 if (_working) {
858 if (_desktop == desktop) {
859 spellcheck_finished();
860 }
861 }
862 return FALSE;
863 }
866 void
867 sp_spellcheck_dialog (void)
868 {
869 _prefs = Inkscape::Preferences::get();
871 // take languages from prefs
872 _lang = _lang2 = _lang3 = NULL;
873 Glib::ustring lang = _prefs->getString(prefs_path + "lang");
874 if (lang != "")
875 _lang = g_strdup(lang.c_str());
876 else
877 _lang = g_strdup("en");
878 lang = _prefs->getString(prefs_path + "lang2");
879 if (lang != "")
880 _lang2 = g_strdup(lang.c_str());
881 lang = _prefs->getString(prefs_path + "lang3");
882 if (lang != "")
883 _lang3 = g_strdup(lang.c_str());
885 if (!dlg)
886 {
887 gchar title[500];
888 sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_SPELLCHECK), title);
890 dlg = sp_window_new (title, TRUE);
891 if (x == -1000 || y == -1000) {
892 x = _prefs->getInt(prefs_path + "x", -1000);
893 y = _prefs->getInt(prefs_path + "y", -1000);
894 }
895 if (w ==0 || h == 0) {
896 w = _prefs->getInt(prefs_path + "w", 0);
897 h = _prefs->getInt(prefs_path + "h", 0);
898 }
900 if (w && h)
901 gtk_window_resize ((GtkWindow *) dlg, w, h);
902 if (x >= 0 && y >= 0 && (x < (gdk_screen_width()-MIN_ONSCREEN_DISTANCE)) && (y < (gdk_screen_height()-MIN_ONSCREEN_DISTANCE))) {
903 gtk_window_move ((GtkWindow *) dlg, x, y);
904 } else {
905 gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER);
906 }
908 sp_transientize (dlg);
909 wd.win = dlg;
910 wd.stop = 0;
911 g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd );
913 g_signal_connect( G_OBJECT(INKSCAPE), "deactivate_desktop", G_CALLBACK( spellcheck_desktop_deactivated ), NULL);
916 gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg);
918 gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_spellcheck_dialog_destroy), NULL );
919 gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_spellcheck_dialog_delete), dlg);
920 g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_spellcheck_dialog_delete), dlg);
922 g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg);
923 g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg);
925 GtkTooltips *tt = gtk_tooltips_new ();
927 gtk_container_set_border_width (GTK_CONTAINER (dlg), 4);
929 /* Toplevel vbox */
930 GtkWidget *vb = gtk_vbox_new (FALSE, 4);
931 gtk_container_add (GTK_CONTAINER (dlg), vb);
933 {
934 GtkWidget *hb = gtk_hbox_new (FALSE, 0);
935 GtkWidget *l = gtk_label_new (NULL);
936 gtk_object_set_data (GTK_OBJECT (dlg), "banner", l);
937 gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0);
938 gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0);
939 }
941 {
942 GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
943 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
944 GTK_POLICY_AUTOMATIC,
945 GTK_POLICY_AUTOMATIC);
947 GtkListStore *model = gtk_list_store_new (1, G_TYPE_STRING);
948 GtkWidget *tree_view = gtk_tree_view_new ();
949 gtk_object_set_data (GTK_OBJECT (dlg), "suggestions", tree_view);
950 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_window),
951 tree_view);
952 gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (model));
953 GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree_view));
954 g_signal_connect (G_OBJECT(selection), "changed",
955 G_CALLBACK (spellcheck_enable_accept), NULL);
956 gtk_widget_show (tree_view);
957 GtkCellRenderer *cell = gtk_cell_renderer_text_new ();
958 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Suggestions:"),
959 cell,
960 "text", 0,
961 NULL);
962 gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view),
963 GTK_TREE_VIEW_COLUMN (column));
964 gtk_box_pack_start (GTK_BOX (vb), scrolled_window, TRUE, TRUE, 0);
965 }
968 {
969 GtkWidget *hb = gtk_hbox_new (FALSE, 0);
970 sp_spellcheck_new_button (dlg, hb, _("_Accept"), tt, _("Accept the chosen suggestion"),
971 sp_spellcheck_accept, "b_accept");
972 sp_spellcheck_new_button (dlg, hb, _("_Ignore once"), tt, _("Ignore this word only once"),
973 sp_spellcheck_ignore_once, "b_ignore_once");
974 sp_spellcheck_new_button (dlg, hb, _("_Ignore"), tt, _("Ignore this word in this session"),
975 sp_spellcheck_ignore, "b_ignore");
976 gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0);
977 }
979 {
980 GtkWidget *hb = gtk_hbox_new (FALSE, 0);
981 sp_spellcheck_new_button (dlg, hb, _("A_dd to dictionary:"), tt, _("Add this word to the chosen dictionary"),
982 sp_spellcheck_add, "b_add");
983 GtkComboBox *cbox = GTK_COMBO_BOX (gtk_combo_box_new_text());
984 gtk_combo_box_append_text (cbox, _lang);
985 if (_lang2) {
986 gtk_combo_box_append_text (cbox, _lang2);
987 }
988 if (_lang3) {
989 gtk_combo_box_append_text (cbox, _lang3);
990 }
991 gtk_combo_box_set_active (cbox, 0);
992 gtk_widget_show_all (GTK_WIDGET(cbox));
993 g_object_set_data (G_OBJECT (dlg), "addto_langs", cbox);
994 gtk_box_pack_start (GTK_BOX (hb), GTK_WIDGET(cbox), TRUE, TRUE, 0);
995 gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0);
996 }
998 {
999 GtkWidget *hs = gtk_hseparator_new ();
1000 gtk_box_pack_start (GTK_BOX (vb), hs, FALSE, FALSE, 0);
1001 }
1003 {
1004 GtkWidget *hb = gtk_hbox_new (FALSE, 0);
1005 sp_spellcheck_new_button (dlg, hb, _("_Stop"), tt, _("Stop the check"),
1006 sp_spellcheck_stop, "b_stop");
1007 sp_spellcheck_new_button (dlg, hb, _("_Start"), tt, _("Start the check"),
1008 sp_spellcheck_start, "b_start");
1009 gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0);
1010 }
1012 gtk_widget_show_all (vb);
1013 }
1015 gtk_window_present ((GtkWindow *) dlg);
1017 // run it at once
1018 sp_spellcheck_start (NULL, NULL);
1019 }
1021 #else
1023 void sp_spellcheck_dialog (void) {}
1025 #endif // HAVE_ASPELL
1028 /*
1029 Local Variables:
1030 mode:c++
1031 c-file-style:"stroustrup"
1032 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1033 indent-tabs-mode:nil
1034 fill-column:99
1035 End:
1036 */
1037 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :