Code

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