Code

Merge and cleanup of GSoC C++-ification project.
[inkscape.git] / src / text-context.cpp
index 945813f1b4574859c559bde8da75ecdbfced50d6..66e5f9450a97f9335e54be7918a35805c0819874 100644 (file)
@@ -1,11 +1,11 @@
-#define __SP_TEXT_CONTEXT_C__
-
 /*
  * SPTextContext
  *
  * 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.
 #include "desktop.h"
 #include "desktop-style.h"
 #include "desktop-handles.h"
-#include "desktop-affine.h"
 #include "message-stack.h"
 #include "message-context.h"
 #include "pixmaps/cursor-text.xpm"
-#include "pixmaps/cursor-text.pixbuf"
 #include "pixmaps/cursor-text-insert.xpm"
-#include "pixmaps/cursor-text-insert.pixbuf"
 #include <glibmm/i18n.h>
 #include "object-edit.h"
 #include "xml/repr.h"
 #include "xml/node-event-vector.h"
-#include "prefs-utils.h"
+#include "preferences.h"
 #include "rubberband.h"
 #include "sp-metrics.h"
 #include "context-fns.h"
-
+#include "verbs.h"
+#include "shape-editor.h"
+#include "selection-chemistry.h"
 #include "text-editing.h"
 
 #include "text-context.h"
 
+using Inkscape::DocumentUndo;
 
 static void sp_text_context_class_init(SPTextContextClass *klass);
 static void sp_text_context_init(SPTextContext *text_context);
@@ -124,11 +124,6 @@ sp_text_context_init(SPTextContext *tc)
     SPEventContext *event_context = SP_EVENT_CONTEXT(tc);
 
     event_context->cursor_shape = cursor_text_xpm;
-    event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
-            -1,
-            cursor_text_pixbuf,
-            FALSE,
-            NULL);  
     event_context->hot_x = 7;
     event_context->hot_y = 7;
 
@@ -137,13 +132,10 @@ sp_text_context_init(SPTextContext *tc)
     event_context->tolerance = 0;
     event_context->within_tolerance = false;
 
-    event_context->shape_repr = NULL;
-    event_context->shape_knot_holder = NULL;
-
     tc->imc = NULL;
 
     tc->text = NULL;
-    tc->pdoc = NR::Point(0, 0);
+    tc->pdoc = Geom::Point(0, 0);
     new (&tc->text_sel_start) Inkscape::Text::Layout::iterator();
     new (&tc->text_sel_end) Inkscape::Text::Layout::iterator();
     new (&tc->text_selection_quads) std::vector<SPCanvasItem*>();
@@ -177,6 +169,10 @@ sp_text_context_dispose(GObject *obj)
     tc->style_set_connection.~connection();
     tc->sel_changed_connection.~connection();
     tc->sel_modified_connection.~connection();
+
+    delete ec->shape_editor;
+    ec->shape_editor = NULL;
+
     tc->text_sel_end.~iterator();
     tc->text_sel_start.~iterator();
     tc->text_selection_quads.~vector();
@@ -188,32 +184,22 @@ sp_text_context_dispose(GObject *obj)
         tc->grabbed = NULL;
     }
 
-    Inkscape::Rubberband::get()->stop();
-
-    if (ec->shape_knot_holder) {
-        sp_knot_holder_destroy(ec->shape_knot_holder);
-        ec->shape_knot_holder = NULL;
-    }
-    if (ec->shape_repr) { // remove old listener
-        sp_repr_remove_listener_by_data(ec->shape_repr, ec);
-        Inkscape::GC::release(ec->shape_repr);
-        ec->shape_repr = 0;
-    }
+    Inkscape::Rubberband::get(ec->desktop)->stop();
 }
 
-static Inkscape::XML::NodeEventVector ec_shape_repr_events = {
-    NULL, /* child_added */
-    NULL, /* child_removed */
-    ec_shape_event_attr_changed,
-    NULL, /* content_changed */
-    NULL  /* order_changed */
-};
-
 static void
 sp_text_context_setup(SPEventContext *ec)
 {
     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
     SPDesktop *desktop = ec->desktop;
+    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;
+    }
 
     tc->cursor = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLLINE, NULL);
     sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), 100, 0, 100, 100);
@@ -221,16 +207,16 @@ sp_text_context_setup(SPEventContext *ec)
     sp_canvas_item_hide(tc->cursor);
 
     tc->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
-    SP_CTRLRECT(tc->indicator)->setRectangle(NR::Rect(NR::Point(0, 0), NR::Point(100, 100)));
+    SP_CTRLRECT(tc->indicator)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
     SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
     sp_canvas_item_hide(tc->indicator);
 
     tc->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
-    SP_CTRLRECT(tc->frame)->setRectangle(NR::Rect(NR::Point(0, 0), NR::Point(100, 100)));
+    SP_CTRLRECT(tc->frame)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
     SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
     sp_canvas_item_hide(tc->frame);
 
-    tc->timeout = gtk_timeout_add(250, (GtkFunction) sp_text_context_timeout, ec);
+    tc->timeout = gtk_timeout_add(timeout, (GtkFunction) sp_text_context_timeout, ec);
 
     tc->imc = gtk_im_multicontext_new();
     if (tc->imc) {
@@ -257,15 +243,11 @@ sp_text_context_setup(SPEventContext *ec)
     if (((SPEventContextClass *) parent_class)->setup)
         ((SPEventContextClass *) parent_class)->setup(ec);
 
+    ec->shape_editor = new ShapeEditor(ec->desktop);
+
     SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
     if (item && SP_IS_FLOWTEXT (item) && SP_FLOWTEXT(item)->has_internal_frame()) {
-        ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
-        Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
-        if (shape_repr) {
-            ec->shape_repr = shape_repr;
-            Inkscape::GC::anchor(shape_repr);
-            sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
-        }
+        ec->shape_editor->set_item(item, SH_KNOTHOLDER);
     }
 
     tc->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged(
@@ -283,10 +265,11 @@ sp_text_context_setup(SPEventContext *ec)
 
     sp_text_context_selection_changed(sp_desktop_selection(desktop), tc);
 
-    if (prefs_get_int_attribute("tools.text", "selcue", 0) != 0) {
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    if (prefs->getBool("/tools/text/selcue")) {
         ec->enableSelectionCue();
     }
-    if (prefs_get_int_attribute("tools.text", "gradientdrag", 0) != 0) {
+    if (prefs->getBool("/tools/text/gradientdrag")) {
         ec->enableGrDrag();
     }
 }
@@ -296,6 +279,10 @@ sp_text_context_finish(SPEventContext *ec)
 {
     SPTextContext *tc = SP_TEXT_CONTEXT(ec);
 
+    if (ec->desktop) {
+        sp_signal_disconnect_by_data(sp_desktop_canvas(ec->desktop), tc);
+    }
+
     ec->enableGrDrag(false);
 
     tc->style_set_connection.disconnect();
@@ -336,18 +323,14 @@ sp_text_context_finish(SPEventContext *ec)
         gtk_object_destroy(*it);
     }
     tc->text_selection_quads.clear();
-
-    if (ec->desktop) {
-        sp_signal_disconnect_by_data(sp_desktop_canvas(ec->desktop), tc);
-    }
 }
 
 
 static gint
-sp_text_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
+sp_text_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
 {
-    SPTextContext *tc = SP_TEXT_CONTEXT(ec);
-    SPDesktop *desktop = ec->desktop;
+    SPTextContext *tc = SP_TEXT_CONTEXT(event_context);
+    SPDesktop *desktop = event_context->desktop;
     SPItem *item_ungrouped;
 
     gint ret = FALSE;
@@ -356,14 +339,14 @@ sp_text_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
 
     switch (event->type) {
         case GDK_BUTTON_PRESS:
-            if (event->button.button == 1) {
+            if (event->button.button == 1 && !event_context->space_panning) {
                 // find out clicked item, disregarding groups
-                item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
+                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(ec->desktop)->set(item_ungrouped);
+                    sp_desktop_selection(desktop)->set(item_ungrouped);
                     if (tc->text) {
                         // find out click point in document coordinates
-                        NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
+                        Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
                         // set the cursor closest to that point
                         tc->text_sel_start = tc->text_sel_end = sp_te_get_position_by_coords(tc->text, p);
                         // update display
@@ -401,17 +384,18 @@ sp_text_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
             }
             break;
         case GDK_BUTTON_RELEASE:
-            if (event->button.button == 1 && tc->dragging) {
+            if (event->button.button == 1 && tc->dragging && !event_context->space_panning) {
                 tc->dragging = 0;
+                sp_event_context_discard_delayed_snap_event(event_context);
                 ret = TRUE;
             }
             break;
         case GDK_MOTION_NOTIFY:
-            if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging) {
+            if (event->motion.state & GDK_BUTTON1_MASK && tc->dragging && !event_context->space_panning) {
                 Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
                 if (!layout) break;
                 // find out click point in document coordinates
-                NR::Point p = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
+                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(tc->text, p);
                 if (tc->dragging == 2) {
@@ -435,24 +419,30 @@ sp_text_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
                     sp_text_context_update_cursor(tc);
                     sp_text_context_update_text_selection(tc);
                 }
+                gobble_motion_events(GDK_BUTTON1_MASK);
                 ret = TRUE;
                 break;
             }
             // find out item under mouse, disregarding groups
-            item_ungrouped = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
+            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(tc->indicator)->setColor(0xff0000ff, false, 0);
+                } else {
+                    SP_CTRLRECT(tc->indicator)->setColor(0x0000ff7f, false, 0);
+                }
+                Geom::OptRect ibbox = item_ungrouped->getBboxDesktop();
+                if (ibbox) {
+                    SP_CTRLRECT(tc->indicator)->setRectangle(*ibbox);
+                }
                 sp_canvas_item_show(tc->indicator);
-                SP_CTRLRECT(tc->indicator)->setRectangle(sp_item_bbox_desktop(item_ungrouped));
-
-                ec->cursor_shape = cursor_text_insert_xpm;
-                ec->cursor_pixbuf = gdk_pixbuf_new_from_inline(
-                        -1,
-                        cursor_text_insert_pixbuf,
-                        FALSE,
-                        NULL);  
-                ec->hot_x = 7;
-                ec->hot_y = 10;
-                sp_event_context_update_cursor(ec);
+
+                event_context->cursor_shape = cursor_text_insert_xpm;
+                event_context->hot_x = 7;
+                event_context->hot_y = 10;
+                sp_event_context_update_cursor(event_context);
                 sp_text_context_update_text_selection(tc);
 
                 if (SP_IS_TEXT (item_ungrouped)) {
@@ -472,7 +462,7 @@ sp_text_context_item_handler(SPEventContext *ec, SPItem *item, GdkEvent *event)
 
     if (!ret) {
         if (((SPEventContextClass *) parent_class)->item_handler)
-            ret = ((SPEventContextClass *) parent_class)->item_handler(ec, item, event);
+            ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
     }
 
     return ret;
@@ -484,23 +474,24 @@ sp_text_context_setup_text(SPTextContext *tc)
     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
 
     /* Create <text> */
-    Inkscape::XML::Node *rtext = sp_repr_new("svg: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_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "/tools/text", true);
 
-    sp_repr_set_svg_double(rtext, "x", tc->pdoc[NR::X]);
-    sp_repr_set_svg_double(rtext, "y", tc->pdoc[NR::Y]);
+    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 = sp_repr_new("svg: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 = sp_repr_new_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));
@@ -508,9 +499,11 @@ sp_text_context_setup_text(SPTextContext *tc)
     /* 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->currentRoot())->getRelativeTransform(ec->desktop->currentLayer());
+    text_item->transform = SP_ITEM(ec->desktop->currentLayer())->i2doc_affine().inverse();
+
     text_item->updateRepr();
-    sp_document_done(sp_desktop_document(ec->desktop));
+    DocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
+                      _("Create text"));
 }
 
 /**
@@ -548,7 +541,8 @@ insert_uni_char(SPTextContext *const tc)
         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);
-        sp_document_done(sp_desktop_document(tc->desktop));
+        DocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM,
+                          _("Insert Unicode character"));
     }
 }
 
@@ -583,46 +577,51 @@ show_curr_uni_char(SPTextContext *const tc)
             }
         }
         tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
-                                          _("Unicode: %s: %s"), tc->uni, utf8);
+                                          _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
     } else {
-        tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
+        tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
     }
 }
 
 static gint
-sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
+sp_text_context_root_handler(SPEventContext *const event_context, GdkEvent *const event)
 {
-    SPTextContext *const tc = SP_TEXT_CONTEXT(ec);
+    SPTextContext *const tc = SP_TEXT_CONTEXT(event_context);
 
-    SPDesktop *desktop = ec->desktop;
+    SPDesktop *desktop = event_context->desktop;
 
     sp_canvas_item_hide(tc->indicator);
 
     sp_text_context_validate_cursor_iterators(tc);
 
-    ec->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
 
     switch (event->type) {
         case GDK_BUTTON_PRESS:
-            if (event->button.button == 1) {
-
-                SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec);
+            if (event->button.button == 1 && !event_context->space_panning) {
 
                 if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
                     return TRUE;
                 }
 
                 // save drag origin
-                ec->xp = (gint) event->button.x;
-                ec->yp = (gint) event->button.y;
-                ec->within_tolerance = true;
+                event_context->xp = (gint) event->button.x;
+                event_context->yp = (gint) event->button.y;
+                event_context->within_tolerance = true;
+
+                Geom::Point const button_pt(event->button.x, event->button.y);
+                Geom::Point button_dt(desktop->w2d(button_pt));
 
-                NR::Point const button_pt(event->button.x, event->button.y);
-                tc->p0 = desktop->w2d(button_pt);
-                Inkscape::Rubberband::get()->start(desktop, tc->p0);
+                SnapManager &m = desktop->namedview->snap_manager;
+                m.setup(desktop);
+                m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
+                m.unSetup();
+
+                tc->p0 = button_dt;
+                Inkscape::Rubberband::get(desktop)->start(desktop, tc->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_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
                                     NULL, event->button.time);
                 tc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
                 tc->creating = 1;
@@ -635,60 +634,74 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
             if (tc->over_text) {
                 tc->over_text = 0;
                 // update cursor and statusbar: we are not over a text object now
-                ec->cursor_shape = cursor_text_xpm;
-                ec->cursor_pixbuf = gdk_pixbuf_new_from_inline(
-                        -1,
-                        cursor_text_pixbuf,
-                        FALSE,
-                        NULL);  
-                ec->hot_x = 7;
-                ec->hot_y = 7;
-                sp_event_context_update_cursor(ec);
+                event_context->cursor_shape = cursor_text_xpm;
+                event_context->hot_x = 7;
+                event_context->hot_y = 7;
+                sp_event_context_update_cursor(event_context);
                 desktop->event_context->defaultMessageContext()->clear();
             }
 
-            if (tc->creating && event->motion.state & GDK_BUTTON1_MASK) {
-                if ( ec->within_tolerance
-                     && ( abs( (gint) event->motion.x - ec->xp ) < ec->tolerance )
-                     && ( abs( (gint) event->motion.y - ec->yp ) < ec->tolerance ) ) {
+            if (tc->creating && event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
+                if ( event_context->within_tolerance
+                     && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
+                     && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->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)
-                ec->within_tolerance = false;
+                event_context->within_tolerance = false;
+
+                Geom::Point const motion_pt(event->motion.x, event->motion.y);
+                Geom::Point p = desktop->w2d(motion_pt);
 
-                NR::Point const motion_pt(event->motion.x, event->motion.y);
-                NR::Point const 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()->move(p);
+                Inkscape::Rubberband::get(desktop)->move(p);
                 gobble_motion_events(GDK_BUTTON1_MASK);
 
                 // status text
-                GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::X]), desktop->namedview->getDefaultMetric());
-                GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[NR::Y]), desktop->namedview->getDefaultMetric());
-                ec->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
+                GString *xs = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::X]), desktop->namedview->getDefaultMetric());
+                GString *ys = SP_PX_TO_METRIC_STRING(fabs((p - tc->p0)[Geom::Y]), desktop->namedview->getDefaultMetric());
+                event_context->_message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
                 g_string_free(xs, FALSE);
                 g_string_free(ys, FALSE);
 
+            } else if (!sp_event_context_knot_mouseover(event_context)) {
+                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_NODE_HANDLE));
+                m.unSetup();
             }
             break;
         case GDK_BUTTON_RELEASE:
-            if (event->button.button == 1) {
+            if (event->button.button == 1 && !event_context->space_panning) {
+                sp_event_context_discard_delayed_snap_event(event_context);
+
+                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 (tc->grabbed) {
                     sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
                     tc->grabbed = NULL;
                 }
 
-                Inkscape::Rubberband::get()->stop();
+                Inkscape::Rubberband::get(desktop)->stop();
 
-                if (tc->creating && ec->within_tolerance) {
+                if (tc->creating && event_context->within_tolerance) {
                     /* Button 1, set X & Y & new item */
                     sp_desktop_selection(desktop)->clear();
-                    NR::Point dtp = ec->desktop->w2d(NR::Point(event->button.x, event->button.y));
-                    tc->pdoc = sp_desktop_dt2root_xy_point(ec->desktop, dtp);
-
+                    tc->pdoc = desktop->dt2doc(p1);
                     tc->show = TRUE;
                     tc->phase = 1;
                     tc->nascent_object = 1; // new object was just created
@@ -696,24 +709,25 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                     /* Cursor */
                     sp_canvas_item_show(tc->cursor);
                     // Cursor height is defined by the new text object's font size; it needs to be set
-                    // articifically here, for the text object does not exist yet:
-                    double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
-                    sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), dtp, dtp + NR::Point(0, cursor_height));
-                    ec->_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
+                    // artificially here, for the text object does not exist yet:
+                    double cursor_height = sp_desktop_get_font_size_tool(desktop);
+                    sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), p1, p1 + Geom::Point(0, cursor_height));
+                    event_context->_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
 
-                    ec->within_tolerance = false;
+                    event_context->within_tolerance = false;
                 } else if (tc->creating) {
-                    NR::Point const button_pt(event->button.x, event->button.y);
-                    NR::Point p1 = desktop->w2d(button_pt);
-                    double cursor_height = sp_desktop_get_font_size_tool(ec->desktop);
-                    if (fabs(p1[NR::Y] - tc->p0[NR::Y]) > cursor_height) {
+                    double cursor_height = sp_desktop_get_font_size_tool(desktop);
+                    if (fabs(p1[Geom::Y] - tc->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, tc->p0, p1);
+                        /* Set style */
+                        sp_desktop_apply_style_tool(desktop, SP_OBJECT_REPR(ft), "/tools/text", true);
                         sp_desktop_selection(desktop)->set(ft);
-                        ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
-                        sp_document_done(sp_desktop_document(desktop));
+                        desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
+                        DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
+                                          _("Create flowed text"));
                     } else {
-                        ec->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
+                        desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
                     }
                 }
                 tc->creating = false;
@@ -773,7 +787,7 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                                     }
                                     /* Exit unimode. */
                                     tc->unimode = false;
-                                    ec->defaultMessageContext()->clear();
+                                    event_context->defaultMessageContext()->clear();
                                     return TRUE;
                                 }
 
@@ -781,7 +795,7 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                                     // Cancel unimode.
                                     tc->unimode = false;
                                     gtk_im_context_reset(tc->imc);
-                                    ec->defaultMessageContext()->clear();
+                                    event_context->defaultMessageContext()->clear();
                                     return TRUE;
                                 }
 
@@ -818,7 +832,17 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                             }
                         }
 
-                        bool (Inkscape::Text::Layout::iterator::*cursor_movement_operator)() = NULL;
+                        Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
+                        Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
+                        bool cursor_moved = false;
+                        int screenlines = 1;
+                        if (tc->text) {
+                            double spacing = sp_te_get_average_linespacing(tc->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) {
@@ -839,21 +863,22 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                                     tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, "\302\240");
                                     sp_text_context_update_cursor(tc);
                                     sp_text_context_update_text_selection(tc);
-                                    ec->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
-                                    sp_document_done(sp_desktop_document(ec->desktop));
+                                    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_U:
                             case GDK_u:
-                                if (MOD__CTRL_ONLY) {
+                                if (MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT)) {
                                     if (tc->unimode) {
                                         tc->unimode = false;
-                                        ec->defaultMessageContext()->clear();
+                                        event_context->defaultMessageContext()->clear();
                                     } else {
                                         tc->unimode = true;
                                         tc->unipos = 0;
-                                        ec->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode: "));
+                                        event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
                                     }
                                     if (tc->imc) {
                                         gtk_im_context_reset(tc->imc);
@@ -876,7 +901,8 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                                         sp_repr_css_set_property(css, "font-weight", "normal");
                                     sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
                                     sp_repr_css_attr_unref(css);
-                                    sp_document_done(sp_desktop_document(ec->desktop));
+                                    DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
+                                                      _("Make bold"));
                                     sp_text_context_update_cursor(tc);
                                     sp_text_context_update_text_selection(tc);
                                     return TRUE;
@@ -887,13 +913,14 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                                 if (MOD__CTRL_ONLY && tc->text) {
                                     SPStyle const *style = sp_te_style_at_position(tc->text, std::min(tc->text_sel_start, tc->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", "italic");
-                                    else
+                                    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(tc->text, tc->text_sel_start, tc->text_sel_end, css);
                                     sp_repr_css_attr_unref(css);
-                                    sp_document_done(sp_desktop_document(ec->desktop));
+                                    DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
+                                                      _("Make italic"));
                                     sp_text_context_update_cursor(tc);
                                     sp_text_context_update_text_selection(tc);
                                     return TRUE;
@@ -916,35 +943,88 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
 
                             case GDK_Return:
                             case GDK_KP_Enter:
+                            {
                                 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>
                                 }
-                                tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
+
+                                iterator_pair enter_pair;
+                                bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, enter_pair);
+                                (void)success; // TODO cleanup
+                                tc->text_sel_start = tc->text_sel_end = enter_pair.first;
+
                                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
+
                                 sp_text_context_update_cursor(tc);
                                 sp_text_context_update_text_selection(tc);
-                                sp_document_done(sp_desktop_document(ec->desktop));
+                                DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
+                                                  _("New line"));
                                 return TRUE;
+                            }
                             case GDK_BackSpace:
                                 if (tc->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
-                                    if (tc->text_sel_start == tc->text_sel_end)
+
+                                    bool noSelection = false;
+
+                                       if (tc->text_sel_start == tc->text_sel_end) {
                                         tc->text_sel_start.prevCursorPosition();
-                                    tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
+                                        noSelection = true;
+                                    }
+
+                                       iterator_pair bspace_pair;
+                                       bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, bspace_pair);
+
+                                    if (noSelection) {
+                                        if (success) {
+                                            tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
+                                        } else { // nothing deleted
+                                            tc->text_sel_start = tc->text_sel_end = bspace_pair.second;
+                                        }
+                                    } else {
+                                        if (success) {
+                                            tc->text_sel_start = tc->text_sel_end = bspace_pair.first;
+                                        } else { // nothing deleted
+                                            tc->text_sel_start = bspace_pair.first;
+                                            tc->text_sel_end = bspace_pair.second;
+                                        }
+                                    }
+
                                     sp_text_context_update_cursor(tc);
                                     sp_text_context_update_text_selection(tc);
-                                    sp_document_done(sp_desktop_document(ec->desktop));
+                                    DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
+                                                      _("Backspace"));
                                 }
                                 return TRUE;
                             case GDK_Delete:
                             case GDK_KP_Delete:
                                 if (tc->text) {
-                                    if (tc->text_sel_start == tc->text_sel_end)
+                                    bool noSelection = false;
+
+                                    if (tc->text_sel_start == tc->text_sel_end) {
                                         tc->text_sel_end.nextCursorPosition();
-                                    tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
+                                        noSelection = true;
+                                    }
+
+                                    iterator_pair del_pair;
+                                    bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, del_pair);
+
+                                    if (noSelection) {
+                                        tc->text_sel_start = tc->text_sel_end = del_pair.first;
+                                    } else {
+                                        if (success) {
+                                            tc->text_sel_start = tc->text_sel_end = del_pair.first;
+                                        } else { // nothing deleted
+                                            tc->text_sel_start = del_pair.first;
+                                            tc->text_sel_end = del_pair.second;
+                                        }
+                                    }
+
+
                                     sp_text_context_update_cursor(tc);
                                     sp_text_context_update_text_selection(tc);
