Code

spellchecker
[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 <aspell.h>
16 #include <gtk/gtk.h>
18 #include <glibmm/i18n.h>
19 #include "helper/window.h"
20 #include "macros.h"
21 #include "inkscape.h"
22 #include "document.h"
23 #include "desktop.h"
24 #include "selection.h"
25 #include "desktop-handles.h"
26 #include "dialog-events.h"
27 #include "tools-switch.h"
28 #include "text-context.h"
29 #include "../interface.h"
30 #include "../preferences.h"
31 #include "../sp-text.h"
32 #include "../sp-flowtext.h"
33 #include "../text-editing.h"
34 #include "../sp-tspan.h"
35 #include "../sp-tref.h"
36 #include "../sp-defs.h"
37 #include "../selection-chemistry.h"
38 #include <xml/repr.h>
39 #include "display/canvas-bpath.h"
40 #include "display/curve.h"
42 #ifdef WIN32
43 #include <windows.h>
44 #endif
46 #define MIN_ONSCREEN_DISTANCE 50
48 static GtkWidget *dlg = NULL;
49 static win_data wd;
51 // impossible original values to make sure they are read from prefs
52 static gint x = -1000, y = -1000, w = 0, h = 0;
53 static Glib::ustring const prefs_path = "/dialogs/spellcheck/";
55 // C++ for the poor: instead of creating a formal C++ class, I just treat this entire file as a
56 // class, with the globals as its data fields. In such a simple case as this, when no inheritance
57 // or encapsulation are necessary, this is much simpler and less verbose, and mixes easily with
58 // plain-C GTK callbacks.
60 static SPDesktop *_desktop = NULL;
61 static AspellSpeller *_speller = NULL;
62 static SPObject *_root;
64 // list of canvasitems (currently just rects) that mark misspelled things on canvas
65 static GSList *_rects = NULL;
67 // list of text objects we have already checked in this session
68 static GSList *_seen_objects = NULL;
70 // the object currently being checked
71 static SPItem *_text = NULL;
72 // its layout
73 static Inkscape::Text::Layout const *_layout = NULL;
75 // iterators for the start and end of the current word
76 static Inkscape::Text::Layout::iterator _begin_w;
77 static Inkscape::Text::Layout::iterator _end_w;
79 // the word we're checking
80 static Glib::ustring _word;
82 // counters for the number of stops and dictionary adds
83 static int _stops = 0;
84 static int _adds = 0;
86 // true if we are in the middle of a check
87 static bool _working = false;
89 // connect to the object being checked in case it is modified or deleted by user
90 static sigc::connection *_modified_connection = NULL;
91 static sigc::connection *_release_connection = NULL;
93 // true if the spell checker dialog has changed text, to suppress modified callback
94 static bool _local_change = false;
98 void spellcheck_clear_rects()
99 {
100     for (GSList *it = _rects; it; it = it->next) {
101         sp_canvas_item_hide((SPCanvasItem*) it->data);
102         gtk_object_destroy((SPCanvasItem*) it->data);
103     }
104     g_slist_free(_rects);
105     _rects = NULL;
108 void
109 spellcheck_disconnect()
111     if (_release_connection) {
112         _release_connection->disconnect();
113         delete _release_connection;
114         _release_connection = NULL;
115     }
116     if (_modified_connection) {
117         _modified_connection->disconnect();
118         delete _modified_connection;
119         _modified_connection = NULL;
120     }
123 static void sp_spellcheck_dialog_destroy(GtkObject *object, gpointer)
125     spellcheck_clear_rects();
126     spellcheck_disconnect();
128     sp_signal_disconnect_by_data (INKSCAPE, object);
129     wd.win = dlg = NULL;
130     wd.stop = 0;
134 static gboolean sp_spellcheck_dialog_delete(GtkObject *, GdkEvent *, gpointer /*data*/)
136     spellcheck_clear_rects();
137     spellcheck_disconnect();
139     gtk_window_get_position (GTK_WINDOW (dlg), &x, &y);
140     gtk_window_get_size (GTK_WINDOW (dlg), &w, &h);
142     if (x<0) x=0;
143     if (y<0) y=0;
145     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
146     prefs->setInt(prefs_path + "x", x);
147     prefs->setInt(prefs_path + "y", y);
148     prefs->setInt(prefs_path + "w", w);
149     prefs->setInt(prefs_path + "h", h);
151     return FALSE; // which means, go ahead and destroy it
154 void
155 sp_spellcheck_new_button (GtkWidget *dlg, GtkWidget *hb, const gchar *label, GtkTooltips *tt, const gchar *tip, void (*function) (GObject *, GObject *), const gchar *cookie)
157     GtkWidget *b = gtk_button_new_with_mnemonic (label);
158     gtk_tooltips_set_tip (tt, b, tip, NULL);
159     gtk_box_pack_start (GTK_BOX (hb), b, TRUE, TRUE, 0);
160     g_signal_connect ( G_OBJECT (b), "clicked", G_CALLBACK (function), dlg );
161     gtk_object_set_data (GTK_OBJECT (dlg), cookie, b);
162     gtk_widget_show (b);
167 GSList *
168 all_text_items (SPObject *r, GSList *l, bool hidden, bool locked)
170     if (!_desktop)
171         return l; // no desktop to check
173     if (SP_IS_DEFS(r))
174         return l; // we're not interested in items in defs
176     if (!strcmp (SP_OBJECT_REPR (r)->name(), "svg:metadata"))
177         return l; // we're not interested in metadata
179     for (SPObject *child = sp_object_first_child(r); child; child = SP_OBJECT_NEXT (child)) {
180         if (SP_IS_ITEM (child) && !SP_OBJECT_IS_CLONED (child) && !_desktop->isLayer(SP_ITEM(child))) {
181                 if ((hidden || !_desktop->itemIsHidden(SP_ITEM(child))) && (locked || !SP_ITEM(child)->isLocked())) {
182                     if (SP_IS_TEXT(child) || SP_IS_FLOWTEXT(child))
183                         l = g_slist_prepend (l, child);
184                 }
185         }
186         l = all_text_items (child, l, hidden, locked);
187     }
188     return l;
191 bool 
192 spellcheck_text_is_valid (SPObject *root, SPItem *text)
194     GSList *l = NULL;
195     l = all_text_items (root, l, false, true);
196     for (GSList *i = l; i; i = i->next) {
197         SPItem *item = (SPItem *) i->data;
198         if (item == text) {
199             g_slist_free (l);
200             return true;
201         }
202     }
203     g_slist_free (l);
204     return false;
207 gint compare_text_bboxes (gconstpointer a, gconstpointer b)
209     SPItem *i1 = SP_ITEM(a);
210     SPItem *i2 = SP_ITEM(b);
212     Geom::OptRect bbox1 = i1->getBounds(sp_item_i2d_affine(i1));
213     Geom::OptRect bbox2 = i2->getBounds(sp_item_i2d_affine(i2));
214     if (!bbox1 || !bbox2) {
215         return 0;
216     }
218     // vector between top left corners
219     Geom::Point diff = Geom::Point(bbox2->min()[Geom::X], bbox2->max()[Geom::Y]) - 
220                        Geom::Point(bbox1->min()[Geom::X], bbox1->max()[Geom::Y]);
222     // sort top to bottom, left to right, but:
223     // if i2 is higher only 0.2 or less times it is righter than i1, put i1 first
224     if (diff[Geom::Y] > 0.2 * diff[Geom::X]) 
225         return 1;
226     else 
227         return -1;
229     return 0;
232 // we regenerate and resort the list every time, because user could have changed it while the
233 // dialog was waiting
234 SPItem *spellcheck_get_text (SPObject *root)
236     GSList *l = NULL;
237     l = all_text_items (root, l, false, true);
238     l = g_slist_sort(l, compare_text_bboxes);
240     for (GSList *i = l; i; i = i->next) {
241         SPItem *item = (SPItem *) i->data;
242         if (!g_slist_find (_seen_objects, item)) {
243             _seen_objects = g_slist_prepend(_seen_objects, item);
244             g_slist_free(l);
245             return item;
246         }
247     }
249     g_slist_free(l);
250     return NULL;
253 void
254 spellcheck_sensitive (const gchar *cookie, gboolean gray)
256    GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), cookie));
257    gtk_widget_set_sensitive(l, gray);
260 static void spellcheck_enable_accept(GtkTreeSelection *selection,
261                                                void *)
263     spellcheck_sensitive ("b_accept", TRUE);
266 static void spellcheck_obj_modified (SPObject *obj, guint /*flags*/, gpointer /*data*/);
267 static void spellcheck_obj_released (SPObject *obj, gpointer /*data*/);
269 void
270 spellcheck_next_text()
272     spellcheck_disconnect();
274     _text = spellcheck_get_text(_root);
275     if (_text) {
276         _release_connection = new sigc::connection (SP_OBJECT(_text)->connectRelease(
277              sigc::bind<1>(sigc::ptr_fun(&spellcheck_obj_released), dlg)));
279         _modified_connection = new sigc::connection (SP_OBJECT(_text)->connectModified(
280              sigc::bind<2>(sigc::ptr_fun(&spellcheck_obj_modified), dlg)));
282         _layout = te_get_layout (_text);
283         _begin_w = _layout->begin();
284     } 
285     _end_w = _begin_w;
286     _word.clear();
289 bool
290 spellcheck_init(SPDesktop *desktop)
292     _desktop = desktop;
294     spellcheck_sensitive("suggestions", FALSE);
295     spellcheck_sensitive("b_accept", FALSE);
296     spellcheck_sensitive("b_ignore", FALSE);
297     spellcheck_sensitive("b_add", FALSE);
298     spellcheck_sensitive("b_start", FALSE);
300     _stops = 0;
301     _adds = 0;
303     spellcheck_clear_rects();
305     AspellConfig *config = new_aspell_config();
307 #ifdef WIN32
308     // on windows, dictionaries are in a lib/aspell-0.60 subdir off inkscape's executable dir;
309     // this is some black magick to find out the executable path to give it to aspell
310     char exeName[MAX_PATH+1];
311     GetModuleFileName(NULL, exeName, MAX_PATH);
312     char *slashPos = strrchr(exeName, '\\');
313     if (slashPos)
314         *slashPos = '\0';
315     g_print ("%s\n", exeName);
316     aspell_config_replace(config, "prefix", exeName);
317 #endif
319     // take language from prefs
320     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
321     Glib::ustring lang = prefs->getString(prefs_path + "lang");
322     if (lang != "")
323         aspell_config_replace(config, "lang", lang.c_str());
324     else
325         aspell_config_replace(config, "lang", "en_US");
327     aspell_config_replace(config, "encoding", "UTF-8");
329     // create speller
330     AspellCanHaveError *ret = new_aspell_speller(config);
331     delete_aspell_config(config);
332     if (aspell_error(ret) != 0) {
333         g_warning("Error: %s\n", aspell_error_message(ret));
334         delete_aspell_can_have_error(ret);
335         return false;
336     }
337     _speller = to_aspell_speller(ret);
339     _root = SP_DOCUMENT_ROOT (sp_desktop_document (desktop));
341     // empty the list of objects we've checked
342     g_slist_free (_seen_objects);
343     _seen_objects = NULL;
345     // grab first text
346     spellcheck_next_text();
348     _working = true;
350     return true;
353 void
354 spellcheck_finished ()
356     aspell_speller_save_all_word_lists(_speller);
357     delete_aspell_speller(_speller);
358     _speller = NULL;
360     spellcheck_clear_rects();
361     spellcheck_disconnect();
363     _desktop->clearWaitingCursor();
365     spellcheck_sensitive("suggestions", FALSE);
366     spellcheck_sensitive("b_accept", FALSE);
367     spellcheck_sensitive("b_ignore", FALSE);
368     spellcheck_sensitive("b_add", FALSE);
369     spellcheck_sensitive("b_stop", FALSE);
370     spellcheck_sensitive("b_start", TRUE);
372     {
373         GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), "banner"));
374         gchar *label;
375         if (_stops)
376             label = g_strdup_printf(_("<b>Finished</b>, <b>%d</b> words added to dictionary"), _adds);
377         else 
378             label = g_strdup_printf(_("<b>Finished</b>, nothing suspicious found"));
379         gtk_label_set_markup (GTK_LABEL(l), label);
380         g_free(label);
381     }
383     g_slist_free(_seen_objects);
384     _seen_objects = NULL;
386     _desktop = NULL;
387     _root = NULL;
389     _working = false;
392 bool
393 spellcheck_next_word()
395     if (!_working)
396         return false;
398     if (!_text) {
399         spellcheck_finished();
400         return false;
401     }
402     _word.clear();
404     while (_word.size() == 0) {
405         _begin_w = _end_w;
407         if (!_layout || _begin_w == _layout->end()) {
408             spellcheck_next_text();
409             return false;
410         }
412         if (!_layout->isStartOfWord(_begin_w)) {
413             _begin_w.nextStartOfWord();
414         }
416         _end_w = _begin_w;
417         _end_w.nextEndOfWord();
418         _word = sp_te_get_string_multiline (_text, _begin_w, _end_w);
419     }
421     // try to link this word with the next if separated by '
422     void *rawptr;
423     Glib::ustring::iterator text_iter;
424     _layout->getSourceOfCharacter(_end_w, &rawptr, &text_iter);
425     SPObject *char_item = SP_OBJECT(rawptr);
426     if (SP_IS_STRING(char_item)) {
427         int this_char = *text_iter;
428         if (this_char == '\'' || this_char == 0x2019) {
429             Inkscape::Text::Layout::iterator end_t = _end_w;
430             end_t.nextCharacter();
431             _layout->getSourceOfCharacter(end_t, &rawptr, &text_iter);
432             SPObject *char_item = SP_OBJECT(rawptr);
433             if (SP_IS_STRING(char_item)) {
434                 int this_char = *text_iter;
435                 if (g_ascii_isalpha(this_char)) { // 's
436                     _end_w.nextEndOfWord();
437                     _word = sp_te_get_string_multiline (_text, _begin_w, _end_w);
438                 }
439             }
440         }
441     }
443     // skip words containing digits
444     bool digits = false;
445     for (gchar *c = (gchar *) _word.c_str(); *c; c++) {
446         if (g_ascii_isdigit(*c)) {
447             digits = true;
448         }
449     }
450     if (digits) { 
451         return false;
452     }
454     //g_print ("%s\n", word.c_str());
456     int have = aspell_speller_check(_speller, _word.c_str(), -1);
457     if (have == 0) { // not found
458         _stops ++;
460         _desktop->clearWaitingCursor();
462         // display it in window
463         {
464             GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), "banner"));
465             gchar *label = g_strdup_printf(_("Not in dictionary: <b>%s</b>"), _word.c_str());
466             gtk_label_set_markup (GTK_LABEL(l), label);
467             g_free(label);
468         }
470         spellcheck_sensitive("suggestions", TRUE);
471         spellcheck_sensitive("b_ignore", TRUE);
472         spellcheck_sensitive("b_add", TRUE);
473         spellcheck_sensitive("b_stop", TRUE);
475         // draw rect
476         std::vector<Geom::Point> points = 
477             _layout->createSelectionShape(_begin_w, _end_w, sp_item_i2d_affine(_text));
478         Geom::Point tl, br;
479         tl = br = points.front();
480         for (unsigned i = 0 ; i < points.size() ; i ++) {
481             if (points[i][Geom::X] < tl[Geom::X])
482                 tl[Geom::X] = points[i][Geom::X];
483             if (points[i][Geom::Y] < tl[Geom::Y])
484                 tl[Geom::Y] = points[i][Geom::Y];
485             if (points[i][Geom::X] > br[Geom::X])
486                 br[Geom::X] = points[i][Geom::X];
487             if (points[i][Geom::Y] > br[Geom::Y])
488                 br[Geom::Y] = points[i][Geom::Y];
489         }
491         // expand slightly
492         Geom::Rect area = Geom::Rect(tl, br);
493         double mindim = fabs(tl[Geom::Y] - br[Geom::Y]);
494         if (fabs(tl[Geom::X] - br[Geom::X]) < mindim)
495             mindim = fabs(tl[Geom::X] - br[Geom::X]);
496         area.expandBy(MAX(0.05 * mindim, 1));
498         // create canvas path rectangle, red stroke
499         SPCanvasItem *rect = sp_canvas_bpath_new(sp_desktop_sketch(_desktop), NULL);
500         sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(rect), 0xff0000ff, 3.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
501         sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(rect), 0, SP_WIND_RULE_NONZERO);
502         SPCurve *curve = new SPCurve();
503         curve->moveto(area.corner(0));
504         curve->lineto(area.corner(1));
505         curve->lineto(area.corner(2));
506         curve->lineto(area.corner(3));
507         curve->lineto(area.corner(0));
508         sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(rect), curve);
509         sp_canvas_item_show(rect);
510         _rects = g_slist_prepend(_rects, rect);
512         // scroll to make it all visible
513         Geom::Point const center = _desktop->get_display_area().midpoint();
514         area.expandBy(0.5 * mindim);
515         Geom::Point scrollto;
516         double dist = 0; 
517         for (unsigned corner = 0; corner < 4; corner ++) {
518             if (Geom::L2(area.corner(corner) - center) > dist) {
519                 dist = Geom::L2(area.corner(corner) - center);
520                 scrollto = area.corner(corner);
521             }
522         }
523         _desktop->scroll_to_point (scrollto, 1.0);
525         // if in Text tool, position cursor to the beginnign of word
526         // unless it is already in the word
527         if (tools_isactive(_desktop, TOOLS_TEXT)) {
528             Inkscape::Text::Layout::iterator *cursor = 
529                 sp_text_context_get_cursor_position(SP_TEXT_CONTEXT(_desktop->event_context), _text);
530             if (!cursor) // some other text is selected there
531                 _desktop->selection->set (_text);
532             else if (*cursor <= _begin_w || *cursor >= _end_w) 
533                 sp_text_context_place_cursor (SP_TEXT_CONTEXT(_desktop->event_context), _text, _begin_w);
534         } else { // just select the object
535             _desktop->selection->set (_text);
536         }
538         // get suggestions
539         {
540             const AspellWordList *wl = aspell_speller_suggest(_speller, _word.c_str(), -1);
541             AspellStringEnumeration * els = aspell_word_list_elements(wl);
542             const char *sugg;
543             GtkTreeView *tree_view = 
544                 GTK_TREE_VIEW(gtk_object_get_data (GTK_OBJECT (dlg), "suggestions"));
545             GtkListStore *model = gtk_list_store_new (1, G_TYPE_STRING);
546             gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (model));
547             GtkTreeIter iter;
548             while ((sugg = aspell_string_enumeration_next(els)) != 0) {
549                 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
550                 gtk_list_store_set (GTK_LIST_STORE (model), 
551                                     &iter,
552                                     0, sugg,
553                                     -1);
554             }
555             delete_aspell_string_enumeration(els);
556             spellcheck_sensitive("b_accept", FALSE); // gray it out until something is chosen
557         }
559         return true;
561     }
562     return false;
567 void
568 spellcheck_delete_last_rect ()
570     if (_rects) {
571         sp_canvas_item_hide(SP_CANVAS_ITEM(_rects->data));
572         gtk_object_destroy(GTK_OBJECT(_rects->data));
573         _rects = _rects->next; // pop latest-prepended rect
574     } 
577 void
578 do_spellcheck ()
580     GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), "banner"));
581     gtk_label_set_markup (GTK_LABEL(l), _("<i>Checking...</i>"));
582     gtk_widget_queue_draw(GTK_WIDGET(dlg));
583     gdk_window_process_updates(GTK_WIDGET(dlg)->window, TRUE);
585     _desktop->setWaitingCursor();
587     while (_working)
588         if (spellcheck_next_word())
589             break;
592 static void
593 spellcheck_obj_modified (SPObject *obj, guint /*flags*/, gpointer /*data*/)
595     if (_local_change) { // this was a change by this dialog, i.e. an Accept, skip it
596         _local_change = false;
597         return;
598     }
600     if (_working && _root) { 
601         // user may have edited the text we're checking; try to do the most sensible thing in this
602         // situation
604         // just in case, re-get text's layout
605         _layout = te_get_layout (_text);
607         // re-get the word
608         _layout->validateIterator(&_begin_w);
609         _end_w = _begin_w;
610         _end_w.nextEndOfWord();
611         Glib::ustring word_new = sp_te_get_string_multiline (_text, _begin_w, _end_w);
612         if (word_new != _word) {
613             _end_w = _begin_w;
614             spellcheck_delete_last_rect ();
615             do_spellcheck (); // recheck this word and go ahead if it's ok
616         }
617     }
620 static void
621 spellcheck_obj_released (SPObject *obj, gpointer /*data*/)
623     if (_working && _root) { 
624         // the text object was deleted
625         spellcheck_delete_last_rect ();
626         spellcheck_next_text();
627         do_spellcheck (); // get next text and continue
628     }
631 void
632 sp_spellcheck_accept (GObject *, GObject *dlg)
634     // insert chosen suggestion
635     GtkTreeView *tv = 
636         GTK_TREE_VIEW(gtk_object_get_data (GTK_OBJECT (dlg), "suggestions"));
637     GtkTreeSelection *ts = gtk_tree_view_get_selection(tv);
638     GtkTreeModel *model = 0;
639     GtkTreeIter   iter;
640     if (gtk_tree_selection_get_selected(ts, &model, &iter)) {
641         gchar *sugg;
642         gtk_tree_model_get (model, &iter, 0, &sugg, -1);
643         if (sugg) {
644             //g_print("chosen: %s\n", sugg);
645             _local_change = true;
646             sp_te_replace(_text, _begin_w, _end_w, sugg);
647             // find the end of the word anew
648             _end_w = _begin_w;
649             _end_w.nextEndOfWord();
650             sp_document_done (sp_desktop_document(_desktop), SP_VERB_CONTEXT_TEXT,
651                               _("Fix spelling"));
652         }
653     }
655     spellcheck_delete_last_rect ();
657     do_spellcheck(); // next word or end
660 void
661 sp_spellcheck_ignore (GObject *, GObject *dlg)
663     aspell_speller_add_to_session(_speller, _word.c_str(), -1);
664     spellcheck_delete_last_rect ();
666     do_spellcheck(); // next word or end
669 void
670 sp_spellcheck_add (GObject *, GObject *dlg)
672     _adds++;
673     aspell_speller_add_to_personal(_speller, _word.c_str(), -1);
674     spellcheck_delete_last_rect ();
676     do_spellcheck(); // next word or end
679 void
680 sp_spellcheck_stop (GObject *, GObject *dlg)
682     spellcheck_finished();
685 void
686 sp_spellcheck_start (GObject *, GObject *)
688     if (spellcheck_init (SP_ACTIVE_DESKTOP))
689         do_spellcheck(); // next word or end
692 static gboolean spellcheck_desktop_deactivated(Inkscape::Application *application, SPDesktop *desktop, void *data)
694     if (_working) {
695         if (_desktop == desktop) {
696             spellcheck_finished();
697         }
698     }
699     return FALSE;
703 void
704 sp_spellcheck_dialog (void)
706     if  (!dlg)
707     {
708         gchar title[500];
709         sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_SPELLCHECK), title);
710         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
712         dlg = sp_window_new (title, TRUE);
713         if (x == -1000 || y == -1000) {
714             x = prefs->getInt(prefs_path + "x", -1000);
715             y = prefs->getInt(prefs_path + "y", -1000);
716         }
717         if (w ==0 || h == 0) {
718             w = prefs->getInt(prefs_path + "w", 0);
719             h = prefs->getInt(prefs_path + "h", 0);
720         }
721         
722         if (w && h)
723             gtk_window_resize ((GtkWindow *) dlg, w, h);
724         if (x >= 0 && y >= 0 && (x < (gdk_screen_width()-MIN_ONSCREEN_DISTANCE)) && (y < (gdk_screen_height()-MIN_ONSCREEN_DISTANCE))) {
725             gtk_window_move ((GtkWindow *) dlg, x, y);
726         } else {
727             gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER);
728         }
730         sp_transientize (dlg);
731         wd.win = dlg;
732         wd.stop = 0;
733         g_signal_connect   ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd );
735         g_signal_connect( G_OBJECT(INKSCAPE), "deactivate_desktop", G_CALLBACK( spellcheck_desktop_deactivated ), NULL);
738         gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg);
740         gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_spellcheck_dialog_destroy), NULL );
741         gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_spellcheck_dialog_delete), dlg);
742         g_signal_connect   ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_spellcheck_dialog_delete), dlg);
744         g_signal_connect   ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg);
745         g_signal_connect   ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg);
747         GtkTooltips *tt = gtk_tooltips_new ();
749         gtk_container_set_border_width (GTK_CONTAINER (dlg), 4);
751         /* Toplevel vbox */
752         GtkWidget *vb = gtk_vbox_new (FALSE, 4);
753         gtk_container_add (GTK_CONTAINER (dlg), vb);
755         {
756             GtkWidget *hb = gtk_hbox_new (FALSE, 0);
757             GtkWidget *l = gtk_label_new (NULL);
758             gtk_object_set_data (GTK_OBJECT (dlg), "banner", l);
759             gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0);
760             gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0);
761         }
763         {
764             GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
765             gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
766                                             GTK_POLICY_AUTOMATIC, 
767                                             GTK_POLICY_AUTOMATIC);
768    
769             GtkListStore *model = gtk_list_store_new (1, G_TYPE_STRING);
770             GtkWidget *tree_view = gtk_tree_view_new ();
771             gtk_object_set_data (GTK_OBJECT (dlg), "suggestions", tree_view);
772             gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_window), 
773                                                    tree_view);
774             gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (model));
775             GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree_view));
776             g_signal_connect (G_OBJECT(selection), "changed", 
777                               G_CALLBACK (spellcheck_enable_accept), NULL);
778             gtk_widget_show (tree_view);
779             GtkCellRenderer *cell = gtk_cell_renderer_text_new ();
780             GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Suggestions:"),
781                                                                                   cell,
782                                                                                   "text", 0,
783                                                                                   NULL);
784             gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view),
785                                          GTK_TREE_VIEW_COLUMN (column));
786             gtk_box_pack_start (GTK_BOX (vb), scrolled_window, TRUE, TRUE, 0);
787         }
790         {
791             GtkWidget *hb = gtk_hbox_new (FALSE, 0);
792             sp_spellcheck_new_button (dlg, hb, _("_Accept"), tt, _("Accept the chosen suggestion"), 
793                                       sp_spellcheck_accept, "b_accept");
794             sp_spellcheck_new_button (dlg, hb, _("_Ignore"), tt, _("Ignore this word in this session"), 
795                                       sp_spellcheck_ignore, "b_ignore");
796             sp_spellcheck_new_button (dlg, hb, _("A_dd"), tt, _("Add this word to the dictionary"), 
797                                       sp_spellcheck_add, "b_add");
798             gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0);
799         }
801         {
802             GtkWidget *hs = gtk_hseparator_new ();
803             gtk_box_pack_start (GTK_BOX (vb), hs, FALSE, FALSE, 0);
804         }
806         {
807             GtkWidget *hb = gtk_hbox_new (FALSE, 0);
808             sp_spellcheck_new_button (dlg, hb, _("_Stop"), tt, _("Stop the check"), 
809                                       sp_spellcheck_stop, "b_stop");
810             sp_spellcheck_new_button (dlg, hb, _("_Start"), tt, _("Start the check"), 
811                                       sp_spellcheck_start, "b_start");
812             gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0);
813         }
815         gtk_widget_show_all (vb);
816     }
818     gtk_window_present ((GtkWindow *) dlg);
820     // run it at once
821     sp_spellcheck_start (NULL, NULL);
825 /*
826   Local Variables:
827   mode:c++
828   c-file-style:"stroustrup"
829   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
830   indent-tabs-mode:nil
831   fill-column:99
832   End:
833 */
834 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :