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