-                                    sp_document_done(sp_desktop_document(ec->desktop));
+                                    DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
+                                                      _("Delete"));
                                 }
                                 return TRUE;
                             case GDK_Left:
@@ -952,16 +1032,22 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                             case GDK_KP_4:
                                 if (tc->text) {
                                     if (MOD__ALT) {
+                                        gint mul = 1 + gobble_key_events(
+                                            get_group0_keyval(&event->key), 0); // with any mask
                                         if (MOD__SHIFT)
-                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-10, 0));
+                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-10, 0));
                                         else
-                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(-1, 0));
+                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*-1, 0));
                                         sp_text_context_update_cursor(tc);
                                         sp_text_context_update_text_selection(tc);
-                                        sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:left");
+                                        DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
+                                                               _("Kern to the left"));
                                     } else {
-                                        cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorLeftWithControl
-                                                                             : &Inkscape::Text::Layout::iterator::cursorLeft;
+                                        if (MOD__CTRL)
+                                            tc->text_sel_end.cursorLeftWithControl();
+                                        else
+                                            tc->text_sel_end.cursorLeft();
+                                        cursor_moved = true;
                                         break;
                                     }
                                 }
@@ -971,16 +1057,22 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                             case GDK_KP_6:
                                 if (tc->text) {
                                     if (MOD__ALT) {
+                                        gint mul = 1 + gobble_key_events(
+                                            get_group0_keyval(&event->key), 0); // with any mask
                                         if (MOD__SHIFT)
-                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(10, 0));
+                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*10, 0));
                                         else
