Code

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