8f4a434def8cad2a1bcbe7e979ed74bc36fa7d62
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;
106 }
108 void
109 spellcheck_disconnect()
110 {
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 }
121 }
123 static void sp_spellcheck_dialog_destroy(GtkObject *object, gpointer)
124 {
125 spellcheck_clear_rects();
126 spellcheck_disconnect();
128 sp_signal_disconnect_by_data (INKSCAPE, object);
129 wd.win = dlg = NULL;
130 wd.stop = 0;
131 }
134 static gboolean sp_spellcheck_dialog_delete(GtkObject *, GdkEvent *, gpointer /*data*/)
135 {
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
152 }
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)
156 {
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);
163 }
167 GSList *
168 all_text_items (SPObject *r, GSList *l, bool hidden, bool locked)
169 {
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;
189 }
191 bool
192 spellcheck_text_is_valid (SPObject *root, SPItem *text)
193 {
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;
205 }
207 gint compare_text_bboxes (gconstpointer a, gconstpointer b)
208 {
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;
230 }
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)
235 {
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;
251 }
253 void
254 spellcheck_sensitive (const gchar *cookie, gboolean gray)
255 {
256 GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), cookie));
257 gtk_widget_set_sensitive(l, gray);
258 }
260 static void spellcheck_enable_accept(GtkTreeSelection */*selection*/,
261 void */*??*/)
262 {
263 spellcheck_sensitive ("b_accept", TRUE);
264 }
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()
271 {
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();
287 }
289 bool
290 spellcheck_init(SPDesktop *desktop)
291 {
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;
351 }
353 void
354 spellcheck_finished ()
355 {
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;
390 }
392 bool
393 spellcheck_next_word()
394 {
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 // select text; if in Text tool, position cursor to the beginning of word
526 // unless it is already in the word
527 if (_desktop->selection->singleItem() != _text)
528 _desktop->selection->set (_text);
529 if (tools_isactive(_desktop, TOOLS_TEXT)) {
530 Inkscape::Text::Layout::iterator *cursor =
531 sp_text_context_get_cursor_position(SP_TEXT_CONTEXT(_desktop->event_context), _text);
532 if (!cursor) // some other text is selected there
533 _desktop->selection->set (_text);
534 else if (*cursor <= _begin_w || *cursor >= _end_w)
535 sp_text_context_place_cursor (SP_TEXT_CONTEXT(_desktop->event_context), _text, _begin_w);
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;
563 }
567 void
568 spellcheck_delete_last_rect ()
569 {
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 }
575 }
577 void
578 do_spellcheck ()
579 {
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;
590 }
592 static void
593 spellcheck_obj_modified (SPObject */*obj*/, guint /*flags*/, gpointer /*data*/)
594 {
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 }
618 }
620 static void
621 spellcheck_obj_released (SPObject */*obj*/, gpointer /*data*/)
622 {
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 }
629 }
631 void
632 sp_spellcheck_accept (GObject *, GObject *dlg)
633 {
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
658 }
660 void
661 sp_spellcheck_ignore (GObject */*obj*/, GObject */*dlg*/)
662 {
663 aspell_speller_add_to_session(_speller, _word.c_str(), -1);
664 spellcheck_delete_last_rect ();
666 do_spellcheck(); // next word or end
667 }
669 void
670 sp_spellcheck_add (GObject */*obj*/, GObject */*dlg*/)
671 {
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
677 }
679 void
680 sp_spellcheck_stop (GObject */*obj*/, GObject */*dlg*/)
681 {
682 spellcheck_finished();
683 }
685 void
686 sp_spellcheck_start (GObject *, GObject *)
687 {
688 if (spellcheck_init (SP_ACTIVE_DESKTOP))
689 do_spellcheck(); // next word or end
690 }
692 static gboolean spellcheck_desktop_deactivated(Inkscape::Application */*application*/, SPDesktop *desktop, void */*data*/)
693 {
694 if (_working) {
695 if (_desktop == desktop) {
696 spellcheck_finished();
697 }
698 }
699 return FALSE;
700 }
703 void
704 sp_spellcheck_dialog (void)
705 {
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 }
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);
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);
822 }
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 :