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