-                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(1, 0));
+                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(mul*1, 0));
                                         sp_text_context_update_cursor(tc);
                                         sp_text_context_update_text_selection(tc);
-                                        sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:right");
+                                        DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
+                                                               _("Kern to the right"));
                                     } else {
-                                        cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorRightWithControl
-                                                                             : &Inkscape::Text::Layout::iterator::cursorRight;
+                                        if (MOD__CTRL)
+                                            tc->text_sel_end.cursorRightWithControl();
+                                        else
+                                            tc->text_sel_end.cursorRight();
+                                        cursor_moved = true;
                                         break;
                                     }
                                 }
@@ -990,16 +1082,22 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                             case GDK_KP_8:
                                 if (tc->text) {
                                     if (MOD__ALT) {
+                                        gint mul = 1 + gobble_key_events(
+                                            get_group0_keyval(&event->key), 0); // with any mask
                                         if (MOD__SHIFT)
-                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -10));
+                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-10));
                                         else
-                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, -1));
+                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*-1));
                                         sp_text_context_update_cursor(tc);
                                         sp_text_context_update_text_selection(tc);
-                                        sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:up");
+                                        DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
+                                                               _("Kern up"));
                                     } else {
-                                        cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorUpWithControl
-                                                                             : &Inkscape::Text::Layout::iterator::cursorUp;
+                                        if (MOD__CTRL)
+                                            tc->text_sel_end.cursorUpWithControl();
+                                        else
+                                            tc->text_sel_end.cursorUp();
+                                        cursor_moved = true;
                                         break;
                                     }
                                 }
@@ -1009,16 +1107,22 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                             case GDK_KP_2:
                                 if (tc->text) {
                                     if (MOD__ALT) {
+                                        gint mul = 1 + gobble_key_events(
+                                            get_group0_keyval(&event->key), 0); // with any mask
                                         if (MOD__SHIFT)
-                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 10));
+                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*10));
                                         else
-                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, NR::Point(0, 1));
+                                            sp_te_adjust_kerning_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, Geom::Point(0, mul*1));
                                         sp_text_context_update_cursor(tc);
                                         sp_text_context_update_text_selection(tc);
-                                        sp_document_maybe_done(sp_desktop_document(ec->desktop), "kern:down");
+                                        DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
+                                                               _("Kern down"));
                                     } else {
-                                        cursor_movement_operator = MOD__CTRL ? &Inkscape::Text::Layout::iterator::cursorDownWithControl
-                                                                             : &Inkscape::Text::Layout::iterator::cursorDown;
+                                        if (MOD__CTRL)
+                                            tc->text_sel_end.cursorDownWithControl();
+                                        else
+                                            tc->text_sel_end.cursorDown();
+                                        cursor_moved = true;
                                         break;
                                     }
                                 }
@@ -1027,9 +1131,10 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                             case GDK_KP_Home:
                                 if (tc->text) {
                                     if (MOD__CTRL)
-                                        cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfShape;
+                                        tc->text_sel_end.thisStartOfShape();
                                     else
-                                        cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisStartOfLine;
+                                        tc->text_sel_end.thisStartOfLine();
+                                    cursor_moved = true;
                                     break;
                                 }
                                 return TRUE;
@@ -1037,9 +1142,26 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                             case GDK_KP_End:
                                 if (tc->text) {
                                     if (MOD__CTRL)
-                                        cursor_movement_operator = &Inkscape::Text::Layout::iterator::nextStartOfShape;
+                                        tc->text_sel_end.nextStartOfShape();
                                     else
-                                        cursor_movement_operator = &Inkscape::Text::Layout::iterator::thisEndOfLine;
+                                        tc->text_sel_end.thisEndOfLine();
+                                    cursor_moved = true;
+                                    break;
+                                }
+                                return TRUE;
+                            case GDK_Page_Down:
+                            case GDK_KP_Page_Down:
+                                if (tc->text) {
+                                    tc->text_sel_end.cursorDown(screenlines);
+                                    cursor_moved = true;
+                                    break;
+                                }
+                                return TRUE;
+                            case GDK_Page_Up:
+                            case GDK_KP_Page_Up:
+                                if (tc->text) {
+                                    tc->text_sel_end.cursorUp(screenlines);
+                                    cursor_moved = true;
                                     break;
                                 }
                                 return TRUE;
@@ -1050,9 +1172,9 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                                         sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
                                         tc->grabbed = NULL;
                                     }
-                                    Inkscape::Rubberband::get()->stop();
+                                    Inkscape::Rubberband::get(desktop)->stop();
                                 } else {
-                                    sp_desktop_selection(ec->desktop)->clear();
+                                    sp_desktop_selection(desktop)->clear();
                                 }
                                 tc->nascent_object = FALSE;
                                 return TRUE;
@@ -1062,14 +1184,15 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                                         if (MOD__ALT) {
                                             if (MOD__SHIFT) {
                                                 // FIXME: alt+shift+[] does not work, don't know why
-                                                sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
+                                                sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
                                             } else {
-                                                sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
+                                                sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
                                             }
                                         } else {
-                                            sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -90);
+                                            sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -90);
                                         }
-                                        sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:ccw");
+                                        DocumentUndo::maybeDone(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
+                                                               _("Rotate counterclockwise"));
                                         sp_text_context_update_cursor(tc);
                                         sp_text_context_update_text_selection(tc);
                                         return TRUE;
@@ -1082,14 +1205,15 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                                         if (MOD__ALT) {
                                             if (MOD__SHIFT) {
                                                 // FIXME: alt+shift+[] does not work, don't know why
-                                                sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
+                                                sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
                                             } else {
-                                                sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
+                                                sp_te_adjust_rotation_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
                                             }
                                         } else {
-                                            sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 90);
+                                            sp_te_adjust_rotation(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 90);
                                         }
-                                        sp_document_maybe_done(sp_desktop_document(ec->desktop), "textrot:cw");
+                                        DocumentUndo::maybeDone(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
+                                                               _("Rotate clockwise"));
                                         sp_text_context_update_cursor(tc);
                                         sp_text_context_update_text_selection(tc);
                                         return TRUE;
@@ -1102,16 +1226,18 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                                     if (MOD__ALT) {
                                         if (MOD__CTRL) {
                                             if (MOD__SHIFT)
-                                                sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
+                                                sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
                                             else
-                                                sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
-                                            sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:dec");
+                                                sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -1);
+                                            DocumentUndo::maybeDone(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
+                                                                   _("Contract line spacing"));
                                         } else {
                                             if (MOD__SHIFT)
-                                                sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -10);
+                                                sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, -10);
                                             else
-                                                sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, -1);
-                                            sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:dec");
+                                                sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->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(tc);
                                         sp_text_context_update_text_selection(tc);
