/** * @file * Spellcheck dialog. */ /* Authors: * bulia byak * Jon A. Cruz * Abhishek Sharma * * Copyright (C) 2009 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ #include "widgets/icon.h" #include "message-stack.h" #include #include #include "helper/window.h" #include "macros.h" #include "inkscape.h" #include "document.h" #include "desktop.h" #include "selection.h" #include "desktop-handles.h" #include "dialog-events.h" #include "tools-switch.h" #include "text-context.h" #include "../interface.h" #include "../preferences.h" #include "../sp-text.h" #include "../sp-flowtext.h" #include "../text-editing.h" #include "../sp-tspan.h" #include "../sp-tref.h" #include "../sp-defs.h" #include "../selection-chemistry.h" #include #include "display/canvas-bpath.h" #include "display/curve.h" #include "document-undo.h" #include "sp-root.h" #ifdef HAVE_ASPELL #include #ifdef WIN32 #include #endif using Inkscape::DocumentUndo; #define MIN_ONSCREEN_DISTANCE 50 static GtkWidget *dlg = NULL; static win_data wd; // impossible original values to make sure they are read from prefs static gint x = -1000, y = -1000, w = 0, h = 0; static Glib::ustring const prefs_path = "/dialogs/spellcheck/"; // C++ for the poor: instead of creating a formal C++ class, I just treat this entire file as a // class, with the globals as its data fields. In such a simple case as this, when no inheritance // or encapsulation are necessary, this is much simpler and less verbose, and mixes easily with // plain-C GTK callbacks. static SPDesktop *_desktop = NULL; static SPObject *_root; static AspellSpeller *_speller = NULL; static AspellSpeller *_speller2 = NULL; static AspellSpeller *_speller3 = NULL; // list of canvasitems (currently just rects) that mark misspelled things on canvas static GSList *_rects = NULL; // list of text objects we have already checked in this session static GSList *_seen_objects = NULL; // the object currently being checked static SPItem *_text = NULL; // its layout static Inkscape::Text::Layout const *_layout = NULL; // iterators for the start and end of the current word static Inkscape::Text::Layout::iterator _begin_w; static Inkscape::Text::Layout::iterator _end_w; // the word we're checking static Glib::ustring _word; // counters for the number of stops and dictionary adds static int _stops = 0; static int _adds = 0; // true if we are in the middle of a check static bool _working = false; // connect to the object being checked in case it is modified or deleted by user static sigc::connection *_modified_connection = NULL; static sigc::connection *_release_connection = NULL; // true if the spell checker dialog has changed text, to suppress modified callback static bool _local_change = false; static Inkscape::Preferences *_prefs = NULL; static gchar *_lang = NULL; static gchar *_lang2 = NULL; static gchar *_lang3 = NULL; void spellcheck_clear_rects() { for (GSList *it = _rects; it; it = it->next) { sp_canvas_item_hide((SPCanvasItem*) it->data); gtk_object_destroy((SPCanvasItem*) it->data); } g_slist_free(_rects); _rects = NULL; } void spellcheck_clear_langs() { if (_lang) { g_free(_lang); _lang = NULL; } if (_lang2) { g_free(_lang2); _lang2 = NULL; } if (_lang3) { g_free(_lang3); _lang3 = NULL; } } void spellcheck_disconnect() { if (_release_connection) { _release_connection->disconnect(); delete _release_connection; _release_connection = NULL; } if (_modified_connection) { _modified_connection->disconnect(); delete _modified_connection; _modified_connection = NULL; } } static void sp_spellcheck_dialog_destroy(GtkObject *object, gpointer) { spellcheck_clear_rects(); spellcheck_clear_langs(); spellcheck_disconnect(); sp_signal_disconnect_by_data (INKSCAPE, object); wd.win = dlg = NULL; wd.stop = 0; } static gboolean sp_spellcheck_dialog_delete(GtkObject *, GdkEvent *, gpointer /*data*/) { spellcheck_clear_rects(); spellcheck_clear_langs(); spellcheck_disconnect(); gtk_window_get_position (GTK_WINDOW (dlg), &x, &y); gtk_window_get_size (GTK_WINDOW (dlg), &w, &h); if (x<0) x=0; if (y<0) y=0; _prefs->setInt(prefs_path + "x", x); _prefs->setInt(prefs_path + "y", y); _prefs->setInt(prefs_path + "w", w); _prefs->setInt(prefs_path + "h", h); return FALSE; // which means, go ahead and destroy it } void sp_spellcheck_new_button (GtkWidget *dlg, GtkWidget *hb, const gchar *label, const gchar *tip, void (*function) (GObject *, GObject *), const gchar *cookie) { GtkWidget *b = gtk_button_new_with_mnemonic (label); gtk_widget_set_tooltip_text (b, tip); gtk_box_pack_start (GTK_BOX (hb), b, TRUE, TRUE, 0); g_signal_connect ( G_OBJECT (b), "clicked", G_CALLBACK (function), dlg ); g_object_set_data (G_OBJECT (dlg), cookie, b); gtk_widget_show (b); } GSList * all_text_items (SPObject *r, GSList *l, bool hidden, bool locked) { if (!_desktop) return l; // no desktop to check if (SP_IS_DEFS(r)) return l; // we're not interested in items in defs if (!strcmp(r->getRepr()->name(), "svg:metadata")) { return l; // we're not interested in metadata } for (SPObject *child = r->firstChild(); child; child = child->next) { if (SP_IS_ITEM (child) && !child->cloned && !_desktop->isLayer(SP_ITEM(child))) { if ((hidden || !_desktop->itemIsHidden(SP_ITEM(child))) && (locked || !SP_ITEM(child)->isLocked())) { if (SP_IS_TEXT(child) || SP_IS_FLOWTEXT(child)) l = g_slist_prepend (l, child); } } l = all_text_items (child, l, hidden, locked); } return l; } bool spellcheck_text_is_valid (SPObject *root, SPItem *text) { GSList *l = NULL; l = all_text_items (root, l, false, true); for (GSList *i = l; i; i = i->next) { SPItem *item = (SPItem *) i->data; if (item == text) { g_slist_free (l); return true; } } g_slist_free (l); return false; } gint compare_text_bboxes (gconstpointer a, gconstpointer b) { SPItem *i1 = SP_ITEM(a); SPItem *i2 = SP_ITEM(b); Geom::OptRect bbox1 = i1->desktopVisualBounds(); Geom::OptRect bbox2 = i2->desktopVisualBounds(); if (!bbox1 || !bbox2) { return 0; } // vector between top left corners Geom::Point diff = Geom::Point(bbox2->min()[Geom::X], bbox2->max()[Geom::Y]) - Geom::Point(bbox1->min()[Geom::X], bbox1->max()[Geom::Y]); // sort top to bottom, left to right, but: // if i2 is higher only 0.2 or less times it is righter than i1, put i1 first if (diff[Geom::Y] > 0.2 * diff[Geom::X]) return 1; else return -1; return 0; } // we regenerate and resort the list every time, because user could have changed it while the // dialog was waiting SPItem *spellcheck_get_text (SPObject *root) { GSList *l = NULL; l = all_text_items (root, l, false, true); l = g_slist_sort(l, compare_text_bboxes); for (GSList *i = l; i; i = i->next) { SPItem *item = (SPItem *) i->data; if (!g_slist_find (_seen_objects, item)) { _seen_objects = g_slist_prepend(_seen_objects, item); g_slist_free(l); return item; } } g_slist_free(l); return NULL; } void spellcheck_sensitive (const gchar *cookie, gboolean gray) { GtkWidget *l = GTK_WIDGET(g_object_get_data(G_OBJECT (dlg), cookie)); gtk_widget_set_sensitive(l, gray); } static void spellcheck_enable_accept(GtkTreeSelection * /*selection*/, void * /*??*/) { spellcheck_sensitive ("b_accept", TRUE); } static void spellcheck_obj_modified (SPObject *obj, guint /*flags*/, gpointer /*data*/); static void spellcheck_obj_released (SPObject *obj, gpointer /*data*/); void spellcheck_next_text() { spellcheck_disconnect(); _text = spellcheck_get_text(_root); if (_text) { _release_connection = new sigc::connection (SP_OBJECT(_text)->connectRelease( sigc::bind<1>(sigc::ptr_fun(&spellcheck_obj_released), dlg))); _modified_connection = new sigc::connection (SP_OBJECT(_text)->connectModified( sigc::bind<2>(sigc::ptr_fun(&spellcheck_obj_modified), dlg))); _layout = te_get_layout (_text); _begin_w = _layout->begin(); } _end_w = _begin_w; _word.clear(); } bool spellcheck_init(SPDesktop *desktop) { _desktop = desktop; spellcheck_sensitive("suggestions", FALSE); spellcheck_sensitive("b_accept", FALSE); spellcheck_sensitive("b_ignore", FALSE); spellcheck_sensitive("b_ignore_once", FALSE); spellcheck_sensitive("b_add", FALSE); spellcheck_sensitive("addto_langs", FALSE); spellcheck_sensitive("b_start", FALSE); #ifdef WIN32 // on windows, dictionaries are in a lib/aspell-0.60 subdir off inkscape's executable dir; // this is some black magick to find out the executable path to give it to aspell char exeName[MAX_PATH+1]; GetModuleFileName(NULL, exeName, MAX_PATH); char *slashPos = strrchr(exeName, '\\'); if (slashPos) *slashPos = '\0'; g_print ("%s\n", exeName); #endif _stops = 0; _adds = 0; spellcheck_clear_rects(); { AspellConfig *config = new_aspell_config(); #ifdef WIN32 aspell_config_replace(config, "prefix", exeName); #endif aspell_config_replace(config, "lang", _lang); aspell_config_replace(config, "encoding", "UTF-8"); AspellCanHaveError *ret = new_aspell_speller(config); delete_aspell_config(config); if (aspell_error(ret) != 0) { g_warning("Error: %s\n", aspell_error_message(ret)); delete_aspell_can_have_error(ret); return false; } _speller = to_aspell_speller(ret); } if (_lang2) { AspellConfig *config = new_aspell_config(); #ifdef WIN32 aspell_config_replace(config, "prefix", exeName); #endif aspell_config_replace(config, "lang", _lang2); aspell_config_replace(config, "encoding", "UTF-8"); AspellCanHaveError *ret = new_aspell_speller(config); delete_aspell_config(config); if (aspell_error(ret) != 0) { g_warning("Error: %s\n", aspell_error_message(ret)); delete_aspell_can_have_error(ret); return false; } _speller2 = to_aspell_speller(ret); } if (_lang3) { AspellConfig *config = new_aspell_config(); #ifdef WIN32 aspell_config_replace(config, "prefix", exeName); #endif aspell_config_replace(config, "lang", _lang3); aspell_config_replace(config, "encoding", "UTF-8"); AspellCanHaveError *ret = new_aspell_speller(config); delete_aspell_config(config); if (aspell_error(ret) != 0) { g_warning("Error: %s\n", aspell_error_message(ret)); delete_aspell_can_have_error(ret); return false; } _speller3 = to_aspell_speller(ret); } _root = sp_desktop_document(desktop)->getRoot(); // empty the list of objects we've checked g_slist_free (_seen_objects); _seen_objects = NULL; // grab first text spellcheck_next_text(); _working = true; return true; } void spellcheck_finished () { aspell_speller_save_all_word_lists(_speller); delete_aspell_speller(_speller); _speller = NULL; if (_speller2) { aspell_speller_save_all_word_lists(_speller2); delete_aspell_speller(_speller2); _speller2 = NULL; } if (_speller3) { aspell_speller_save_all_word_lists(_speller3); delete_aspell_speller(_speller3); _speller3 = NULL; } spellcheck_clear_rects(); spellcheck_disconnect(); _desktop->clearWaitingCursor(); spellcheck_sensitive("suggestions", FALSE); spellcheck_sensitive("b_accept", FALSE); spellcheck_sensitive("b_ignore", FALSE); spellcheck_sensitive("b_ignore_once", FALSE); spellcheck_sensitive("b_add", FALSE); spellcheck_sensitive("addto_langs", FALSE); spellcheck_sensitive("b_stop", FALSE); spellcheck_sensitive("b_start", TRUE); { GtkWidget *l = GTK_WIDGET(g_object_get_data(G_OBJECT (dlg), "banner")); gchar *label; if (_stops) label = g_strdup_printf(_("Finished, %d words added to dictionary"), _adds); else label = g_strdup_printf(_("Finished, nothing suspicious found")); gtk_label_set_markup (GTK_LABEL(l), label); g_free(label); } g_slist_free(_seen_objects); _seen_objects = NULL; _desktop = NULL; _root = NULL; _working = false; } bool spellcheck_next_word() { if (!_working) return false; if (!_text) { spellcheck_finished(); return false; } _word.clear(); while (_word.size() == 0) { _begin_w = _end_w; if (!_layout || _begin_w == _layout->end()) { spellcheck_next_text(); return false; } if (!_layout->isStartOfWord(_begin_w)) { _begin_w.nextStartOfWord(); } _end_w = _begin_w; _end_w.nextEndOfWord(); _word = sp_te_get_string_multiline (_text, _begin_w, _end_w); } // try to link this word with the next if separated by ' void *rawptr; Glib::ustring::iterator text_iter; _layout->getSourceOfCharacter(_end_w, &rawptr, &text_iter); SPObject *char_item = SP_OBJECT(rawptr); if (SP_IS_STRING(char_item)) { int this_char = *text_iter; if (this_char == '\'' || this_char == 0x2019) { Inkscape::Text::Layout::iterator end_t = _end_w; end_t.nextCharacter(); _layout->getSourceOfCharacter(end_t, &rawptr, &text_iter); SPObject *char_item = SP_OBJECT(rawptr); if (SP_IS_STRING(char_item)) { int this_char = *text_iter; if (g_ascii_isalpha(this_char)) { // 's _end_w.nextEndOfWord(); _word = sp_te_get_string_multiline (_text, _begin_w, _end_w); } } } } // skip words containing digits if (_prefs->getInt(prefs_path + "ignorenumbers") != 0) { bool digits = false; for (unsigned int i = 0; i < _word.size(); i++) { if (g_unichar_isdigit(_word[i])) { digits = true; break; } } if (digits) { return false; } } // skip ALL-CAPS words if (_prefs->getInt(prefs_path + "ignoreallcaps") != 0) { bool allcaps = true; for (unsigned int i = 0; i < _word.size(); i++) { if (!g_unichar_isupper(_word[i])) { allcaps = false; break; } } if (allcaps) { return false; } } // run it by all active spellers int have = aspell_speller_check(_speller, _word.c_str(), -1); if (_speller2) have += aspell_speller_check(_speller2, _word.c_str(), -1); if (_speller3) have += aspell_speller_check(_speller3, _word.c_str(), -1); if (have == 0) { // not found in any! _stops ++; _desktop->clearWaitingCursor(); // display it in window { GtkWidget *l = GTK_WIDGET(g_object_get_data(G_OBJECT (dlg), "banner")); Glib::ustring langs = _lang; if (_lang2) langs = langs + ", " + _lang2; if (_lang3) langs = langs + ", " + _lang3; gchar *label = g_strdup_printf(_("Not in dictionary (%s): %s"), langs.c_str(), _word.c_str()); gtk_label_set_markup (GTK_LABEL(l), label); g_free(label); } spellcheck_sensitive("suggestions", TRUE); spellcheck_sensitive("b_ignore", TRUE); spellcheck_sensitive("b_ignore_once", TRUE); spellcheck_sensitive("b_add", TRUE); spellcheck_sensitive("addto_langs", TRUE); spellcheck_sensitive("b_stop", TRUE); // draw rect std::vector points = _layout->createSelectionShape(_begin_w, _end_w, _text->i2dt_affine()); Geom::Point tl, br; tl = br = points.front(); for (unsigned i = 0 ; i < points.size() ; i ++) { if (points[i][Geom::X] < tl[Geom::X]) tl[Geom::X] = points[i][Geom::X]; if (points[i][Geom::Y] < tl[Geom::Y]) tl[Geom::Y] = points[i][Geom::Y]; if (points[i][Geom::X] > br[Geom::X]) br[Geom::X] = points[i][Geom::X]; if (points[i][Geom::Y] > br[Geom::Y]) br[Geom::Y] = points[i][Geom::Y]; } // expand slightly Geom::Rect area = Geom::Rect(tl, br); double mindim = fabs(tl[Geom::Y] - br[Geom::Y]); if (fabs(tl[Geom::X] - br[Geom::X]) < mindim) mindim = fabs(tl[Geom::X] - br[Geom::X]); area.expandBy(MAX(0.05 * mindim, 1)); // create canvas path rectangle, red stroke SPCanvasItem *rect = sp_canvas_bpath_new(sp_desktop_sketch(_desktop), NULL); sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(rect), 0xff0000ff, 3.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(rect), 0, SP_WIND_RULE_NONZERO); SPCurve *curve = new SPCurve(); curve->moveto(area.corner(0)); curve->lineto(area.corner(1)); curve->lineto(area.corner(2)); curve->lineto(area.corner(3)); curve->lineto(area.corner(0)); sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(rect), curve); sp_canvas_item_show(rect); _rects = g_slist_prepend(_rects, rect); // scroll to make it all visible Geom::Point const center = _desktop->get_display_area().midpoint(); area.expandBy(0.5 * mindim); Geom::Point scrollto; double dist = 0; for (unsigned corner = 0; corner < 4; corner ++) { if (Geom::L2(area.corner(corner) - center) > dist) { dist = Geom::L2(area.corner(corner) - center); scrollto = area.corner(corner); } } _desktop->scroll_to_point (scrollto, 1.0); // select text; if in Text tool, position cursor to the beginning of word // unless it is already in the word if (_desktop->selection->singleItem() != _text) _desktop->selection->set (_text); if (tools_isactive(_desktop, TOOLS_TEXT)) { Inkscape::Text::Layout::iterator *cursor = sp_text_context_get_cursor_position(SP_TEXT_CONTEXT(_desktop->event_context), _text); if (!cursor) // some other text is selected there _desktop->selection->set (_text); else if (*cursor <= _begin_w || *cursor >= _end_w) sp_text_context_place_cursor (SP_TEXT_CONTEXT(_desktop->event_context), _text, _begin_w); } // get suggestions { GtkTreeView *tree_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT (dlg), "suggestions")); GtkListStore *model = gtk_list_store_new (1, G_TYPE_STRING); gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (model)); { const AspellWordList *wl = aspell_speller_suggest(_speller, _word.c_str(), -1); AspellStringEnumeration * els = aspell_word_list_elements(wl); const char *sugg; GtkTreeIter iter; while ((sugg = aspell_string_enumeration_next(els)) != 0) { gtk_list_store_append (GTK_LIST_STORE (model), &iter); gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, sugg, -1); } delete_aspell_string_enumeration(els); } if (_speller2) { const AspellWordList *wl = aspell_speller_suggest(_speller2, _word.c_str(), -1); AspellStringEnumeration * els = aspell_word_list_elements(wl); const char *sugg; GtkTreeIter iter; while ((sugg = aspell_string_enumeration_next(els)) != 0) { gtk_list_store_append (GTK_LIST_STORE (model), &iter); gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, sugg, -1); } delete_aspell_string_enumeration(els); } if (_speller3) { const AspellWordList *wl = aspell_speller_suggest(_speller3, _word.c_str(), -1); AspellStringEnumeration * els = aspell_word_list_elements(wl); const char *sugg; GtkTreeIter iter; while ((sugg = aspell_string_enumeration_next(els)) != 0) { gtk_list_store_append (GTK_LIST_STORE (model), &iter); gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, sugg, -1); } delete_aspell_string_enumeration(els); } spellcheck_sensitive("b_accept", FALSE); // gray it out until something is chosen } return true; } return false; } void spellcheck_delete_last_rect () { if (_rects) { sp_canvas_item_hide(SP_CANVAS_ITEM(_rects->data)); gtk_object_destroy(GTK_OBJECT(_rects->data)); _rects = _rects->next; // pop latest-prepended rect } } void do_spellcheck () { GtkWidget *l = GTK_WIDGET(g_object_get_data(G_OBJECT (dlg), "banner")); gtk_label_set_markup (GTK_LABEL(l), _("Checking...")); gtk_widget_queue_draw(GTK_WIDGET(dlg)); gdk_window_process_updates(gtk_widget_get_window (GTK_WIDGET(dlg)), TRUE); _desktop->setWaitingCursor(); while (_working) if (spellcheck_next_word()) break; } static void spellcheck_obj_modified (SPObject */*obj*/, guint /*flags*/, gpointer /*data*/) { if (_local_change) { // this was a change by this dialog, i.e. an Accept, skip it _local_change = false; return; } if (_working && _root) { // user may have edited the text we're checking; try to do the most sensible thing in this // situation // just in case, re-get text's layout _layout = te_get_layout (_text); // re-get the word _layout->validateIterator(&_begin_w); _end_w = _begin_w; _end_w.nextEndOfWord(); Glib::ustring word_new = sp_te_get_string_multiline (_text, _begin_w, _end_w); if (word_new != _word) { _end_w = _begin_w; spellcheck_delete_last_rect (); do_spellcheck (); // recheck this word and go ahead if it's ok } } } static void spellcheck_obj_released (SPObject */*obj*/, gpointer /*data*/) { if (_working && _root) { // the text object was deleted spellcheck_delete_last_rect (); spellcheck_next_text(); do_spellcheck (); // get next text and continue } } void sp_spellcheck_accept (GObject *, GObject *dlg) { // insert chosen suggestion GtkTreeView *tv = GTK_TREE_VIEW(g_object_get_data(G_OBJECT (dlg), "suggestions")); GtkTreeSelection *ts = gtk_tree_view_get_selection(tv); GtkTreeModel *model = 0; GtkTreeIter iter; if (gtk_tree_selection_get_selected(ts, &model, &iter)) { gchar *sugg; gtk_tree_model_get (model, &iter, 0, &sugg, -1); if (sugg) { //g_print("chosen: %s\n", sugg); _local_change = true; sp_te_replace(_text, _begin_w, _end_w, sugg); // find the end of the word anew _end_w = _begin_w; _end_w.nextEndOfWord(); DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_TEXT, _("Fix spelling")); } } spellcheck_delete_last_rect (); do_spellcheck(); // next word or end } void sp_spellcheck_ignore (GObject */*obj*/, GObject */*dlg*/) { aspell_speller_add_to_session(_speller, _word.c_str(), -1); if (_speller2) aspell_speller_add_to_session(_speller2, _word.c_str(), -1); if (_speller3) aspell_speller_add_to_session(_speller3, _word.c_str(), -1); spellcheck_delete_last_rect (); do_spellcheck(); // next word or end } void sp_spellcheck_ignore_once (GObject */*obj*/, GObject */*dlg*/) { spellcheck_delete_last_rect (); do_spellcheck(); // next word or end } void sp_spellcheck_add (GObject */*obj*/, GObject */*dlg*/) { _adds++; GtkComboBox *cbox = GTK_COMBO_BOX(g_object_get_data(G_OBJECT (dlg), "addto_langs")); gint num = gtk_combo_box_get_active(cbox); switch (num) { case 0: aspell_speller_add_to_personal(_speller, _word.c_str(), -1); break; case 1: if (_speller2) aspell_speller_add_to_personal(_speller2, _word.c_str(), -1); break; case 2: if (_speller3) aspell_speller_add_to_personal(_speller3, _word.c_str(), -1); break; default: break; } spellcheck_delete_last_rect (); do_spellcheck(); // next word or end } void sp_spellcheck_stop (GObject */*obj*/, GObject */*dlg*/) { spellcheck_finished(); } void sp_spellcheck_start (GObject *, GObject *) { if (spellcheck_init (SP_ACTIVE_DESKTOP)) do_spellcheck(); // next word or end } static gboolean spellcheck_desktop_deactivated(Inkscape::Application */*application*/, SPDesktop *desktop, void */*data*/) { if (_working) { if (_desktop == desktop) { spellcheck_finished(); } } return FALSE; } void sp_spellcheck_dialog (void) { _prefs = Inkscape::Preferences::get(); // take languages from prefs _lang = _lang2 = _lang3 = NULL; Glib::ustring lang = _prefs->getString(prefs_path + "lang"); if (lang != "") _lang = g_strdup(lang.c_str()); else _lang = g_strdup("en"); lang = _prefs->getString(prefs_path + "lang2"); if (lang != "") _lang2 = g_strdup(lang.c_str()); lang = _prefs->getString(prefs_path + "lang3"); if (lang != "") _lang3 = g_strdup(lang.c_str()); if (!dlg) { gchar title[500]; sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_SPELLCHECK), title); dlg = sp_window_new (title, TRUE); if (x == -1000 || y == -1000) { x = _prefs->getInt(prefs_path + "x", -1000); y = _prefs->getInt(prefs_path + "y", -1000); } if (w ==0 || h == 0) { w = _prefs->getInt(prefs_path + "w", 0); h = _prefs->getInt(prefs_path + "h", 0); } if (w && h) gtk_window_resize ((GtkWindow *) dlg, w, h); if (x >= 0 && y >= 0 && (x < (gdk_screen_width()-MIN_ONSCREEN_DISTANCE)) && (y < (gdk_screen_height()-MIN_ONSCREEN_DISTANCE))) { gtk_window_move ((GtkWindow *) dlg, x, y); } else { gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER); } sp_transientize (dlg); wd.win = dlg; wd.stop = 0; g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd ); g_signal_connect( G_OBJECT(INKSCAPE), "deactivate_desktop", G_CALLBACK( spellcheck_desktop_deactivated ), NULL); g_signal_connect ( G_OBJECT (dlg), "event", G_CALLBACK (sp_dialog_event_handler), dlg); g_signal_connect ( G_OBJECT (dlg), "destroy", G_CALLBACK (sp_spellcheck_dialog_destroy), NULL ); g_signal_connect ( G_OBJECT (dlg), "delete_event", G_CALLBACK (sp_spellcheck_dialog_delete), dlg); g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_spellcheck_dialog_delete), dlg); g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg); g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg); gtk_container_set_border_width (GTK_CONTAINER (dlg), 4); /* Toplevel vbox */ GtkWidget *vb = gtk_vbox_new (FALSE, 4); gtk_container_add (GTK_CONTAINER (dlg), vb); { GtkWidget *hb = gtk_hbox_new (FALSE, 0); GtkWidget *l = gtk_label_new (NULL); g_object_set_data (G_OBJECT (dlg), "banner", l); gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); } { GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); GtkListStore *model = gtk_list_store_new (1, G_TYPE_STRING); GtkWidget *tree_view = gtk_tree_view_new (); g_object_set_data (G_OBJECT (dlg), "suggestions", tree_view); gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_window), tree_view); gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (model)); GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree_view)); g_signal_connect (G_OBJECT(selection), "changed", G_CALLBACK (spellcheck_enable_accept), NULL); gtk_widget_show (tree_view); GtkCellRenderer *cell = gtk_cell_renderer_text_new (); GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Suggestions:"), cell, "text", 0, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), GTK_TREE_VIEW_COLUMN (column)); gtk_box_pack_start (GTK_BOX (vb), scrolled_window, TRUE, TRUE, 0); } { GtkWidget *hb = gtk_hbox_new (FALSE, 0); sp_spellcheck_new_button (dlg, hb, _("_Accept"), _("Accept the chosen suggestion"), sp_spellcheck_accept, "b_accept"); sp_spellcheck_new_button (dlg, hb, _("_Ignore once"), _("Ignore this word only once"), sp_spellcheck_ignore_once, "b_ignore_once"); sp_spellcheck_new_button (dlg, hb, _("_Ignore"), _("Ignore this word in this session"), sp_spellcheck_ignore, "b_ignore"); gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); } { GtkWidget *hb = gtk_hbox_new (FALSE, 0); sp_spellcheck_new_button (dlg, hb, _("A_dd to dictionary:"), _("Add this word to the chosen dictionary"), sp_spellcheck_add, "b_add"); // Backward compatibility fix: The GtkComboBoxText API was introduced with // GTK+ 2.24. This check should eventually be dropped when we bump our // GTK dependency. #if GTK_CHECK_VERSION(2, 24, 0) GtkWidget *cbox = gtk_combo_box_text_new(); gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (cbox), _lang); if (_lang2) { gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (cbox), _lang2); } if (_lang3) { gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (cbox), _lang3); } #else GtkWidget *cbox = gtk_combo_box_new_text(); gtk_combo_box_append_text (GTK_COMBO_BOX (cbox), _lang); if (_lang2) { gtk_combo_box_append_text (GTK_COMBO_BOX (cbox), _lang2); } if (_lang3) { gtk_combo_box_append_text (GTK_COMBO_BOX (cbox), _lang3); } #endif gtk_combo_box_set_active (GTK_COMBO_BOX (cbox), 0); gtk_widget_show_all (cbox); g_object_set_data (G_OBJECT (dlg), "addto_langs", cbox); gtk_box_pack_start (GTK_BOX (hb), cbox, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); } { GtkWidget *hs = gtk_hseparator_new (); gtk_box_pack_start (GTK_BOX (vb), hs, FALSE, FALSE, 0); } { GtkWidget *hb = gtk_hbox_new (FALSE, 0); sp_spellcheck_new_button (dlg, hb, _("_Stop"), _("Stop the check"), sp_spellcheck_stop, "b_stop"); sp_spellcheck_new_button (dlg, hb, _("_Start"), _("Start the check"), sp_spellcheck_start, "b_start"); gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); } gtk_widget_show_all (vb); } gtk_window_present ((GtkWindow *) dlg); // run it at once sp_spellcheck_start (NULL, NULL); } #else void sp_spellcheck_dialog (void) {} #endif // HAVE_ASPELL /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :