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"
42 #include "document-undo.h"
44 #ifdef HAVE_ASPELL
45 #include <aspell.h>
47 #ifdef WIN32
48 #include <windows.h>
49 #endif
51 #define MIN_ONSCREEN_DISTANCE 50
53 static GtkWidget *dlg = NULL;
54 static win_data wd;
56 // impossible original values to make sure they are read from prefs
57 static gint x = -1000, y = -1000, w = 0, h = 0;
58 static Glib::ustring const prefs_path = "/dialogs/spellcheck/";
60 // C++ for the poor: instead of creating a formal C++ class, I just treat this entire file as a
61 // class, with the globals as its data fields. In such a simple case as this, when no inheritance
62 // or encapsulation are necessary, this is much simpler and less verbose, and mixes easily with
63 // plain-C GTK callbacks.
65 static SPDesktop *_desktop = NULL;
66 static SPObject *_root;
68 static AspellSpeller *_speller = NULL;
69 static AspellSpeller *_speller2 = NULL;
70 static AspellSpeller *_speller3 = NULL;
72 // list of canvasitems (currently just rects) that mark misspelled things on canvas
73 static GSList *_rects = NULL;
75 // list of text objects we have already checked in this session
76 static GSList *_seen_objects = NULL;
78 // the object currently being checked
79 static SPItem *_text = NULL;
80 // its layout
81 static Inkscape::Text::Layout const *_layout = NULL;
83 // iterators for the start and end of the current word
84 static Inkscape::Text::Layout::iterator _begin_w;
85 static Inkscape::Text::Layout::iterator _end_w;
87 // the word we're checking
88 static Glib::ustring _word;
90 // counters for the number of stops and dictionary adds
91 static int _stops = 0;
92 static int _adds = 0;
94 // true if we are in the middle of a check
95 static bool _working = false;
97 // connect to the object being checked in case it is modified or deleted by user
98 static sigc::connection *_modified_connection = NULL;
99 static sigc::connection *_release_connection = NULL;
101 // true if the spell checker dialog has changed text, to suppress modified callback
102 static bool _local_change = false;
104 static Inkscape::Preferences *_prefs = NULL;
106 static gchar *_lang = NULL;
107 static gchar *_lang2 = NULL;
108 static gchar *_lang3 = NULL;
111 void spellcheck_clear_rects()
112 {
113 for (GSList *it = _rects; it; it = it->next) {
114 sp_canvas_item_hide((SPCanvasItem*) it->data);
115 gtk_object_destroy((SPCanvasItem*) it->data);
116 }
117 g_slist_free(_rects);
118 _rects = NULL;
119 }
121 void spellcheck_clear_langs()
122 {
123 if (_lang) {
124 g_free(_lang);
125 _lang = NULL;
126 }
127 if (_lang2) {
128 g_free(_lang2);
129 _lang2 = NULL;
130 }
131 if (_lang3) {
132 g_free(_lang3);
133 _lang3 = NULL;
134 }
135 }
137 void
138 spellcheck_disconnect()
139 {
140 if (_release_connection) {
141 _release_connection->disconnect();
142 delete _release_connection;
143 _release_connection = NULL;
144 }
145 if (_modified_connection) {
146 _modified_connection->disconnect();
147 delete _modified_connection;
148 _modified_connection = NULL;
149 }
150 }
152 static void sp_spellcheck_dialog_destroy(GtkObject *object, gpointer)
153 {
154 spellcheck_clear_rects();
155 spellcheck_clear_langs();
156 spellcheck_disconnect();
158 sp_signal_disconnect_by_data (INKSCAPE, object);
159 wd.win = dlg = NULL;
160 wd.stop = 0;
161 }
164 static gboolean sp_spellcheck_dialog_delete(GtkObject *, GdkEvent *, gpointer /*data*/)
165 {
166 spellcheck_clear_rects();
167 spellcheck_clear_langs();
168 spellcheck_disconnect();
170 gtk_window_get_position (GTK_WINDOW (dlg), &x, &y);
171 gtk_window_get_size (GTK_WINDOW (dlg), &w, &h);
173 if (x<0) x=0;
174 if (y<0) y=0;
176 _prefs->setInt(prefs_path + "x", x);
177 _prefs->setInt(prefs_path + "y", y);
178 _prefs->setInt(prefs_path + "w", w);
179 _prefs->setInt(prefs_path + "h", h);
181 return FALSE; // which means, go ahead and destroy it
182 }
184 void
185 sp_spellcheck_new_button (GtkWidget *dlg, GtkWidget *hb, const gchar *label, GtkTooltips *tt, const gchar *tip, void (*function) (GObject *, GObject *), const gchar *cookie)
186 {
187 GtkWidget *b = gtk_button_new_with_mnemonic (label);
188 gtk_tooltips_set_tip (tt, b, tip, NULL);
189 gtk_box_pack_start (GTK_BOX (hb), b, TRUE, TRUE, 0);
190 g_signal_connect ( G_OBJECT (b), "clicked", G_CALLBACK (function), dlg );
191 gtk_object_set_data (GTK_OBJECT (dlg), cookie, b);
192 gtk_widget_show (b);
193 }
197 GSList *
198 all_text_items (SPObject *r, GSList *l, bool hidden, bool locked)
199 {
200 if (!_desktop)
201 return l; // no desktop to check
203 if (SP_IS_DEFS(r))
204 return l; // we're not interested in items in defs
206 if (!strcmp(r->getRepr()->name(), "svg:metadata")) {
207 return l; // we're not interested in metadata
208 }
210 for (SPObject *child = r->firstChild(); child; child = child->next) {
211 if (SP_IS_ITEM (child) && !SP_OBJECT_IS_CLONED (child) && !_desktop->isLayer(SP_ITEM(child))) {
212 if ((hidden || !_desktop->itemIsHidden(SP_ITEM(child))) && (locked || !SP_ITEM(child)->isLocked())) {
213 if (SP_IS_TEXT(child) || SP_IS_FLOWTEXT(child))
214 l = g_slist_prepend (l, child);
215 }
216 }
217 l = all_text_items (child, l, hidden, locked);
218 }
219 return l;
220 }
222 bool
223 spellcheck_text_is_valid (SPObject *root, SPItem *text)
224 {
225 GSList *l = NULL;
226 l = all_text_items (root, l, false, true);
227 for (GSList *i = l; i; i = i->next) {
228 SPItem *item = (SPItem *) i->data;
229 if (item == text) {
230 g_slist_free (l);
231 return true;
232 }
233 }
234 g_slist_free (l);
235 return false;
236 }
238 gint compare_text_bboxes (gconstpointer a, gconstpointer b)
239 {
240 SPItem *i1 = SP_ITEM(a);
241 SPItem *i2 = SP_ITEM(b);
243 Geom::OptRect bbox1 = i1->getBounds(i1->i2d_affine());
244 Geom::OptRect bbox2 = i2->getBounds(i2->i2d_affine());
245 if (!bbox1 || !bbox2) {
246 return 0;
247 }
249 // vector between top left corners
250 Geom::Point diff = Geom::Point(bbox2->min()[Geom::X], bbox2->max()[Geom::Y]) -
251 Geom::Point(bbox1->min()[Geom::X], bbox1->max()[Geom::Y]);
253 // sort top to bottom, left to right, but:
254 // if i2 is higher only 0.2 or less times it is righter than i1, put i1 first
255 if (diff[Geom::Y] > 0.2 * diff[Geom::X])
256 return 1;
257 else
258 return -1;
260 return 0;
261 }
263 // we regenerate and resort the list every time, because user could have changed it while the
264 // dialog was waiting
265 SPItem *spellcheck_get_text (SPObject *root)
266 {
267 GSList *l = NULL;
268 l = all_text_items (root, l, false, true);
269 l = g_slist_sort(l, compare_text_bboxes);
271 for (GSList *i = l; i; i = i->next) {
272 SPItem *item = (SPItem *) i->data;
273 if (!g_slist_find (_seen_objects, item)) {
274 _seen_objects = g_slist_prepend(_seen_objects, item);
275 g_slist_free(l);
276 return item;
277 }
278 }
280 g_slist_free(l);
281 return NULL;
282 }
284 void
285 spellcheck_sensitive (const gchar *cookie, gboolean gray)
286 {
287 GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), cookie));
288 gtk_widget_set_sensitive(l, gray);
289 }
291 static void spellcheck_enable_accept(GtkTreeSelection */*selection*/,
292 void */*??*/)
293 {
294 spellcheck_sensitive ("b_accept", TRUE);
295 }
297 static void spellcheck_obj_modified (SPObject *obj, guint /*flags*/, gpointer /*data*/);
298 static void spellcheck_obj_released (SPObject *obj, gpointer /*data*/);
300 void
301 spellcheck_next_text()
302 {
303 spellcheck_disconnect();
305 _text = spellcheck_get_text(_root);
306 if (_text) {
307 _release_connection = new sigc::connection (SP_OBJECT(_text)->connectRelease(
308 sigc::bind<1>(sigc::ptr_fun(&spellcheck_obj_released), dlg)));
310 _modified_connection = new sigc::connection (SP_OBJECT(_text)->connectModified(
311 sigc::bind<2>(sigc::ptr_fun(&spellcheck_obj_modified), dlg)));
313 _layout = te_get_layout (_text);
314 _begin_w = _layout->begin();
315 }
316 _end_w = _begin_w;
317 _word.clear();
318 }
320 bool
321 spellcheck_init(SPDesktop *desktop)
322 {
323 _desktop = desktop;
325 spellcheck_sensitive("suggestions", FALSE);
326 spellcheck_sensitive("b_accept", FALSE);
327 spellcheck_sensitive("b_ignore", FALSE);
328 spellcheck_sensitive("b_ignore_once", FALSE);
329 spellcheck_sensitive("b_add", FALSE);
330 spellcheck_sensitive("addto_langs", FALSE);
331 spellcheck_sensitive("b_start", FALSE);
333 #ifdef WIN32
334 // on windows, dictionaries are in a lib/aspell-0.60 subdir off inkscape's executable dir;
335 // this is some black magick to find out the executable path to give it to aspell
336 char exeName[MAX_PATH+1];
337 GetModuleFileName(NULL, exeName, MAX_PATH);
338 char *slashPos = strrchr(exeName, '\\');
339 if (slashPos)
340 *slashPos = '\0';
341 g_print ("%s\n", exeName);
342 #endif
344 _stops = 0;
345 _adds = 0;
346 spellcheck_clear_rects();
348 {
349 AspellConfig *config = new_aspell_config();
350 #ifdef WIN32
351 aspell_config_replace(config, "prefix", exeName);
352 #endif
353 aspell_config_replace(config, "lang", _lang);
354 aspell_config_replace(config, "encoding", "UTF-8");
355 AspellCanHaveError *ret = new_aspell_speller(config);
356 delete_aspell_config(config);
357 if (aspell_error(ret) != 0) {
358 g_warning("Error: %s\n", aspell_error_message(ret));
359 delete_aspell_can_have_error(ret);
360 return false;
361 }
362 _speller = to_aspell_speller(ret);
363 }
365 if (_lang2) {
366 AspellConfig *config = new_aspell_config();
367 #ifdef WIN32
368 aspell_config_replace(config, "prefix", exeName);
369 #endif
370 aspell_config_replace(config, "lang", _lang2);
371 aspell_config_replace(config, "encoding", "UTF-8");
372 AspellCanHaveError *ret = new_aspell_speller(config);
373 delete_aspell_config(config);
374 if (aspell_error(ret) != 0) {
375 g_warning("Error: %s\n", aspell_error_message(ret));
376 delete_aspell_can_have_error(ret);
377 return false;
378 }
379 _speller2 = to_aspell_speller(ret);
380 }
382 if (_lang3) {
383 AspellConfig *config = new_aspell_config();
384 #ifdef WIN32
385 aspell_config_replace(config, "prefix", exeName);
386 #endif
387 aspell_config_replace(config, "lang", _lang3);
388 aspell_config_replace(config, "encoding", "UTF-8");
389 AspellCanHaveError *ret = new_aspell_speller(config);
390 delete_aspell_config(config);
391 if (aspell_error(ret) != 0) {
392 g_warning("Error: %s\n", aspell_error_message(ret));
393 delete_aspell_can_have_error(ret);
394 return false;
395 }
396 _speller3 = to_aspell_speller(ret);
397 }
399 _root = sp_desktop_document(desktop)->getRoot();
401 // empty the list of objects we've checked
402 g_slist_free (_seen_objects);
403 _seen_objects = NULL;
405 // grab first text
406 spellcheck_next_text();
408 _working = true;
410 return true;
411 }
413 void
414 spellcheck_finished ()
415 {
416 aspell_speller_save_all_word_lists(_speller);
417 delete_aspell_speller(_speller);
418 _speller = NULL;
419 if (_speller2) {
420 aspell_speller_save_all_word_lists(_speller2);
421 delete_aspell_speller(_speller2);
422 _speller2 = NULL;
423 }
424 if (_speller3) {
425 aspell_speller_save_all_word_lists(_speller3);
426 delete_aspell_speller(_speller3);
427 _speller3 = NULL;
428 }
430 spellcheck_clear_rects();
431 spellcheck_disconnect();
433 _desktop->clearWaitingCursor();
435 spellcheck_sensitive("suggestions", FALSE);
436 spellcheck_sensitive("b_accept", FALSE);
437 spellcheck_sensitive("b_ignore", FALSE);
438 spellcheck_sensitive("b_ignore_once", FALSE);
439 spellcheck_sensitive("b_add", FALSE);
440 spellcheck_sensitive("addto_langs", FALSE);
441 spellcheck_sensitive("b_stop", FALSE);
442 spellcheck_sensitive("b_start", TRUE);
444 {
445 GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), "banner"));
446 gchar *label;
447 if (_stops)
448 label = g_strdup_printf(_("<b>Finished</b>, <b>%d</b> words added to dictionary"), _adds);
449 else
450 label = g_strdup_printf(_("<b>Finished</b>, nothing suspicious found"));
451 gtk_label_set_markup (GTK_LABEL(l), label);
452 g_free(label);
453 }
455 g_slist_free(_seen_objects);
456 _seen_objects = NULL;
458 _desktop = NULL;
459 _root = NULL;
461 _working = false;
462 }
464 bool
465 spellcheck_next_word()
466 {
467 if (!_working)
468 return false;
470 if (!_text) {
471 spellcheck_finished();
472 return false;
473 }
474 _word.clear();
476 while (_word.size() == 0) {
477 _begin_w = _end_w;
479 if (!_layout || _begin_w == _layout->end()) {
480 spellcheck_next_text();
481 return false;
482 }
484 if (!_layout->isStartOfWord(_begin_w)) {
485 _begin_w.nextStartOfWord();
486 }
488 _end_w = _begin_w;
489 _end_w.nextEndOfWord();
490 _word = sp_te_get_string_multiline (_text, _begin_w, _end_w);
491 }
493 // try to link this word with the next if separated by '
494 void *rawptr;
495 Glib::ustring::iterator text_iter;
496 _layout->getSourceOfCharacter(_end_w, &rawptr, &text_iter);
497 SPObject *char_item = SP_OBJECT(rawptr);
498 if (SP_IS_STRING(char_item)) {
499 int this_char = *text_iter;
500 if (this_char == '\'' || this_char == 0x2019) {
501 Inkscape::Text::Layout::iterator end_t = _end_w;
502 end_t.nextCharacter();
503 _layout->getSourceOfCharacter(end_t, &rawptr, &text_iter);
504 SPObject *char_item = SP_OBJECT(rawptr);
505 if (SP_IS_STRING(char_item)) {
506 int this_char = *text_iter;
507 if (g_ascii_isalpha(this_char)) { // 's
508 _end_w.nextEndOfWord();
509 _word = sp_te_get_string_multiline (_text, _begin_w, _end_w);
510 }
511 }
512 }
513 }
515 // skip words containing digits
516 if (_prefs->getInt(prefs_path + "ignorenumbers") != 0) {
517 bool digits = false;
518 for (unsigned int i = 0; i < _word.size(); i++) {
519 if (g_unichar_isdigit(_word[i])) {
520 digits = true;
521 break;
522 }
523 }
524 if (digits) {
525 return false;
526 }
527 }
529 // skip ALL-CAPS words
530 if (_prefs->getInt(prefs_path + "ignoreallcaps") != 0) {
531 bool allcaps = true;
532 for (unsigned int i = 0; i < _word.size(); i++) {
533 if (!g_unichar_isupper(_word[i])) {
534 allcaps = false;
535 break;
536 }
537 }
538 if (allcaps) {
539 return false;
540 }
541 }
543 // run it by all active spellers
544 int have = aspell_speller_check(_speller, _word.c_str(), -1);
545 if (_speller2)
546 have += aspell_speller_check(_speller2, _word.c_str(), -1);
547 if (_speller3)
548 have += aspell_speller_check(_speller3, _word.c_str(), -1);
550 if (have == 0) { // not found in any!
551 _stops ++;
553 _desktop->clearWaitingCursor();
555 // display it in window
556 {
557 GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), "banner"));
558 Glib::ustring langs = _lang;
559 if (_lang2)
560 langs = langs + ", " + _lang2;
561 if (_lang3)
562 langs = langs + ", " + _lang3;
563 gchar *label = g_strdup_printf(_("Not in dictionary (%s): <b>%s</b>"), langs.c_str(), _word.c_str());
564 gtk_label_set_markup (GTK_LABEL(l), label);
565 g_free(label);
566 }
568 spellcheck_sensitive("suggestions", TRUE);
569 spellcheck_sensitive("b_ignore", TRUE);
570 spellcheck_sensitive("b_ignore_once", TRUE);
571 spellcheck_sensitive("b_add", TRUE);
572 spellcheck_sensitive("addto_langs", TRUE);
573 spellcheck_sensitive("b_stop", TRUE);
575 // draw rect
576 std::vector<Geom::Point> points =
577 _layout->createSelectionShape(_begin_w, _end_w, _text->i2d_affine());
578 Geom::Point tl, br;
579 tl = br = points.front();
580 for (unsigned i = 0 ; i < points.size() ; i ++) {
581 if (points[i][Geom::X] < tl[Geom::X])
582 tl[Geom::X] = points[i][Geom::X];
583 if (points[i][Geom::Y] < tl[Geom::Y])
584 tl[Geom::Y] = points[i][Geom::Y];
585 if (points[i][Geom::X] > br[Geom::X])
586 br[Geom::X] = points[i][Geom::X];
587 if (points[i][Geom::Y] > br[Geom::Y])
588 br[Geom::Y] = points[i][Geom::Y];
589 }
591 // expand slightly
592 Geom::Rect area = Geom::Rect(tl, br);
593 double mindim = fabs(tl[Geom::Y] - br[Geom::Y]);
594 if (fabs(tl[Geom::X] - br[Geom::X]) < mindim)
595 mindim = fabs(tl[Geom::X] - br[Geom::X]);
596 area.expandBy(MAX(0.05 * mindim, 1));
598 // create canvas path rectangle, red stroke
599 SPCanvasItem *rect = sp_canvas_bpath_new(sp_desktop_sketch(_desktop), NULL);
600 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(rect), 0xff0000ff, 3.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
601 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(rect), 0, SP_WIND_RULE_NONZERO);
602 SPCurve *curve = new SPCurve();
603 curve->moveto(area.corner(0));
604 curve->lineto(area.corner(1));
605 curve->lineto(area.corner(2));
606 curve->lineto(area.corner(3));
607 curve->lineto(area.corner(0));
608 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(rect), curve);
609 sp_canvas_item_show(rect);
610 _rects = g_slist_prepend(_rects, rect);
612 // scroll to make it all visible
613 Geom::Point const center = _desktop->get_display_area().midpoint();
614 area.expandBy(0.5 * mindim);
615 Geom::Point scrollto;
616 double dist = 0;
617 for (unsigned corner = 0; corner < 4; corner ++) {
618 if (Geom::L2(area.corner(corner) - center) > dist) {
619 dist = Geom::L2(area.corner(corner) - center);
620 scrollto = area.corner(corner);
621 }
622 }
623 _desktop->scroll_to_point (scrollto, 1.0);
625 // select text; if in Text tool, position cursor to the beginning of word
626 // unless it is already in the word
627 if (_desktop->selection->singleItem() != _text)
628 _desktop->selection->set (_text);
629 if (tools_isactive(_desktop, TOOLS_TEXT)) {
630 Inkscape::Text::Layout::iterator *cursor =
631 sp_text_context_get_cursor_position(SP_TEXT_CONTEXT(_desktop->event_context), _text);
632 if (!cursor) // some other text is selected there
633 _desktop->selection->set (_text);
634 else if (*cursor <= _begin_w || *cursor >= _end_w)
635 sp_text_context_place_cursor (SP_TEXT_CONTEXT(_desktop->event_context), _text, _begin_w);
636 }
638 // get suggestions
639 {
640 GtkTreeView *tree_view =
641 GTK_TREE_VIEW(gtk_object_get_data (GTK_OBJECT (dlg), "suggestions"));
642 GtkListStore *model = gtk_list_store_new (1, G_TYPE_STRING);
643 gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (model));
645 {
646 const AspellWordList *wl = aspell_speller_suggest(_speller, _word.c_str(), -1);
647 AspellStringEnumeration * els = aspell_word_list_elements(wl);
648 const char *sugg;
649 GtkTreeIter iter;
650 while ((sugg = aspell_string_enumeration_next(els)) != 0) {
651 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
652 gtk_list_store_set (GTK_LIST_STORE (model),
653 &iter,
654 0, sugg,
655 -1);
656 }
657 delete_aspell_string_enumeration(els);
658 }
660 if (_speller2) {
661 const AspellWordList *wl = aspell_speller_suggest(_speller2, _word.c_str(), -1);
662 AspellStringEnumeration * els = aspell_word_list_elements(wl);
663 const char *sugg;
664 GtkTreeIter iter;
665 while ((sugg = aspell_string_enumeration_next(els)) != 0) {
666 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
667 gtk_list_store_set (GTK_LIST_STORE (model),
668 &iter,
669 0, sugg,
670 -1);
671 }
672 delete_aspell_string_enumeration(els);
673 }
675 if (_speller3) {
676 const AspellWordList *wl = aspell_speller_suggest(_speller3, _word.c_str(), -1);
677 AspellStringEnumeration * els = aspell_word_list_elements(wl);
678 const char *sugg;
679 GtkTreeIter iter;
680 while ((sugg = aspell_string_enumeration_next(els)) != 0) {
681 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
682 gtk_list_store_set (GTK_LIST_STORE (model),
683 &iter,
684 0, sugg,
685 -1);
686 }
687 delete_aspell_string_enumeration(els);
688 }
690 spellcheck_sensitive("b_accept", FALSE); // gray it out until something is chosen
691 }
693 return true;
695 }
696 return false;
697 }
701 void
702 spellcheck_delete_last_rect ()
703 {
704 if (_rects) {
705 sp_canvas_item_hide(SP_CANVAS_ITEM(_rects->data));
706 gtk_object_destroy(GTK_OBJECT(_rects->data));
707 _rects = _rects->next; // pop latest-prepended rect
708 }
709 }
711 void
712 do_spellcheck ()
713 {
714 GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), "banner"));
715 gtk_label_set_markup (GTK_LABEL(l), _("<i>Checking...</i>"));
716 gtk_widget_queue_draw(GTK_WIDGET(dlg));
717 gdk_window_process_updates(GTK_WIDGET(dlg)->window, TRUE);
719 _desktop->setWaitingCursor();
721 while (_working)
722 if (spellcheck_next_word())
723 break;
724 }
726 static void
727 spellcheck_obj_modified (SPObject */*obj*/, guint /*flags*/, gpointer /*data*/)
728 {
729 if (_local_change) { // this was a change by this dialog, i.e. an Accept, skip it
730 _local_change = false;
731 return;
732 }
734 if (_working && _root) {
735 // user may have edited the text we're checking; try to do the most sensible thing in this
736 // situation
738 // just in case, re-get text's layout
739 _layout = te_get_layout (_text);
741 // re-get the word
742 _layout->validateIterator(&_begin_w);
743 _end_w = _begin_w;
744 _end_w.nextEndOfWord();
745 Glib::ustring word_new = sp_te_get_string_multiline (_text, _begin_w, _end_w);
746 if (word_new != _word) {
747 _end_w = _begin_w;
748 spellcheck_delete_last_rect ();
749 do_spellcheck (); // recheck this word and go ahead if it's ok
750 }
751 }
752 }
754 static void
755 spellcheck_obj_released (SPObject */*obj*/, gpointer /*data*/)
756 {
757 if (_working && _root) {
758 // the text object was deleted
759 spellcheck_delete_last_rect ();
760 spellcheck_next_text();
761 do_spellcheck (); // get next text and continue
762 }
763 }
765 void
766 sp_spellcheck_accept (GObject *, GObject *dlg)
767 {
768 // insert chosen suggestion
769 GtkTreeView *tv =
770 GTK_TREE_VIEW(gtk_object_get_data (GTK_OBJECT (dlg), "suggestions"));
771 GtkTreeSelection *ts = gtk_tree_view_get_selection(tv);
772 GtkTreeModel *model = 0;
773 GtkTreeIter iter;
774 if (gtk_tree_selection_get_selected(ts, &model, &iter)) {
775 gchar *sugg;
776 gtk_tree_model_get (model, &iter, 0, &sugg, -1);
777 if (sugg) {
778 //g_print("chosen: %s\n", sugg);
779 _local_change = true;
780 sp_te_replace(_text, _begin_w, _end_w, sugg);
781 // find the end of the word anew
782 _end_w = _begin_w;
783 _end_w.nextEndOfWord();
784 Inkscape::DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_TEXT,
785 _("Fix spelling"));
786 }
787 }
789 spellcheck_delete_last_rect ();
791 do_spellcheck(); // next word or end
792 }
794 void
795 sp_spellcheck_ignore (GObject */*obj*/, GObject */*dlg*/)
796 {
797 aspell_speller_add_to_session(_speller, _word.c_str(), -1);
798 if (_speller2)
799 aspell_speller_add_to_session(_speller2, _word.c_str(), -1);
800 if (_speller3)
801 aspell_speller_add_to_session(_speller3, _word.c_str(), -1);
802 spellcheck_delete_last_rect ();
804 do_spellcheck(); // next word or end
805 }
807 void
808 sp_spellcheck_ignore_once (GObject */*obj*/, GObject */*dlg*/)
809 {
810 spellcheck_delete_last_rect ();
812 do_spellcheck(); // next word or end
813 }
815 void
816 sp_spellcheck_add (GObject */*obj*/, GObject */*dlg*/)
817 {
818 _adds++;
819 GtkComboBox *cbox =
820 GTK_COMBO_BOX(gtk_object_get_data (GTK_OBJECT (dlg), "addto_langs"));
821 gint num = gtk_combo_box_get_active(cbox);
822 switch (num) {
823 case 0:
824 aspell_speller_add_to_personal(_speller, _word.c_str(), -1);
825 break;
826 case 1:
827 if (_speller2)
828 aspell_speller_add_to_personal(_speller2, _word.c_str(), -1);
829 break;
830 case 2:
831 if (_speller3)
832 aspell_speller_add_to_personal(_speller3, _word.c_str(), -1);
833 break;
834 default:
835 break;
836 }
838 spellcheck_delete_last_rect ();
840 do_spellcheck(); // next word or end
841 }
843 void
844 sp_spellcheck_stop (GObject */*obj*/, GObject */*dlg*/)
845 {
846 spellcheck_finished();
847 }
849 void
850 sp_spellcheck_start (GObject *, GObject *)
851 {
852 if (spellcheck_init (SP_ACTIVE_DESKTOP))
853 do_spellcheck(); // next word or end
854 }
856 static gboolean spellcheck_desktop_deactivated(Inkscape::Application */*application*/, SPDesktop *desktop, void */*data*/)
857 {
858 if (_working) {
859 if (_desktop == desktop) {
860 spellcheck_finished();
861 }
862 }
863 return FALSE;
864 }
867 void
868 sp_spellcheck_dialog (void)
869 {
870 _prefs = Inkscape::Preferences::get();
872 // take languages from prefs
873 _lang = _lang2 = _lang3 = NULL;
874 Glib::ustring lang = _prefs->getString(prefs_path + "lang");
875 if (lang != "")
876 _lang = g_strdup(lang.c_str());
877 else
878 _lang = g_strdup("en");
879 lang = _prefs->getString(prefs_path + "lang2");
880 if (lang != "")
881 _lang2 = g_strdup(lang.c_str());
882 lang = _prefs->getString(prefs_path + "lang3");
883 if (lang != "")
884 _lang3 = g_strdup(lang.c_str());
886 if (!dlg)
887 {
888 gchar title[500];
889 sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_SPELLCHECK), title);
891 dlg = sp_window_new (title, TRUE);
892 if (x == -1000 || y == -1000) {
893 x = _prefs->getInt(prefs_path + "x", -1000);
894 y = _prefs->getInt(prefs_path + "y", -1000);
895 }
896 if (w ==0 || h == 0) {
897 w = _prefs->getInt(prefs_path + "w", 0);
898 h = _prefs->getInt(prefs_path + "h", 0);
899 }
901 if (w && h)
902 gtk_window_resize ((GtkWindow *) dlg, w, h);
903 if (x >= 0 && y >= 0 && (x < (gdk_screen_width()-MIN_ONSCREEN_DISTANCE)) && (y < (gdk_screen_height()-MIN_ONSCREEN_DISTANCE))) {
904 gtk_window_move ((GtkWindow *) dlg, x, y);
905 } else {
906 gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER);
907 }
909 sp_transientize (dlg);
910 wd.win = dlg;
911 wd.stop = 0;
912 g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd );
914 g_signal_connect( G_OBJECT(INKSCAPE), "deactivate_desktop", G_CALLBACK( spellcheck_desktop_deactivated ), NULL);
917 gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg);
919 gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_spellcheck_dialog_destroy), NULL );
920 gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_spellcheck_dialog_delete), dlg);
921 g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_spellcheck_dialog_delete), dlg);
923 g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg);
924 g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg);
926 GtkTooltips *tt = gtk_tooltips_new ();
928 gtk_container_set_border_width (GTK_CONTAINER (dlg), 4);
930 /* Toplevel vbox */
931 GtkWidget *vb = gtk_vbox_new (FALSE, 4);
932 gtk_container_add (GTK_CONTAINER (dlg), vb);
934 {
935 GtkWidget *hb = gtk_hbox_new (FALSE, 0);
936 GtkWidget *l = gtk_label_new (NULL);
937 gtk_object_set_data (GTK_OBJECT (dlg), "banner", l);
938 gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0);
939 gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0);
940 }
942 {
943 GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
944 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
945 GTK_POLICY_AUTOMATIC,
946 GTK_POLICY_AUTOMATIC);
948 GtkListStore *model = gtk_list_store_new (1, G_TYPE_STRING);
949 GtkWidget *tree_view = gtk_tree_view_new ();
950 gtk_object_set_data (GTK_OBJECT (dlg), "suggestions", tree_view);
951 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_window),
952 tree_view);
953 gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (model));
954 GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree_view));
955 g_signal_connect (G_OBJECT(selection), "changed",
956 G_CALLBACK (spellcheck_enable_accept), NULL);
957 gtk_widget_show (tree_view);
958 GtkCellRenderer *cell = gtk_cell_renderer_text_new ();
959 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Suggestions:"),
960 cell,
961 "text", 0,
962 NULL);
963 gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view),
964 GTK_TREE_VIEW_COLUMN (column));
965 gtk_box_pack_start (GTK_BOX (vb), scrolled_window, TRUE, TRUE, 0);
966 }
969 {
970 GtkWidget *hb = gtk_hbox_new (FALSE, 0);
971 sp_spellcheck_new_button (dlg, hb, _("_Accept"), tt, _("Accept the chosen suggestion"),
972 sp_spellcheck_accept, "b_accept");
973 sp_spellcheck_new_button (dlg, hb, _("_Ignore once"), tt, _("Ignore this word only once"),
974 sp_spellcheck_ignore_once, "b_ignore_once");
975 sp_spellcheck_new_button (dlg, hb, _("_Ignore"), tt, _("Ignore this word in this session"),
976 sp_spellcheck_ignore, "b_ignore");
977 gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0);
978 }
980 {
981 GtkWidget *hb = gtk_hbox_new (FALSE, 0);
982 sp_spellcheck_new_button (dlg, hb, _("A_dd to dictionary:"), tt, _("Add this word to the chosen dictionary"),
983 sp_spellcheck_add, "b_add");
984 GtkComboBox *cbox = GTK_COMBO_BOX (gtk_combo_box_new_text());
985 gtk_combo_box_append_text (cbox, _lang);
986 if (_lang2) {
987 gtk_combo_box_append_text (cbox, _lang2);
988 }
989 if (_lang3) {
990 gtk_combo_box_append_text (cbox, _lang3);
991 }
992 gtk_combo_box_set_active (cbox, 0);
993 gtk_widget_show_all (GTK_WIDGET(cbox));
994 g_object_set_data (G_OBJECT (dlg), "addto_langs", cbox);
995 gtk_box_pack_start (GTK_BOX (hb), GTK_WIDGET(cbox), TRUE, TRUE, 0);
996 gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0);
997 }
999 {
1000 GtkWidget *hs = gtk_hseparator_new ();
1001 gtk_box_pack_start (GTK_BOX (vb), hs, FALSE, FALSE, 0);
1002 }
1004 {
1005 GtkWidget *hb = gtk_hbox_new (FALSE, 0);
1006 sp_spellcheck_new_button (dlg, hb, _("_Stop"), tt, _("Stop the check"),
1007 sp_spellcheck_stop, "b_stop");
1008 sp_spellcheck_new_button (dlg, hb, _("_Start"), tt, _("Start the check"),
1009 sp_spellcheck_start, "b_start");
1010 gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0);
1011 }
1013 gtk_widget_show_all (vb);
1014 }
1016 gtk_window_present ((GtkWindow *) dlg);
1018 // run it at once
1019 sp_spellcheck_start (NULL, NULL);
1020 }
1022 #else
1024 void sp_spellcheck_dialog (void) {}
1026 #endif // HAVE_ASPELL
1029 /*
1030 Local Variables:
1031 mode:c++
1032 c-file-style:"stroustrup"
1033 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1034 indent-tabs-mode:nil
1035 fill-column:99
1036 End:
1037 */
1038 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :