Code

Fix change in revision 9947 to be consistent with rest of the codebase.
[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"
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()
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;
123 void spellcheck_clear_langs()
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     }
139 void
140 spellcheck_disconnect()
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     }
154 static void sp_spellcheck_dialog_destroy(GtkObject *object, gpointer)
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;
166 static gboolean sp_spellcheck_dialog_delete(GtkObject *, GdkEvent *, gpointer /*data*/)
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
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)
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);
199 GSList *
200 all_text_items (SPObject *r, GSList *l, bool hidden, bool locked)
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;
224 bool
225 spellcheck_text_is_valid (SPObject *root, SPItem *text)
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;
240 gint compare_text_bboxes (gconstpointer a, gconstpointer b)
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;
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)
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;
286 void
287 spellcheck_sensitive (const gchar *cookie, gboolean gray)
289    GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), cookie));
290    gtk_widget_set_sensitive(l, gray);
293 static void spellcheck_enable_accept(GtkTreeSelection */*selection*/,
294                                      void */*??*/)
296     spellcheck_sensitive ("b_accept", TRUE);
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()
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();
322 bool
323 spellcheck_init(SPDesktop *desktop)
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;
415 void
416 spellcheck_finished ()
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;
466 bool
467 spellcheck_next_word()
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;
703 void
704 spellcheck_delete_last_rect ()
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     }
713 void
714 do_spellcheck ()
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;
728 static void
729 spellcheck_obj_modified (SPObject */*obj*/, guint /*flags*/, gpointer /*data*/)
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     }
756 static void
757 spellcheck_obj_released (SPObject */*obj*/, gpointer /*data*/)
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     }
767 void
768 sp_spellcheck_accept (GObject *, GObject *dlg)
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
796 void
797 sp_spellcheck_ignore (GObject */*obj*/, GObject */*dlg*/)
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
809 void
810 sp_spellcheck_ignore_once (GObject */*obj*/, GObject */*dlg*/)
812     spellcheck_delete_last_rect ();
814     do_spellcheck(); // next word or end
817 void
818 sp_spellcheck_add (GObject */*obj*/, GObject */*dlg*/)
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
845 void
846 sp_spellcheck_stop (GObject */*obj*/, GObject */*dlg*/)
848     spellcheck_finished();
851 void
852 sp_spellcheck_start (GObject *, GObject *)
854     if (spellcheck_init (SP_ACTIVE_DESKTOP))
855         do_spellcheck(); // next word or end
858 static gboolean spellcheck_desktop_deactivated(Inkscape::Application */*application*/, SPDesktop *desktop, void */*data*/)
860     if (_working) {
861         if (_desktop == desktop) {
862             spellcheck_finished();
863         }
864     }
865     return FALSE;
869 void
870 sp_spellcheck_dialog (void)
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);
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 :