Code

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