From fbf7c7f321e9c63763078889de6b8078cde29bab Mon Sep 17 00:00:00 2001 From: buliabyak Date: Tue, 17 Feb 2009 20:33:18 +0000 Subject: [PATCH] spellchecker --- src/dialogs/Makefile_insert | 2 + src/dialogs/spellcheck.cpp | 834 ++++++++++++++++++++++++++++++++++++ src/dialogs/spellcheck.h | 31 ++ src/menus-skeleton.h | 2 + src/preferences-skeleton.h | 1 + src/verbs.cpp | 11 + src/verbs.h | 2 + 7 files changed, 883 insertions(+) create mode 100644 src/dialogs/spellcheck.cpp create mode 100644 src/dialogs/spellcheck.h diff --git a/src/dialogs/Makefile_insert b/src/dialogs/Makefile_insert index 5d5614668..1b3358506 100644 --- a/src/dialogs/Makefile_insert +++ b/src/dialogs/Makefile_insert @@ -50,6 +50,8 @@ dialogs_libspdialogs_a_SOURCES = \ dialogs/rdf.h \ dialogs/sp-attribute-widget.cpp \ dialogs/sp-attribute-widget.h \ + dialogs/spellcheck.cpp \ + dialogs/spellcheck.h \ dialogs/stroke-style.cpp \ dialogs/stroke-style.h \ dialogs/swatches.cpp \ diff --git a/src/dialogs/spellcheck.cpp b/src/dialogs/spellcheck.cpp new file mode 100644 index 000000000..853c6bcf5 --- /dev/null +++ b/src/dialogs/spellcheck.cpp @@ -0,0 +1,834 @@ +/** @file + * @brief Spellcheck dialog + */ +/* Authors: + * bulia byak + * + * 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 +#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" + +#ifdef WIN32 +#include +#endif + +#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 AspellSpeller *_speller = NULL; +static SPObject *_root; + +// 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; + + + +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_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_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_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; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + 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, GtkTooltips *tt, const gchar *tip, void (*function) (GObject *, GObject *), const gchar *cookie) +{ + GtkWidget *b = gtk_button_new_with_mnemonic (label); + gtk_tooltips_set_tip (tt, b, tip, NULL); + gtk_box_pack_start (GTK_BOX (hb), b, TRUE, TRUE, 0); + g_signal_connect ( G_OBJECT (b), "clicked", G_CALLBACK (function), dlg ); + gtk_object_set_data (GTK_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 (SP_OBJECT_REPR (r)->name(), "svg:metadata")) + return l; // we're not interested in metadata + + for (SPObject *child = sp_object_first_child(r); child; child = SP_OBJECT_NEXT (child)) { + if (SP_IS_ITEM (child) && !SP_OBJECT_IS_CLONED (child) && !_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->getBounds(sp_item_i2d_affine(i1)); + Geom::OptRect bbox2 = i2->getBounds(sp_item_i2d_affine(i2)); + 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(gtk_object_get_data (GTK_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_add", FALSE); + spellcheck_sensitive("b_start", FALSE); + + _stops = 0; + _adds = 0; + + spellcheck_clear_rects(); + + AspellConfig *config = new_aspell_config(); + +#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); + aspell_config_replace(config, "prefix", exeName); +#endif + + // take language from prefs + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring lang = prefs->getString(prefs_path + "lang"); + if (lang != "") + aspell_config_replace(config, "lang", lang.c_str()); + else + aspell_config_replace(config, "lang", "en_US"); + + aspell_config_replace(config, "encoding", "UTF-8"); + + // create speller + 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); + + _root = SP_DOCUMENT_ROOT (sp_desktop_document (desktop)); + + // 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; + + spellcheck_clear_rects(); + spellcheck_disconnect(); + + _desktop->clearWaitingCursor(); + + spellcheck_sensitive("suggestions", FALSE); + spellcheck_sensitive("b_accept", FALSE); + spellcheck_sensitive("b_ignore", FALSE); + spellcheck_sensitive("b_add", FALSE); + spellcheck_sensitive("b_stop", FALSE); + spellcheck_sensitive("b_start", TRUE); + + { + GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_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 + bool digits = false; + for (gchar *c = (gchar *) _word.c_str(); *c; c++) { + if (g_ascii_isdigit(*c)) { + digits = true; + } + } + if (digits) { + return false; + } + + //g_print ("%s\n", word.c_str()); + + int have = aspell_speller_check(_speller, _word.c_str(), -1); + if (have == 0) { // not found + _stops ++; + + _desktop->clearWaitingCursor(); + + // display it in window + { + GtkWidget *l = GTK_WIDGET(gtk_object_get_data (GTK_OBJECT (dlg), "banner")); + gchar *label = g_strdup_printf(_("Not in dictionary: %s"), _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_add", TRUE); + spellcheck_sensitive("b_stop", TRUE); + + // draw rect + std::vector points = + _layout->createSelectionShape(_begin_w, _end_w, sp_item_i2d_affine(_text)); + 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); + + // if in Text tool, position cursor to the beginnign of word + // unless it is already in the word + 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); + } else { // just select the object + _desktop->selection->set (_text); + } + + // get suggestions + { + const AspellWordList *wl = aspell_speller_suggest(_speller, _word.c_str(), -1); + AspellStringEnumeration * els = aspell_word_list_elements(wl); + const char *sugg; + GtkTreeView *tree_view = + GTK_TREE_VIEW(gtk_object_get_data (GTK_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)); + 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(gtk_object_get_data (GTK_OBJECT (dlg), "banner")); + gtk_label_set_markup (GTK_LABEL(l), _("Checking...")); + gtk_widget_queue_draw(GTK_WIDGET(dlg)); + gdk_window_process_updates(GTK_WIDGET(dlg)->window, 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(gtk_object_get_data (GTK_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(); + sp_document_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 *, GObject *dlg) +{ + aspell_speller_add_to_session(_speller, _word.c_str(), -1); + spellcheck_delete_last_rect (); + + do_spellcheck(); // next word or end +} + +void +sp_spellcheck_add (GObject *, GObject *dlg) +{ + _adds++; + aspell_speller_add_to_personal(_speller, _word.c_str(), -1); + spellcheck_delete_last_rect (); + + do_spellcheck(); // next word or end +} + +void +sp_spellcheck_stop (GObject *, 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) +{ + if (!dlg) + { + gchar title[500]; + sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_SPELLCHECK), title); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + 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); + + + gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg); + + gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_spellcheck_dialog_destroy), NULL ); + gtk_signal_connect ( GTK_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); + + GtkTooltips *tt = gtk_tooltips_new (); + + 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); + gtk_object_set_data (GTK_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 (); + gtk_object_set_data (GTK_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"), tt, _("Accept the chosen suggestion"), + sp_spellcheck_accept, "b_accept"); + sp_spellcheck_new_button (dlg, hb, _("_Ignore"), tt, _("Ignore this word in this session"), + sp_spellcheck_ignore, "b_ignore"); + sp_spellcheck_new_button (dlg, hb, _("A_dd"), tt, _("Add this word to the dictionary"), + sp_spellcheck_add, "b_add"); + 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"), tt, _("Stop the check"), + sp_spellcheck_stop, "b_stop"); + sp_spellcheck_new_button (dlg, hb, _("_Start"), tt, _("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); +} + + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/dialogs/spellcheck.h b/src/dialogs/spellcheck.h new file mode 100644 index 000000000..b941788ca --- /dev/null +++ b/src/dialogs/spellcheck.h @@ -0,0 +1,31 @@ +/** @file + * @brief Spellcheck dialog + */ +/* Authors: + * bulia byak + * + * Copyright (C) 2009 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_SPELLCHECK_H +#define SEEN_SPELLCHECK_H + +#include + +void sp_spellcheck_dialog(); + + +#endif /* !SEEN_SPELLCHECK_H */ + +/* + 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 : diff --git a/src/menus-skeleton.h b/src/menus-skeleton.h index 9840a1aa1..5d110483d 100644 --- a/src/menus-skeleton.h +++ b/src/menus-skeleton.h @@ -238,6 +238,8 @@ static char const menus_skeleton[] = " \n" " \n" " \n" +" \n" +" \n" " \n" " \n" " \n" diff --git a/src/preferences-skeleton.h b/src/preferences-skeleton.h index 64a2b4ba1..65d30d6a6 100644 --- a/src/preferences-skeleton.h +++ b/src/preferences-skeleton.h @@ -174,6 +174,7 @@ static char const preferences_skeleton[] = " \n" " \n" " \n" +" \n" " \n" " \n" " \n" diff --git a/src/verbs.cpp b/src/verbs.cpp index 78c4423f5..56d4e667d 100644 --- a/src/verbs.cpp +++ b/src/verbs.cpp @@ -42,6 +42,7 @@ #include "dialogs/xml-tree.h" #include "dialogs/item-properties.h" #include "dialogs/find.h" +#include "dialogs/spellcheck.h" #include "dialogs/layer-properties.h" #include "dialogs/clonetiler.h" #include "dialogs/iconpreview.h" @@ -1791,6 +1792,12 @@ DialogVerb::perform(SPAction *action, void *data, void */*pdata*/) // Please test the new find dialog if you have time: // dt->_dlg_mgr->showDialog("Find"); break; + case SP_VERB_DIALOG_FINDREPLACE: + // not implemented yet + break; + case SP_VERB_DIALOG_SPELLCHECK: + sp_spellcheck_dialog(); + break; case SP_VERB_DIALOG_DEBUG: dt->_dlg_mgr->showDialog("Messages"); break; @@ -2634,6 +2641,10 @@ Verb *Verb::_base_verbs[] = { N_("View and edit the XML tree of the document"), "xml_editor"), new DialogVerb(SP_VERB_DIALOG_FIND, "DialogFind", N_("_Find..."), N_("Find objects in document"), GTK_STOCK_FIND ), + new DialogVerb(SP_VERB_DIALOG_FINDREPLACE, "DialogFindReplace", N_("Find and _Replace Text..."), + N_("Find and replace text in document"), GTK_STOCK_FIND_AND_REPLACE ), + new DialogVerb(SP_VERB_DIALOG_SPELLCHECK, "DialogSpellcheck", N_("Check Spellin_g..."), + N_("Check spelling of text in document"), GTK_STOCK_SPELL_CHECK ), new DialogVerb(SP_VERB_DIALOG_DEBUG, "DialogDebug", N_("_Messages..."), N_("View debug messages"), "messages"), new DialogVerb(SP_VERB_DIALOG_SCRIPT, "DialogScript", N_("S_cripts..."), diff --git a/src/verbs.h b/src/verbs.h index 7798f6364..c3b88918b 100644 --- a/src/verbs.h +++ b/src/verbs.h @@ -231,6 +231,8 @@ enum { SP_VERB_DIALOG_TEXT, SP_VERB_DIALOG_XML_EDITOR, SP_VERB_DIALOG_FIND, + SP_VERB_DIALOG_FINDREPLACE, + SP_VERB_DIALOG_SPELLCHECK, SP_VERB_DIALOG_DEBUG, SP_VERB_DIALOG_SCRIPT, SP_VERB_DIALOG_TOGGLE, -- 2.30.2