diff options
Diffstat (limited to 'src/text-context.cpp')
| -rw-r--r-- | src/text-context.cpp | 1765 |
1 files changed, 0 insertions, 1765 deletions
diff --git a/src/text-context.cpp b/src/text-context.cpp deleted file mode 100644 index cc27e5589..000000000 --- a/src/text-context.cpp +++ /dev/null @@ -1,1765 +0,0 @@ -/* - * TextTool - * - * Authors: - * Lauris Kaplinski <lauris@kaplinski.com> - * bulia byak <buliabyak@users.sf.net> - * Jon A. Cruz <jon@joncruz.org> - * Abhishek Sharma - * - * Copyright (C) 1999-2005 authors - * Copyright (C) 2001 Ximian, Inc. - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifdef HAVE_CONFIG_H -# include <config.h> -#endif - -#include <gtkmm/clipboard.h> -#include <display/sp-ctrlline.h> -#include <display/sodipodi-ctrlrect.h> -#include <display/sp-ctrlquadr.h> -#include <gdk/gdkkeysyms.h> -#include <glibmm/i18n.h> -#include <sstream> - -#include "context-fns.h" -#include "desktop-handles.h" -#include "desktop-style.h" -#include "desktop.h" -#include "document.h" -#include "document-undo.h" -#include "macros.h" -#include "message-context.h" -#include "message-stack.h" -#include "pixmaps/cursor-text-insert.xpm" -#include "pixmaps/cursor-text.xpm" -#include "preferences.h" -#include "rubberband.h" -#include "selection-chemistry.h" -#include "selection.h" -#include "shape-editor.h" -#include "sp-flowtext.h" -#include "sp-namedview.h" -#include "sp-text.h" -#include "style.h" -#include "text-context.h" -#include "text-editing.h" -#include "ui/control-manager.h" -#include "verbs.h" -#include "xml/node-event-vector.h" -#include "xml/repr.h" -#include <gtk/gtk.h> -#include "tool-factory.h" - -using Inkscape::ControlManager; -using Inkscape::DocumentUndo; - -namespace Inkscape { -namespace UI { -namespace Tools { - -static void sp_text_context_selection_changed(Inkscape::Selection *selection, TextTool *tc); -static void sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, TextTool *tc); -static bool sp_text_context_style_set(SPCSSAttr const *css, TextTool *tc); -static int sp_text_context_style_query(SPStyle *style, int property, TextTool *tc); - -static void sp_text_context_validate_cursor_iterators(TextTool *tc); -static void sp_text_context_update_cursor(TextTool *tc, bool scroll_to_see = true); -static void sp_text_context_update_text_selection(TextTool *tc); -static gint sp_text_context_timeout(TextTool *tc); -static void sp_text_context_forget_text(TextTool *tc); - -static gint sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, TextTool *tc); -static gint sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, TextTool *tc); -static void sptc_commit(GtkIMContext *imc, gchar *string, TextTool *tc); - -namespace { - ToolBase* createTextContext() { - return new TextTool(); - } - - bool textContextRegistered = ToolFactory::instance().registerObject("/tools/text", createTextContext); -} - -const std::string& TextTool::getPrefsPath() { - return TextTool::prefsPath; -} - -const std::string TextTool::prefsPath = "/tools/text"; - - -TextTool::TextTool() : ToolBase() { - this->preedit_string = 0; - this->unipos = 0; - - this->cursor_shape = cursor_text_xpm; - this->hot_x = 7; - this->hot_y = 7; - - this->xp = 0; - this->yp = 0; - this->tolerance = 0; - this->within_tolerance = false; - - this->imc = NULL; - - this->text = NULL; - this->pdoc = Geom::Point(0, 0); - - this->unimode = false; - - this->cursor = NULL; - this->indicator = NULL; - this->frame = NULL; - this->grabbed = NULL; - this->timeout = 0; - this->show = FALSE; - this->phase = 0; - this->nascent_object = 0; - this->over_text = 0; - this->dragging = 0; - this->creating = 0; -} - -TextTool::~TextTool() { - delete this->shape_editor; - this->shape_editor = NULL; - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); - this->grabbed = NULL; - } - - Inkscape::Rubberband::get(this->desktop)->stop(); -} - -void TextTool::setup() { - GtkSettings* settings = gtk_settings_get_default(); - gint timeout = 0; - g_object_get( settings, "gtk-cursor-blink-time", &timeout, NULL ); - - if (timeout < 0) { - timeout = 200; - } else { - timeout /= 2; - } - - this->cursor = ControlManager::getManager().createControlLine(sp_desktop_controls(desktop), Geom::Point(100, 0), Geom::Point(100, 100)); - this->cursor->setRgba32(0x000000ff); - sp_canvas_item_hide(this->cursor); - - this->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL); - SP_CTRLRECT(this->indicator)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100))); - SP_CTRLRECT(this->indicator)->setColor(0x0000ff7f, false, 0); - sp_canvas_item_hide(this->indicator); - - this->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL); - SP_CTRLRECT(this->frame)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100))); - SP_CTRLRECT(this->frame)->setColor(0x0000ff7f, false, 0); - sp_canvas_item_hide(this->frame); - - this->timeout = g_timeout_add(timeout, (GSourceFunc) sp_text_context_timeout, this); - - this->imc = gtk_im_multicontext_new(); - if (this->imc) { - GtkWidget *canvas = GTK_WIDGET(sp_desktop_canvas(desktop)); - - /* im preedit handling is very broken in inkscape for - * multi-byte characters. See bug 1086769. - * We need to let the IM handle the preediting, and - * just take in the characters when they're finished being - * entered. - */ - gtk_im_context_set_use_preedit(this->imc, FALSE); - gtk_im_context_set_client_window(this->imc, - gtk_widget_get_window (canvas)); - - g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), this); - g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), this); - g_signal_connect(G_OBJECT(this->imc), "commit", G_CALLBACK(sptc_commit), this); - - if (gtk_widget_has_focus(canvas)) { - sptc_focus_in(canvas, NULL, this); - } - } - - ToolBase::setup(); - - this->shape_editor = new ShapeEditor(this->desktop); - - SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); - if (item && SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) { - this->shape_editor->set_item(item, SH_KNOTHOLDER); - } - - this->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged( - sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), this) - ); - this->sel_modified_connection = sp_desktop_selection(desktop)->connectModified( - sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), this) - ); - this->style_set_connection = desktop->connectSetStyle( - sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), this) - ); - this->style_query_connection = desktop->connectQueryStyle( - sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), this) - ); - - sp_text_context_selection_changed(sp_desktop_selection(desktop), this); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/text/selcue")) { - this->enableSelectionCue(); - } - if (prefs->getBool("/tools/text/gradientdrag")) { - this->enableGrDrag(); - } -} - -void TextTool::finish() { - if (this->desktop) { - sp_signal_disconnect_by_data(sp_desktop_canvas(this->desktop), this); - } - - this->enableGrDrag(false); - - this->style_set_connection.disconnect(); - this->style_query_connection.disconnect(); - this->sel_changed_connection.disconnect(); - this->sel_modified_connection.disconnect(); - - sp_text_context_forget_text(SP_TEXT_CONTEXT(this)); - - if (this->imc) { - g_object_unref(G_OBJECT(this->imc)); - this->imc = NULL; - } - - if (this->timeout) { - g_source_remove(this->timeout); - this->timeout = 0; - } - - if (this->cursor) { - sp_canvas_item_destroy(this->cursor); - this->cursor = NULL; - } - - if (this->indicator) { - sp_canvas_item_destroy(this->indicator); - this->indicator = NULL; - } - - if (this->frame) { - sp_canvas_item_destroy(this->frame); - this->frame = NULL; - } - - for (std::vector<SPCanvasItem*>::iterator it = this->text_selection_quads.begin() ; - it != this->text_selection_quads.end() ; ++it) { - sp_canvas_item_hide(*it); - sp_canvas_item_destroy(*it); - } - - this->text_selection_quads.clear(); -} - -bool TextTool::item_handler(SPItem* item, GdkEvent* event) { - SPItem *item_ungrouped; - - gint ret = FALSE; - - sp_text_context_validate_cursor_iterators(this); - Inkscape::Text::Layout::iterator old_start = this->text_sel_start; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - // find out clicked item, disregarding groups - item_ungrouped = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE); - if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) { - sp_desktop_selection(desktop)->set(item_ungrouped); - if (this->text) { - // find out click point in document coordinates - Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y)); - // set the cursor closest to that point - if (event->button.state & GDK_SHIFT_MASK) { - this->text_sel_start = old_start; - this->text_sel_end = sp_te_get_position_by_coords(this->text, p); - } else { - this->text_sel_start = this->text_sel_end = sp_te_get_position_by_coords(this->text, p); - } - // update display - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - this->dragging = 1; - } - ret = TRUE; - } - } - break; - case GDK_2BUTTON_PRESS: - if (event->button.button == 1 && this->text) { - Inkscape::Text::Layout const *layout = te_get_layout(this->text); - if (layout) { - if (!layout->isStartOfWord(this->text_sel_start)) - this->text_sel_start.prevStartOfWord(); - if (!layout->isEndOfWord(this->text_sel_end)) - this->text_sel_end.nextEndOfWord(); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - this->dragging = 2; - ret = TRUE; - } - } - break; - case GDK_3BUTTON_PRESS: - if (event->button.button == 1 && this->text) { - this->text_sel_start.thisStartOfLine(); - this->text_sel_end.thisEndOfLine(); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - this->dragging = 3; - ret = TRUE; - } - break; - case GDK_BUTTON_RELEASE: - if (event->button.button == 1 && this->dragging && !this->space_panning) { - this->dragging = 0; - sp_event_context_discard_delayed_snap_event(this); - ret = TRUE; - } - break; - case GDK_MOTION_NOTIFY: - if ((event->motion.state & GDK_BUTTON1_MASK) && this->dragging && !this->space_panning) { - Inkscape::Text::Layout const *layout = te_get_layout(this->text); - if (!layout) break; - // find out click point in document coordinates - Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y)); - // set the cursor closest to that point - Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(this->text, p); - if (this->dragging == 2) { - // double-click dragging: go by word - if (new_end < this->text_sel_start) { - if (!layout->isStartOfWord(new_end)) - new_end.prevStartOfWord(); - } else - if (!layout->isEndOfWord(new_end)) - new_end.nextEndOfWord(); - } else if (this->dragging == 3) { - // triple-click dragging: go by line - if (new_end < this->text_sel_start) - new_end.thisStartOfLine(); - else - new_end.thisEndOfLine(); - } - // update display - if (this->text_sel_end != new_end) { - this->text_sel_end = new_end; - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - } - gobble_motion_events(GDK_BUTTON1_MASK); - ret = TRUE; - break; - } - // find out item under mouse, disregarding groups - item_ungrouped = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE); - if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) { - - Inkscape::Text::Layout const *layout = te_get_layout(item_ungrouped); - if (layout->inputTruncated()) { - SP_CTRLRECT(this->indicator)->setColor(0xff0000ff, false, 0); - } else { - SP_CTRLRECT(this->indicator)->setColor(0x0000ff7f, false, 0); - } - Geom::OptRect ibbox = item_ungrouped->desktopVisualBounds(); - if (ibbox) { - SP_CTRLRECT(this->indicator)->setRectangle(*ibbox); - } - sp_canvas_item_show(this->indicator); - - this->cursor_shape = cursor_text_insert_xpm; - this->hot_x = 7; - this->hot_y = 10; - this->sp_event_context_update_cursor(); - sp_text_context_update_text_selection(this); - - if (SP_IS_TEXT (item_ungrouped)) { - desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text.")); - } else { - desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the flowed text, <b>drag</b> to select part of the text.")); - } - - this->over_text = true; - - ret = TRUE; - } - break; - default: - break; - } - - if (!ret) { - ret = ToolBase::item_handler(item, event); - } - - return ret; -} - -static void sp_text_context_setup_text(TextTool *tc) -{ - ToolBase *ec = SP_EVENT_CONTEXT(tc); - - /* Create <text> */ - Inkscape::XML::Document *xml_doc = SP_EVENT_CONTEXT_DESKTOP(ec)->doc()->getReprDoc(); - Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text"); - rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create - - /* Set style */ - sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "/tools/text", true); - - sp_repr_set_svg_double(rtext, "x", tc->pdoc[Geom::X]); - sp_repr_set_svg_double(rtext, "y", tc->pdoc[Geom::Y]); - - /* Create <tspan> */ - Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan"); - rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan? - rtext->addChild(rtspan, NULL); - Inkscape::GC::release(rtspan); - - /* Create TEXT */ - Inkscape::XML::Node *rstring = xml_doc->createTextNode(""); - rtspan->addChild(rstring, NULL); - Inkscape::GC::release(rstring); - SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext)); - /* fixme: Is selection::changed really immediate? */ - /* yes, it's immediate .. why does it matter? */ - sp_desktop_selection(ec->desktop)->set(text_item); - Inkscape::GC::release(rtext); - text_item->transform = SP_ITEM(ec->desktop->currentLayer())->i2doc_affine().inverse(); - - text_item->updateRepr(); - text_item->doWriteTransform(text_item->getRepr(), text_item->transform, NULL, true); - DocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, - _("Create text")); -} - -/** - * Insert the character indicated by tc.uni to replace the current selection, - * and reset tc.uni/tc.unipos to empty string. - * - * \pre tc.uni/tc.unipos non-empty. - */ -static void insert_uni_char(TextTool *const tc) -{ - g_return_if_fail(tc->unipos - && tc->unipos < sizeof(tc->uni) - && tc->uni[tc->unipos] == '\0'); - unsigned int uv; - std::stringstream ss; - ss << std::hex << tc->uni; - ss >> uv; - tc->unipos = 0; - tc->uni[tc->unipos] = '\0'; - - if ( !g_unichar_isprint(static_cast<gunichar>(uv)) - && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) { - // This may be due to bad input, so it goes to statusbar. - tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, - _("Non-printable character")); - } else { - if (!tc->text) { // printable key; create text if none (i.e. if nascent_object) - sp_text_context_setup_text(tc); - tc->nascent_object = 0; // we don't need it anymore, having created a real <text> - } - - gchar u[10]; - guint const len = g_unichar_to_utf8(uv, u); - u[len] = '\0'; - - tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u); - sp_text_context_update_cursor(tc); - sp_text_context_update_text_selection(tc); - DocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM, - _("Insert Unicode character")); - } -} - -static void hex_to_printable_utf8_buf(char const *const ehex, char *utf8) -{ - unsigned int uv; - std::stringstream ss; - ss << std::hex << ehex; - ss >> uv; - if (!g_unichar_isprint((gunichar) uv)) { - uv = 0xfffd; - } - guint const len = g_unichar_to_utf8(uv, utf8); - utf8[len] = '\0'; -} - -static void show_curr_uni_char(TextTool *const tc) -{ - g_return_if_fail(tc->unipos < sizeof(tc->uni) - && tc->uni[tc->unipos] == '\0'); - if (tc->unipos) { - char utf8[10]; - hex_to_printable_utf8_buf(tc->uni, utf8); - - /* Status bar messages are in pango markup, so we need xml escaping. */ - if (utf8[1] == '\0') { - switch(utf8[0]) { - case '<': strcpy(utf8, "<"); break; - case '>': strcpy(utf8, ">"); break; - case '&': strcpy(utf8, "&"); break; - default: break; - } - } - tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE, - _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8); - } else { - tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): ")); - } -} - -bool TextTool::root_handler(GdkEvent* event) { - sp_canvas_item_hide(this->indicator); - - sp_text_context_validate_cursor_iterators(this); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - - if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) { - return TRUE; - } - - // save drag origin - this->xp = (gint) event->button.x; - this->yp = (gint) event->button.y; - this->within_tolerance = true; - - Geom::Point const button_pt(event->button.x, event->button.y); - Geom::Point button_dt(desktop->w2d(button_pt)); - - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - - this->p0 = button_dt; - Inkscape::Rubberband::get(desktop)->start(desktop, this->p0); - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK, - NULL, event->button.time); - this->grabbed = SP_CANVAS_ITEM(desktop->acetate); - this->creating = 1; - - /* Processed */ - return TRUE; - } - break; - case GDK_MOTION_NOTIFY: - if (this->over_text) { - this->over_text = 0; - // update cursor and statusbar: we are not over a text object now - this->cursor_shape = cursor_text_xpm; - this->hot_x = 7; - this->hot_y = 7; - this->sp_event_context_update_cursor(); - desktop->event_context->defaultMessageContext()->clear(); - } - - if (this->creating && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { - if ( this->within_tolerance - && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) - && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to draw, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - this->within_tolerance = false; - - Geom::Point const motion_pt(event->motion.x, event->motion.y); - Geom::Point p = desktop->w2d(motion_pt); - - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - - Inkscape::Rubberband::get(desktop)->move(p); - gobble_motion_events(GDK_BUTTON1_MASK); - - // status text - Inkscape::Util::Quantity x_q = Inkscape::Util::Quantity(fabs((p - this->p0)[Geom::X]), "px"); - Inkscape::Util::Quantity y_q = Inkscape::Util::Quantity(fabs((p - this->p0)[Geom::Y]), "px"); - GString *xs = g_string_new(x_q.string(desktop->namedview->doc_units).c_str()); - GString *ys = g_string_new(y_q.string(desktop->namedview->doc_units).c_str()); - this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s × %s"), xs->str, ys->str); - - g_string_free(xs, FALSE); - g_string_free(ys, FALSE); - - } else if (!sp_event_context_knot_mouseover(this)) { - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE)); - m.unSetup(); - } - break; - case GDK_BUTTON_RELEASE: - if (event->button.button == 1 && !this->space_panning) { - sp_event_context_discard_delayed_snap_event(this); - - Geom::Point p1 = desktop->w2d(Geom::Point(event->button.x, event->button.y)); - - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - m.freeSnapReturnByRef(p1, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); - this->grabbed = NULL; - } - - Inkscape::Rubberband::get(desktop)->stop(); - - if (this->creating && this->within_tolerance) { - /* Button 1, set X & Y & new item */ - sp_desktop_selection(desktop)->clear(); - this->pdoc = desktop->dt2doc(p1); - this->show = TRUE; - this->phase = 1; - this->nascent_object = 1; // new object was just created - - /* Cursor */ - sp_canvas_item_show(this->cursor); - // Cursor height is defined by the new text object's font size; it needs to be set - // artificially here, for the text object does not exist yet: - double cursor_height = sp_desktop_get_font_size_tool(desktop); - this->cursor->setCoords(p1, p1 + Geom::Point(0, cursor_height)); - if (this->imc) { - GdkRectangle im_cursor; - Geom::Point const top_left = SP_EVENT_CONTEXT(this)->desktop->get_display_area().corner(3); - Geom::Point const cursor_size(0, cursor_height); - Geom::Point const im_position = SP_EVENT_CONTEXT(this)->desktop->d2w(p1 + cursor_size - top_left); - im_cursor.x = (int) floor(im_position[Geom::X]); - im_cursor.y = (int) floor(im_position[Geom::Y]); - im_cursor.width = 0; - im_cursor.height = (int) -floor(SP_EVENT_CONTEXT(this)->desktop->d2w(cursor_size)[Geom::Y]); - gtk_im_context_set_cursor_location(this->imc, &im_cursor); - } - this->message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line.")); // FIXME:: this is a copy of a string from _update_cursor below, do not desync - - this->within_tolerance = false; - } else if (this->creating) { - double cursor_height = sp_desktop_get_font_size_tool(desktop); - if (fabs(p1[Geom::Y] - this->p0[Geom::Y]) > cursor_height) { - // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance) - SPItem *ft = create_flowtext_with_internal_frame (desktop, this->p0, p1); - /* Set style */ - sp_desktop_apply_style_tool(desktop, ft->getRepr(), "/tools/text", true); - sp_desktop_selection(desktop)->set(ft); - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created.")); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("Create flowed text")); - } else { - desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created.")); - } - } - this->creating = false; - return TRUE; - } - break; - case GDK_KEY_PRESS: { - guint const group0_keyval = get_group0_keyval(&event->key); - - if (group0_keyval == GDK_KEY_KP_Add || - group0_keyval == GDK_KEY_KP_Subtract) { - if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys - break; // otherwise pass on keypad +/- so they can zoom - } - - if ((this->text) || (this->nascent_object)) { - // there is an active text object in this context, or a new object was just created - - if (this->unimode || !this->imc - || (MOD__CTRL(event) && MOD__SHIFT(event)) // input methods tend to steal this for unimode, - // but we have our own so make sure they don't swallow it - || !gtk_im_context_filter_keypress(this->imc, (GdkEventKey*) event)) { - //IM did not consume the key, or we're in unimode - - if (!MOD__CTRL_ONLY(event) && this->unimode) { - /* TODO: ISO 14755 (section 3 Definitions) says that we should also - accept the first 6 characters of alphabets other than the latin - alphabet "if the Latin alphabet is not used". The below is also - reasonable (viz. hope that the user's keyboard includes latin - characters and force latin interpretation -- just as we do for our - keyboard shortcuts), but differs from the ISO 14755 - recommendation. */ - switch (group0_keyval) { - case GDK_KEY_space: - case GDK_KEY_KP_Space: { - if (this->unipos) { - insert_uni_char(this); - } - /* Stay in unimode. */ - show_curr_uni_char(this); - return TRUE; - } - - case GDK_KEY_BackSpace: { - g_return_val_if_fail(this->unipos < sizeof(this->uni), TRUE); - if (this->unipos) { - this->uni[--this->unipos] = '\0'; - } - show_curr_uni_char(this); - return TRUE; - } - - case GDK_KEY_Return: - case GDK_KEY_KP_Enter: { - if (this->unipos) { - insert_uni_char(this); - } - /* Exit unimode. */ - this->unimode = false; - this->defaultMessageContext()->clear(); - return TRUE; - } - - case GDK_KEY_Escape: { - // Cancel unimode. - this->unimode = false; - gtk_im_context_reset(this->imc); - this->defaultMessageContext()->clear(); - return TRUE; - } - - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - break; - - default: { - if (g_ascii_isxdigit(group0_keyval)) { - g_return_val_if_fail(this->unipos < sizeof(this->uni) - 1, TRUE); - this->uni[this->unipos++] = group0_keyval; - this->uni[this->unipos] = '\0'; - if (this->unipos == 8) { - /* This behaviour is partly to allow us to continue to - use a fixed-length buffer for tc->uni. Reason for - choosing the number 8 is that it's the length of - ``canonical form'' mentioned in the ISO 14755 spec. - An advantage over choosing 6 is that it allows using - backspace for typos & misremembering when entering a - 6-digit number. */ - insert_uni_char(this); - } - show_curr_uni_char(this); - return TRUE; - } else { - /* The intent is to ignore but consume characters that could be - typos for hex digits. Gtk seems to ignore & consume all - non-hex-digits, and we do similar here. Though note that some - shortcuts (like keypad +/- for zoom) get processed before - reaching this code. */ - return TRUE; - } - } - } - } - - Inkscape::Text::Layout::iterator old_start = this->text_sel_start; - Inkscape::Text::Layout::iterator old_end = this->text_sel_end; - bool cursor_moved = false; - int screenlines = 1; - if (this->text) { - double spacing = sp_te_get_average_linespacing(this->text); - Geom::Rect const d = desktop->get_display_area(); - screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1; - if (screenlines <= 0) - screenlines = 1; - } - - /* Neither unimode nor IM consumed key; process text tool shortcuts */ - switch (group0_keyval) { - case GDK_KEY_x: - case GDK_KEY_X: - if (MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx-text"); - return TRUE; - } - break; - case GDK_KEY_space: - if (MOD__CTRL_ONLY(event)) { - /* No-break space */ - if (!this->text) { // printable key; create text if none (i.e. if nascent_object) - sp_text_context_setup_text(this); - this->nascent_object = 0; // we don't need it anymore, having created a real <text> - } - this->text_sel_start = this->text_sel_end = sp_te_replace(this->text, this->text_sel_start, this->text_sel_end, "\302\240"); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space")); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("Insert no-break space")); - return TRUE; - } - break; - case GDK_KEY_U: - case GDK_KEY_u: - if (MOD__CTRL_ONLY(event) || (MOD__CTRL(event) && MOD__SHIFT(event))) { - if (this->unimode) { - this->unimode = false; - this->defaultMessageContext()->clear(); - } else { - this->unimode = true; - this->unipos = 0; - this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): ")); - } - if (this->imc) { - gtk_im_context_reset(this->imc); - } - return TRUE; - } - break; - case GDK_KEY_B: - case GDK_KEY_b: - if (MOD__CTRL_ONLY(event) && this->text) { - SPStyle const *style = sp_te_style_at_position(this->text, std::min(this->text_sel_start, this->text_sel_end)); - SPCSSAttr *css = sp_repr_css_attr_new(); - if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL - || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100 - || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200 - || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300 - || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400) - sp_repr_css_set_property(css, "font-weight", "bold"); - else - sp_repr_css_set_property(css, "font-weight", "normal"); - sp_te_apply_style(this->text, this->text_sel_start, this->text_sel_end, css); - sp_repr_css_attr_unref(css); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("Make bold")); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - break; - case GDK_KEY_I: - case GDK_KEY_i: - if (MOD__CTRL_ONLY(event) && this->text) { - SPStyle const *style = sp_te_style_at_position(this->text, std::min(this->text_sel_start, this->text_sel_end)); - SPCSSAttr *css = sp_repr_css_attr_new(); - if (style->font_style.computed != SP_CSS_FONT_STYLE_NORMAL) - sp_repr_css_set_property(css, "font-style", "normal"); - else - sp_repr_css_set_property(css, "font-style", "italic"); - sp_te_apply_style(this->text, this->text_sel_start, this->text_sel_end, css); - sp_repr_css_attr_unref(css); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("Make italic")); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - break; - - case GDK_KEY_A: - case GDK_KEY_a: - if (MOD__CTRL_ONLY(event) && this->text) { - Inkscape::Text::Layout const *layout = te_get_layout(this->text); - if (layout) { - this->text_sel_start = layout->begin(); - this->text_sel_end = layout->end(); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - } - break; - - case GDK_KEY_Return: - case GDK_KEY_KP_Enter: - { - if (!this->text) { // printable key; create text if none (i.e. if nascent_object) - sp_text_context_setup_text(this); - this->nascent_object = 0; // we don't need it anymore, having created a real <text> - } - - iterator_pair enter_pair; - bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, enter_pair); - (void)success; // TODO cleanup - this->text_sel_start = this->text_sel_end = enter_pair.first; - - this->text_sel_start = this->text_sel_end = sp_te_insert_line(this->text, this->text_sel_start); - - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("New line")); - return TRUE; - } - case GDK_KEY_BackSpace: - if (this->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys - - bool noSelection = false; - - if (MOD__CTRL(event)) { - this->text_sel_start = this->text_sel_end; - } - - if (this->text_sel_start == this->text_sel_end) { - if (MOD__CTRL(event)) { - this->text_sel_start.prevStartOfWord(); - } else { - this->text_sel_start.prevCursorPosition(); - } - noSelection = true; - } - - iterator_pair bspace_pair; - bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, bspace_pair); - - if (noSelection) { - if (success) { - this->text_sel_start = this->text_sel_end = bspace_pair.first; - } else { // nothing deleted - this->text_sel_start = this->text_sel_end = bspace_pair.second; - } - } else { - if (success) { - this->text_sel_start = this->text_sel_end = bspace_pair.first; - } else { // nothing deleted - this->text_sel_start = bspace_pair.first; - this->text_sel_end = bspace_pair.second; - } - } - - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("Backspace")); - } - return TRUE; - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - if (this->text) { - bool noSelection = false; - - if (MOD__CTRL(event)) { - this->text_sel_start = this->text_sel_end; - } - - if (this->text_sel_start == this->text_sel_end) { - if (MOD__CTRL(event)) { - this->text_sel_end.nextEndOfWord(); - } else { - this->text_sel_end.nextCursorPosition(); - } - noSelection = true; - } - - iterator_pair del_pair; - bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, del_pair); - - if (noSelection) { - this->text_sel_start = this->text_sel_end = del_pair.first; - } else { - if (success) { - this->text_sel_start = this->text_sel_end = del_pair.first; - } else { // nothing deleted - this->text_sel_start = del_pair.first; - this->text_sel_end = del_pair.second; - } - } - - - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("Delete")); - } - return TRUE; - case GDK_KEY_Left: - case GDK_KEY_KP_Left: - case GDK_KEY_KP_4: - if (this->text) { - if (MOD__ALT(event)) { - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__SHIFT(event)) - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*-10, 0)); - else - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*-1, 0)); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT, - _("Kern to the left")); - } else { - if (MOD__CTRL(event)) - this->text_sel_end.cursorLeftWithControl(); - else - this->text_sel_end.cursorLeft(); - cursor_moved = true; - break; - } - } - return TRUE; - case GDK_KEY_Right: - case GDK_KEY_KP_Right: - case GDK_KEY_KP_6: - if (this->text) { - if (MOD__ALT(event)) { - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__SHIFT(event)) - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*10, 0)); - else - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*1, 0)); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT, - _("Kern to the right")); - } else { - if (MOD__CTRL(event)) - this->text_sel_end.cursorRightWithControl(); - else - this->text_sel_end.cursorRight(); - cursor_moved = true; - break; - } - } - return TRUE; - case GDK_KEY_Up: - case GDK_KEY_KP_Up: - case GDK_KEY_KP_8: - if (this->text) { - if (MOD__ALT(event)) { - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__SHIFT(event)) - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*-10)); - else - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*-1)); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT, - _("Kern up")); - } else { - if (MOD__CTRL(event)) - this->text_sel_end.cursorUpWithControl(); - else - this->text_sel_end.cursorUp(); - cursor_moved = true; - break; - } - } - return TRUE; - case GDK_KEY_Down: - case GDK_KEY_KP_Down: - case GDK_KEY_KP_2: - if (this->text) { - if (MOD__ALT(event)) { - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__SHIFT(event)) - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*10)); - else - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*1)); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT, - _("Kern down")); - } else { - if (MOD__CTRL(event)) - this->text_sel_end.cursorDownWithControl(); - else - this->text_sel_end.cursorDown(); - cursor_moved = true; - break; - } - } - return TRUE; - case GDK_KEY_Home: - case GDK_KEY_KP_Home: - if (this->text) { - if (MOD__CTRL(event)) - this->text_sel_end.thisStartOfShape(); - else - this->text_sel_end.thisStartOfLine(); - cursor_moved = true; - break; - } - return TRUE; - case GDK_KEY_End: - case GDK_KEY_KP_End: - if (this->text) { - if (MOD__CTRL(event)) - this->text_sel_end.nextStartOfShape(); - else - this->text_sel_end.thisEndOfLine(); - cursor_moved = true; - break; - } - return TRUE; - case GDK_KEY_Page_Down: - case GDK_KEY_KP_Page_Down: - if (this->text) { - this->text_sel_end.cursorDown(screenlines); - cursor_moved = true; - break; - } - return TRUE; - case GDK_KEY_Page_Up: - case GDK_KEY_KP_Page_Up: - if (this->text) { - this->text_sel_end.cursorUp(screenlines); - cursor_moved = true; - break; - } - return TRUE; - case GDK_KEY_Escape: - if (this->creating) { - this->creating = 0; - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); - this->grabbed = NULL; - } - Inkscape::Rubberband::get(desktop)->stop(); - } else { - sp_desktop_selection(desktop)->clear(); - } - this->nascent_object = FALSE; - return TRUE; - case GDK_KEY_bracketleft: - if (this->text) { - if (MOD__ALT(event) || MOD__CTRL(event)) { - if (MOD__ALT(event)) { - if (MOD__SHIFT(event)) { - // FIXME: alt+shift+[] does not work, don't know why - sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10); - } else { - sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1); - } - } else { - sp_te_adjust_rotation(this->text, this->text_sel_start, this->text_sel_end, desktop, -90); - } - DocumentUndo::maybeDone(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT, - _("Rotate counterclockwise")); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - } - break; - case GDK_KEY_bracketright: - if (this->text) { - if (MOD__ALT(event) || MOD__CTRL(event)) { - if (MOD__ALT(event)) { - if (MOD__SHIFT(event)) { - // FIXME: alt+shift+[] does not work, don't know why - sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10); - } else { - sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1); - } - } else { - sp_te_adjust_rotation(this->text, this->text_sel_start, this->text_sel_end, desktop, 90); - } - DocumentUndo::maybeDone(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT, - _("Rotate clockwise")); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - } - break; - case GDK_KEY_less: - case GDK_KEY_comma: - if (this->text) { - if (MOD__ALT(event)) { - if (MOD__CTRL(event)) { - if (MOD__SHIFT(event)) - sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10); - else - sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT, - _("Contract line spacing")); - } else { - if (MOD__SHIFT(event)) - sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10); - else - sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT, - _("Contract letter spacing")); - } - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - } - break; - case GDK_KEY_greater: - case GDK_KEY_period: - if (this->text) { - if (MOD__ALT(event)) { - if (MOD__CTRL(event)) { - if (MOD__SHIFT(event)) - sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10); - else - sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT, - _("Expand line spacing")); - } else { - if (MOD__SHIFT(event)) - sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10); - else - sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT, - _("Expand letter spacing"));\ - } - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - } - break; - default: - break; - } - - if (cursor_moved) { - if (!MOD__SHIFT(event)) - this->text_sel_start = this->text_sel_end; - if (old_start != this->text_sel_start || old_end != this->text_sel_end) { - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - } - return TRUE; - } - - } else return TRUE; // return the "I took care of it" value if it was consumed by the IM - } else { // do nothing if there's no object to type in - the key will be sent to parent context, - // except up/down that are swallowed to prevent the zoom field from activation - if ((group0_keyval == GDK_KEY_Up || - group0_keyval == GDK_KEY_Down || - group0_keyval == GDK_KEY_KP_Up || - group0_keyval == GDK_KEY_KP_Down ) - && !MOD__CTRL_ONLY(event)) { - return TRUE; - } else if (group0_keyval == GDK_KEY_Escape) { // cancel rubberband - if (this->creating) { - this->creating = 0; - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); - this->grabbed = NULL; - } - Inkscape::Rubberband::get(desktop)->stop(); - } - } else if ((group0_keyval == GDK_KEY_x || group0_keyval == GDK_KEY_X) && MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx-text"); - return TRUE; - } - } - break; - } - - case GDK_KEY_RELEASE: - if (!this->unimode && this->imc && gtk_im_context_filter_keypress(this->imc, (GdkEventKey*) event)) { - return TRUE; - } - break; - default: - break; - } - - // if nobody consumed it so far -// if ((SP_EVENT_CONTEXT_CLASS(sp_text_context_parent_class))->root_handler) { // and there's a handler in parent context, -// return (SP_EVENT_CONTEXT_CLASS(sp_text_context_parent_class))->root_handler(event_context, event); // send event to parent -// } else { -// return FALSE; // return "I did nothing" value so that global shortcuts can be activated -// } - return ToolBase::root_handler(event); - -} - -/** - Attempts to paste system clipboard into the currently edited text, returns true on success - */ -bool sp_text_paste_inline(ToolBase *ec) -{ - if (!SP_IS_TEXT_CONTEXT(ec)) - return false; - - TextTool *tc = SP_TEXT_CONTEXT(ec); - - if ((tc->text) || (tc->nascent_object)) { - // there is an active text object in this context, or a new object was just created - - Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get(); - Glib::ustring const clip_text = refClipboard->wait_for_text(); - - if (!clip_text.empty()) { - // Fix for 244940 - // The XML standard defines the following as valid characters - // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2) - // char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] - // Since what comes in off the paste buffer will go right into XML, clean - // the text here. - Glib::ustring text(clip_text); - Glib::ustring::iterator itr = text.begin(); - gunichar paste_string_uchar; - - while(itr != text.end()) - { - paste_string_uchar = *itr; - - // Make sure we don't have a control character. We should really check - // for the whole range above... Add the rest of the invalid cases from - // above if we find additional issues - if(paste_string_uchar >= 0x00000020 || - paste_string_uchar == 0x00000009 || - paste_string_uchar == 0x0000000A || - paste_string_uchar == 0x0000000D) { - itr++; - } else { - itr = text.erase(itr); - } - } - - if (!tc->text) { // create text if none (i.e. if nascent_object) - sp_text_context_setup_text(tc); - tc->nascent_object = 0; // we don't need it anymore, having created a real <text> - } - - // using indices is slow in ustrings. Whatever. - Glib::ustring::size_type begin = 0; - for ( ; ; ) { - Glib::ustring::size_type end = text.find('\n', begin); - if (end == Glib::ustring::npos) { - if (begin != text.length()) - tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, text.substr(begin).c_str()); - break; - } - tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, text.substr(begin, end - begin).c_str()); - tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start); - begin = end + 1; - } - DocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, - _("Paste text")); - - return true; - } - } // FIXME: else create and select a new object under cursor! - - return false; -} - -/** - Gets the raw characters that comprise the currently selected text, converting line - breaks into lf characters. -*/ -Glib::ustring sp_text_get_selected_text(ToolBase const *ec) -{ - if (!SP_IS_TEXT_CONTEXT(ec)) - return ""; - TextTool const *tc = SP_TEXT_CONTEXT(ec); - if (tc->text == NULL) - return ""; - - return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end); -} - -SPCSSAttr *sp_text_get_style_at_cursor(ToolBase const *ec) -{ - if (!SP_IS_TEXT_CONTEXT(ec)) - return NULL; - TextTool const *tc = SP_TEXT_CONTEXT(ec); - if (tc->text == NULL) - return NULL; - - SPObject const *obj = sp_te_object_at_position(tc->text, tc->text_sel_end); - - if (obj) { - return take_style_from_item(const_cast<SPObject*>(obj)); - } - - return NULL; -} - -/** - Deletes the currently selected characters. Returns false if there is no - text selection currently. -*/ -bool sp_text_delete_selection(ToolBase *ec) -{ - if (!SP_IS_TEXT_CONTEXT(ec)) - return false; - TextTool *tc = SP_TEXT_CONTEXT(ec); - if (tc->text == NULL) - return false; - - if (tc->text_sel_start == tc->text_sel_end) - return false; - - iterator_pair pair; - bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair); - - - if (success) { - tc->text_sel_start = tc->text_sel_end = pair.first; - } else { // nothing deleted - tc->text_sel_start = pair.first; - tc->text_sel_end = pair.second; - } - - sp_text_context_update_cursor(tc); - sp_text_context_update_text_selection(tc); - - return true; -} - -/** - * \param selection Should not be NULL. - */ -static void -sp_text_context_selection_changed(Inkscape::Selection *selection, TextTool *tc) -{ - g_assert(selection != NULL); - - ToolBase *ec = SP_EVENT_CONTEXT(tc); - - ec->shape_editor->unset_item(SH_KNOTHOLDER); - SPItem *item = selection->singleItem(); - if (item && SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) { - ec->shape_editor->set_item(item, SH_KNOTHOLDER); - } - - if (tc->text && (item != tc->text)) { - sp_text_context_forget_text(tc); - } - tc->text = NULL; - - if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { - tc->text = item; - Inkscape::Text::Layout const *layout = te_get_layout(tc->text); - if (layout) - tc->text_sel_start = tc->text_sel_end = layout->end(); - } else { - tc->text = NULL; - } - - // we update cursor without scrolling, because this position may not be final; - // item_handler moves cusros to the point of click immediately - sp_text_context_update_cursor(tc, false); - sp_text_context_update_text_selection(tc); -} - -static void -sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, TextTool *tc) -{ - sp_text_context_update_cursor(tc); - sp_text_context_update_text_selection(tc); -} - -static bool sp_text_context_style_set(SPCSSAttr const *css, TextTool *tc) -{ - if (tc->text == NULL) - return false; - if (tc->text_sel_start == tc->text_sel_end) - return false; // will get picked up by the parent and applied to the whole text object - - sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css); - DocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT, - _("Set text style")); - sp_text_context_update_cursor(tc); - sp_text_context_update_text_selection(tc); - - return true; -} - -static int -sp_text_context_style_query(SPStyle *style, int property, TextTool *tc) -{ - if (tc->text == NULL) { - return QUERY_STYLE_NOTHING; - } - const Inkscape::Text::Layout *layout = te_get_layout(tc->text); - if (layout == NULL) { - return QUERY_STYLE_NOTHING; - } - sp_text_context_validate_cursor_iterators(tc); - - GSList *styles_list = NULL; - - Inkscape::Text::Layout::iterator begin_it, end_it; - if (tc->text_sel_start < tc->text_sel_end) { - begin_it = tc->text_sel_start; - end_it = tc->text_sel_end; - } else { - begin_it = tc->text_sel_end; - end_it = tc->text_sel_start; - } - if (begin_it == end_it) { - if (!begin_it.prevCharacter()) { - end_it.nextCharacter(); - } - } - for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) { - SPObject const *pos_obj = 0; - void *rawptr = 0; - layout->getSourceOfCharacter(it, &rawptr); - if (!rawptr || !SP_IS_OBJECT(rawptr)) { - continue; - } - pos_obj = SP_OBJECT(rawptr); - while (SP_IS_STRING(pos_obj) && pos_obj->parent) { - pos_obj = pos_obj->parent; // SPStrings don't have style - } - styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj); - } - - int result = sp_desktop_query_style_from_list (styles_list, style, property); - - g_slist_free(styles_list); - return result; -} - -static void sp_text_context_validate_cursor_iterators(TextTool *tc) -{ - if (tc->text == NULL) - return; - Inkscape::Text::Layout const *layout = te_get_layout(tc->text); - if (layout) { // undo can change the text length without us knowing it - layout->validateIterator(&tc->text_sel_start); - layout->validateIterator(&tc->text_sel_end); - } -} - -static void sp_text_context_update_cursor(TextTool *tc, bool scroll_to_see) -{ - // due to interruptible display, tc may already be destroyed during a display update before - // the cursor update (can't do both atomically, alas) - if (!tc->desktop) return; - - if (tc->text) { - Geom::Point p0, p1; - sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1); - Geom::Point const d0 = p0 * tc->text->i2dt_affine(); - Geom::Point const d1 = p1 * tc->text->i2dt_affine(); - - // scroll to show cursor - if (scroll_to_see) { - Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint(); - if (Geom::L2(d0 - center) > Geom::L2(d1 - center)) - // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed - SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0); - else - SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0); - } - - sp_canvas_item_show(tc->cursor); - tc->cursor->setCoords(d0, d1); - - /* fixme: ... need another transformation to get canvas widget coordinate space? */ - if (tc->imc) { - GdkRectangle im_cursor = { 0, 0, 1, 1 }; - Geom::Point const top_left = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().corner(3); - Geom::Point const im_d0 = SP_EVENT_CONTEXT(tc)->desktop->d2w(d0 - top_left); - Geom::Point const im_d1 = SP_EVENT_CONTEXT(tc)->desktop->d2w(d1 - top_left); - im_cursor.x = (int) floor(im_d0[Geom::X]); - im_cursor.y = (int) floor(im_d1[Geom::Y]); - im_cursor.width = (int) floor(im_d1[Geom::X]) - im_cursor.x; - im_cursor.height = (int) floor(im_d0[Geom::Y]) - im_cursor.y; - gtk_im_context_set_cursor_location(tc->imc, &im_cursor); - } - - tc->show = TRUE; - tc->phase = 1; - - Inkscape::Text::Layout const *layout = te_get_layout(tc->text); - int const nChars = layout->iteratorToCharIndex(layout->end()); - char const *trunc = ""; - bool truncated = false; - if (layout->inputTruncated()) { - truncated = true; - trunc = _(" [truncated]"); - } - if (SP_IS_FLOWTEXT(tc->text)) { - SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only - if (frame) { - if (truncated) { - SP_CTRLRECT(tc->frame)->setColor(0xff0000ff, false, 0); - } else { - SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0); - } - sp_canvas_item_show(tc->frame); - Geom::OptRect frame_bbox = frame->desktopVisualBounds(); - if (frame_bbox) { - SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox); - } - } - - SP_EVENT_CONTEXT(tc)->message_context->setF(Inkscape::NORMAL_MESSAGE, ngettext("Type or edit flowed text (%d character%s); <b>Enter</b> to start new paragraph.", "Type or edit flowed text (%d characters%s); <b>Enter</b> to start new paragraph.", nChars), nChars, trunc); - } else { - SP_EVENT_CONTEXT(tc)->message_context->setF(Inkscape::NORMAL_MESSAGE, ngettext("Type or edit text (%d character%s); <b>Enter</b> to start new line.", "Type or edit text (%d characters%s); <b>Enter</b> to start new line.", nChars), nChars, trunc); - } - - } else { - sp_canvas_item_hide(tc->cursor); - sp_canvas_item_hide(tc->frame); - tc->show = FALSE; - if (!tc->nascent_object) { - SP_EVENT_CONTEXT(tc)->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to select or create text, <b>drag</b> to create flowed text; then type.")); // FIXME: this is a copy of string from tools-switch, do not desync - } - } - - SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc); -} - -static void sp_text_context_update_text_selection(TextTool *tc) -{ - // due to interruptible display, tc may already be destroyed during a display update before - // the selection update (can't do both atomically, alas) - if (!tc->desktop) return; - - for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) { - sp_canvas_item_hide(*it); - sp_canvas_item_destroy(*it); - } - tc->text_selection_quads.clear(); - - std::vector<Geom::Point> quads; - if (tc->text != NULL) - quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, (tc->text)->i2dt_affine()); - for (unsigned i = 0 ; i < quads.size() ; i += 4) { - SPCanvasItem *quad_canvasitem; - quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL); - // FIXME: make the color settable in prefs - // for now, use semitrasparent blue, as cairo cannot do inversion :( - sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777); - sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]); - sp_canvas_item_show(quad_canvasitem); - tc->text_selection_quads.push_back(quad_canvasitem); - } -} - -static gint sp_text_context_timeout(TextTool *tc) -{ - if (tc->show) { - sp_canvas_item_show(tc->cursor); - if (tc->phase) { - tc->phase = 0; - tc->cursor->setRgba32(0x000000ff); - } else { - tc->phase = 1; - tc->cursor->setRgba32(0xffffffff); - } - } - - return TRUE; -} - -static void sp_text_context_forget_text(TextTool *tc) -{ - if (! tc->text) return; - SPItem *ti = tc->text; - (void)ti; - /* We have to set it to zero, - * or selection changed signal messes everything up */ - tc->text = NULL; - -/* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext. - So don't create an empty flowtext in the first place? Create it when first character is typed. - */ -/* - if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) { - Inkscape::XML::Node *text_repr = ti->getRepr(); - // the repr may already have been unparented - // if we were called e.g. as the result of - // an undo or the element being removed from - // the XML editor - if ( text_repr && text_repr->parent() ) { - sp_repr_unparent(text_repr); - SPDocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT, - _("Remove empty text")); - } - } -*/ -} - -gint sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, TextTool *tc) -{ - gtk_im_context_focus_in(tc->imc); - return FALSE; -} - -gint sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, TextTool *tc) -{ - gtk_im_context_focus_out(tc->imc); - return FALSE; -} - -static void sptc_commit(GtkIMContext */*imc*/, gchar *string, TextTool *tc) -{ - if (!tc->text) { - sp_text_context_setup_text(tc); - tc->nascent_object = 0; // we don't need it anymore, having created a real <text> - } - - tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string); - sp_text_context_update_cursor(tc); - sp_text_context_update_text_selection(tc); - - DocumentUndo::done(tc->text->document, SP_VERB_CONTEXT_TEXT, - _("Type text")); -} - -void sp_text_context_place_cursor (TextTool *tc, SPObject *text, Inkscape::Text::Layout::iterator where) -{ - SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text); - tc->text_sel_start = tc->text_sel_end = where; - sp_text_context_update_cursor(tc); - sp_text_context_update_text_selection(tc); -} - -void sp_text_context_place_cursor_at (TextTool *tc, SPObject *text, Geom::Point const p) -{ - SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text); - sp_text_context_place_cursor (tc, text, sp_te_get_position_by_coords(tc->text, p)); -} - -Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(TextTool *tc, SPObject *text) -{ - if (text != tc->text) - return NULL; - return &(tc->text_sel_end); -} - -} -} -} - -/* - 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 : |