@@ -1125,16 +1251,18 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                                     if (MOD__ALT) {
                                         if (MOD__CTRL) {
                                             if (MOD__SHIFT)
-                                                sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
+                                                sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
                                             else
-                                                sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
-                                            sp_document_maybe_done(sp_desktop_document(ec->desktop), "linespacing:inc");
+                                                sp_te_adjust_linespacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 1);
+                                            DocumentUndo::maybeDone(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
+                                                                   _("Expand line spacing"));
                                         } else {
                                             if (MOD__SHIFT)
-                                                sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 10);
+                                                sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, desktop, 10);
                                             else
-                                                sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->text_sel_end, ec->desktop, 1);
-                                            sp_document_maybe_done(sp_desktop_document(ec->desktop), "letterspacing:inc");
+                                                sp_te_adjust_tspan_letterspacing_screen(tc->text, tc->text_sel_start, tc->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(tc);
                                         sp_text_context_update_text_selection(tc);
@@ -1146,10 +1274,7 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                                 break;
                         }
 
-                        if (cursor_movement_operator) {
-                            Inkscape::Text::Layout::iterator old_start = tc->text_sel_start;
-                            Inkscape::Text::Layout::iterator old_end = tc->text_sel_end;
-                            (tc->text_sel_end.*cursor_movement_operator)();
+                        if (cursor_moved) {
                             if (!MOD__SHIFT)
                                 tc->text_sel_start = tc->text_sel_end;
                             if (old_start != tc->text_sel_start || old_end != tc->text_sel_end) {
@@ -1175,8 +1300,11 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
                             sp_canvas_item_ungrab(tc->grabbed, GDK_CURRENT_TIME);
                             tc->grabbed = NULL;
                         }
-                        Inkscape::Rubberband::get()->stop();
+                        Inkscape::Rubberband::get(desktop)->stop();
                     }
+                } else if ((group0_keyval == GDK_x || group0_keyval == GDK_X) && MOD__ALT_ONLY) {
+                    desktop->setToolboxFocusTo ("altx-text");
+                    return TRUE;
                 }
             }
             break;
@@ -1193,7 +1321,7 @@ sp_text_context_root_handler(SPEventContext *const ec, GdkEvent *const event)
 
     // if nobody consumed it so far
     if (((SPEventContextClass *) parent_class)->root_handler) { // and there's a handler in parent context,
-        return ((SPEventContextClass *) parent_class)->root_handler(ec, event); // send event to parent
+        return ((SPEventContextClass *) 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
     }
@@ -1214,9 +1342,35 @@ sp_text_paste_inline(SPEventContext *ec)
         // 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 text = refClipboard->wait_for_text();
-
-        if (!text.empty()) {
+        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);
@@ -1236,7 +1390,8 @@ sp_text_paste_inline(SPEventContext *ec)
                 tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
                 begin = end + 1;
             }
-            sp_document_done(sp_desktop_document(ec->desktop));
+            DocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
+                              _("Paste text"));
 
             return true;
         }
@@ -1261,6 +1416,21 @@ sp_text_get_selected_text(SPEventContext const *ec)
     return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
 }
 
+SPCSSAttr *
+sp_text_get_style_at_cursor(SPEventContext const *ec)
+{
+    if (!SP_IS_TEXT_CONTEXT(ec))
+        return NULL;
+    SPTextContext 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((SPItem *) obj);
+    return NULL;
+}
+
 /**
  Deletes the currently selected characters. Returns false if there is no
  text selection currently.
@@ -1275,9 +1445,21 @@ bool sp_text_delete_selection(SPEventContext *ec)
 
     if (tc->text_sel_start == tc->text_sel_end)
         return false;
-    tc->text_sel_start = tc->text_sel_end = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end);
+
+    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;
 }
 
@@ -1291,26 +1473,10 @@ sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext
 
     SPEventContext *ec = SP_EVENT_CONTEXT(tc);
 
-    if (ec->shape_knot_holder) { // destroy knotholder
-        sp_knot_holder_destroy(ec->shape_knot_holder);
-        ec->shape_knot_holder = NULL;
-    }
-
-    if (ec->shape_repr) { // remove old listener
-        sp_repr_remove_listener_by_data(ec->shape_repr, ec);
-        Inkscape::GC::release(ec->shape_repr);
-        ec->shape_repr = 0;
-    }
-
+    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_knot_holder = sp_item_knot_holder(item, ec->desktop);
-        Inkscape::XML::Node *shape_repr = SP_OBJECT_REPR(SP_FLOWTEXT(item)->get_frame(NULL));
-        if (shape_repr) {
-            ec->shape_repr = shape_repr;
-            Inkscape::GC::anchor(shape_repr);
-            sp_repr_add_listener(shape_repr, &ec_shape_repr_events, ec);
-        }
+        ec->shape_editor->set_item(item, SH_KNOTHOLDER);
     }
 
     if (tc->text && (item != tc->text)) {
@@ -1334,7 +1500,7 @@ sp_text_context_selection_changed(Inkscape::Selection *selection, SPTextContext
 }
 
 static void
-sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, SPTextContext *tc)
+sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, SPTextContext *tc)
 {
     sp_text_context_update_cursor(tc);
     sp_text_context_update_text_selection(tc);
@@ -1349,7 +1515,8 @@ sp_text_context_style_set(SPCSSAttr const *css, SPTextContext *tc)
         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);
-    sp_document_done(sp_desktop_document(tc->desktop));
+    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);
 
@@ -1383,10 +1550,12 @@ sp_text_context_style_query(SPStyle *style, int property, SPTextContext *tc)
         SPObject const *pos_obj = 0;
         void *rawptr = 0;
         layout->getSourceOfCharacter(it, &rawptr);
+        if (!rawptr || !SP_IS_OBJECT(rawptr))
+            continue;
         pos_obj = SP_OBJECT(rawptr);
-        if (pos_obj == 0) continue;
-        while (SP_OBJECT_STYLE(pos_obj) == NULL && SP_OBJECT_PARENT(pos_obj))
-            pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
+        while (SP_IS_STRING(pos_obj) && SP_OBJECT_PARENT(pos_obj)) {
+           pos_obj = SP_OBJECT_PARENT(pos_obj);   // SPStrings don't have style
+        }
         styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
     }
 
@@ -1413,40 +1582,64 @@ sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
 {
     GdkRectangle im_cursor = { 0, 0, 1, 1 };
 
+    // 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) {
-        NR::Point p0, p1;
+        Geom::Point p0, p1;
         sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
-        NR::Point const d0 = p0 * sp_item_i2d_affine(SP_ITEM(tc->text));
-        NR::Point const d1 = p1 * sp_item_i2d_affine(SP_ITEM(tc->text));
+        Geom::Point const d0 = p0 * SP_ITEM(tc->text)->i2d_affine();
+        Geom::Point const d1 = p1 * SP_ITEM(tc->text)->i2d_affine();
 
         // scroll to show cursor
         if (scroll_to_see) {
-            NR::Point const dm = (d0 + d1) / 2;
-            // 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(&dm, 1.0);
+            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);
         sp_ctrlline_set_coords(SP_CTRLLINE(tc->cursor), d0, d1);
 
         /* fixme: ... need another transformation to get canvas widget coordinate space? */
-        im_cursor.x = (int) floor(d0[NR::X]);
-        im_cursor.y = (int) floor(d0[NR::Y]);
-        im_cursor.width = (int) floor(d1[NR::X]) - im_cursor.x;
-        im_cursor.height = (int) floor(d1[NR::Y]) - im_cursor.y;
+        im_cursor.x = (int) floor(d0[Geom::X]);
+        im_cursor.y = (int) floor(d0[Geom::Y]);
+        im_cursor.width = (int) floor(d1[Geom::X]) - im_cursor.x;
+        im_cursor.height = (int) floor(d1[Geom::Y]) - im_cursor.y;
 
         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);
-                SP_CTRLRECT(tc->frame)->setRectangle(sp_item_bbox_desktop(frame));
+                Geom::OptRect frame_bbox = frame->getBboxDesktop();
+                if (frame_bbox) {
+                    SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
+                }
             }
-            SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type flowed text; <b>Enter</b> to start new paragraph."));
+
+            SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit flowed text (%d characters%s); <b>Enter</b> to start new paragraph."), nChars, trunc);
         } else {
-            SP_EVENT_CONTEXT(tc)->_message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line."));
+            SP_EVENT_CONTEXT(tc)->_message_context->setF(Inkscape::NORMAL_MESSAGE, _("Type or edit text (%d characters%s); <b>Enter</b> to start new line."), nChars, trunc);
         }
 
     } else {
@@ -1466,19 +1659,25 @@ sp_text_context_update_cursor(SPTextContext *tc,  bool scroll_to_see)
 
 static void sp_text_context_update_text_selection(SPTextContext *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);
         gtk_object_destroy(*it);
     }
     tc->text_selection_quads.clear();
 
-    std::vector<NR::Point> quads;
+    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, sp_item_i2d_affine(tc->text));
+        quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, (tc->text)->i2d_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);
-        sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x000000ff);
+        // 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);
@@ -1489,12 +1688,13 @@ static gint
 sp_text_context_timeout(SPTextContext *tc)
 {
     if (tc->show) {
+        sp_canvas_item_show(tc->cursor);
         if (tc->phase) {
             tc->phase = 0;
-            sp_canvas_item_hide(tc->cursor);
+            sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0x000000ff);
         } else {
             tc->phase = 1;
-            sp_canvas_item_show(tc->cursor);
+            sp_ctrlline_set_rgba32(SP_CTRLLINE(tc->cursor), 0xffffffff);
         }
     }
 
@@ -1506,9 +1706,15 @@ sp_text_context_forget_text(SPTextContext *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=SP_OBJECT_REPR(ti);
         // the repr may already have been unparented
@@ -1517,26 +1723,29 @@ sp_text_context_forget_text(SPTextContext *tc)
         // the XML editor
         if ( text_repr && sp_repr_parent(text_repr) ) {
             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, SPTextContext *tc)
+sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
 {
     gtk_im_context_focus_in(tc->imc);
     return FALSE;
 }
 
 gint
-sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, SPTextContext *tc)
+sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, SPTextContext *tc)
 {
     gtk_im_context_focus_out(tc->imc);
     return FALSE;
 }
 
 static void
-sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
+sptc_commit(GtkIMContext */*imc*/, gchar *string, SPTextContext *tc)
 {
     if (!tc->text) {
         sp_text_context_setup_text(tc);
@@ -1547,7 +1756,31 @@ sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
     sp_text_context_update_cursor(tc);
     sp_text_context_update_text_selection(tc);
 
-    sp_document_done(SP_OBJECT_DOCUMENT(tc->text));
+    DocumentUndo::done(SP_OBJECT_DOCUMENT(tc->text), SP_VERB_CONTEXT_TEXT,
+                      _("Type text"));
+}
+
+void
+sp_text_context_place_cursor (SPTextContext *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 (SPTextContext *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(SPTextContext *tc, SPObject *text)
+{
+    if (text != tc->text)
+        return NULL;
+    return &(tc->text_sel_end);
 }
 
 
@@ -1560,4 +1793,4 @@ sptc_commit(GtkIMContext *imc, gchar *string, SPTextContext *tc)
   fill-column:99
   End:
 */
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :