text-tool.cpp revision 080a261ed16d96daf01a6140ca7e66d6dc389ebd
/*
* 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 <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 "ui/tools/text-tool.h"
#include "text-editing.h"
#include "ui/control-manager.h"
#include "verbs.h"
#include "xml/node-event-vector.h"
#include "tool-factory.h"
using Inkscape::ControlManager;
using Inkscape::DocumentUndo;
namespace Inkscape {
namespace UI {
namespace Tools {
static void sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, TextTool *tc);
namespace {
return new TextTool();
}
bool textContextRegistered = ToolFactory::instance().registerObject("/tools/text", createTextContext);
}
}
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->unimode = false;
this->timeout = 0;
this->phase = 0;
this->nascent_object = 0;
this->over_text = 0;
this->dragging = 0;
this->creating = 0;
}
delete this->shape_editor;
this->shape_editor = NULL;
if (this->grabbed) {
}
}
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));
sp_canvas_item_hide(this->cursor);
sp_canvas_item_hide(this->indicator);
sp_canvas_item_hide(this->frame);
this->imc = gtk_im_multicontext_new();
if (this->imc) {
/* 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.
*/
if (gtk_widget_has_focus(canvas)) {
}
}
}
);
);
);
);
this->enableSelectionCue();
}
this->enableGrDrag();
}
}
if (this->desktop) {
}
this->enableGrDrag(false);
this->style_set_connection.disconnect();
this->style_query_connection.disconnect();
this->sel_changed_connection.disconnect();
this->sel_modified_connection.disconnect();
if (this->imc) {
}
if (this->timeout) {
g_source_remove(this->timeout);
this->timeout = 0;
}
if (this->cursor) {
sp_canvas_item_destroy(this->cursor);
}
if (this->indicator) {
sp_canvas_item_destroy(this->indicator);
}
if (this->frame) {
sp_canvas_item_destroy(this->frame);
}
}
this->text_selection_quads.clear();
}
case GDK_BUTTON_PRESS:
// find out clicked item, disregarding groups
if (this->text) {
// find out click point in document coordinates
// set the cursor closest to that point
this->text_sel_start = old_start;
} else {
}
// update display
this->dragging = 1;
}
}
}
break;
case GDK_2BUTTON_PRESS:
if (layout) {
this->text_sel_start.prevStartOfWord();
this->text_sel_end.nextEndOfWord();
this->dragging = 2;
}
}
break;
case GDK_3BUTTON_PRESS:
this->text_sel_start.thisStartOfLine();
this->text_sel_end.thisEndOfLine();
this->dragging = 3;
}
break;
case GDK_BUTTON_RELEASE:
this->dragging = 0;
}
break;
case GDK_MOTION_NOTIFY:
if (!layout) break;
// find out click point in document coordinates
// set the cursor closest to that point
if (this->dragging == 2) {
// double-click dragging: go by word
if (new_end < this->text_sel_start) {
} else
} else if (this->dragging == 3) {
// triple-click dragging: go by line
if (new_end < this->text_sel_start)
else
}
// update display
if (this->text_sel_end != new_end) {
this->text_sel_end = new_end;
}
break;
}
// find out item under mouse, disregarding groups
if (layout->inputTruncated()) {
} else {
}
if (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();
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;
}
break;
default:
break;
}
if (!ret) {
}
return ret;
}
{
/* Create <text> */
/* Set style */
/* Create <tspan> */
/* Create TEXT */
/* fixme: Is selection::changed really immediate? */
/* yes, it's immediate .. why does it matter? */
text_item->updateRepr();
_("Create text"));
}
/**
* Insert the character indicated by tc.uni to replace the current selection,
*
*/
{
unsigned int 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.
_("Non-printable character"));
} else {
}
gchar u[10];
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);
_("Insert Unicode character"));
}
}
{
unsigned int uv;
uv = 0xfffd;
}
}
{
char utf8[10];
/* Status bar messages are in pango markup, so we need xml escaping. */
switch(utf8[0]) {
default: break;
}
}
} else {
tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
}
}
sp_canvas_item_hide(this->indicator);
case GDK_BUTTON_PRESS:
return TRUE;
}
// save drag origin
this->within_tolerance = true;
m.unSetup();
GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
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();
}
if ( this->within_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;
m.unSetup();
// status text
this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s × %s"), xs->str, ys->str);
} else if (!sp_event_context_knot_mouseover(this)) {
m.unSetup();
}
break;
case GDK_BUTTON_RELEASE:
m.unSetup();
if (this->grabbed) {
}
if (this->creating && this->within_tolerance) {
/* Button 1, set X & Y & new item */
this->phase = 1;
/* 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:
if (this->imc) {
}
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) {
// otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
/* Set style */
_("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: {
if (group0_keyval == GDK_KEY_KP_Add ||
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
// but we have our own so make sure they don't swallow it
//IM did not consume the key, or we're in 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: {
if (this->unipos) {
}
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)) {
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;
}
}
}
}
bool cursor_moved = false;
int screenlines = 1;
if (this->text) {
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)) {
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");
_("Insert no-break space"));
return TRUE;
}
break;
case GDK_KEY_U:
case GDK_KEY_u:
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:
SPStyle const *style = sp_te_style_at_position(this->text, std::min(this->text_sel_start, this->text_sel_end));
else
_("Make bold"));
return TRUE;
}
break;
case GDK_KEY_I:
case GDK_KEY_i:
SPStyle const *style = sp_te_style_at_position(this->text, std::min(this->text_sel_start, this->text_sel_end));
else
_("Make italic"));
return TRUE;
}
break;
case GDK_KEY_A:
case GDK_KEY_a:
if (layout) {
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>
}
(void)success; // TODO cleanup
_("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;
this->text_sel_start = this->text_sel_end;
}
if (this->text_sel_start == this->text_sel_end) {
this->text_sel_start.prevStartOfWord();
} else {
this->text_sel_start.prevCursorPosition();
}
noSelection = true;
}
if (noSelection) {
if (success) {
} else { // nothing deleted
}
} else {
if (success) {
} else { // nothing deleted
}
}
_("Backspace"));
}
return TRUE;
case GDK_KEY_Delete:
case GDK_KEY_KP_Delete:
if (this->text) {
bool noSelection = false;
this->text_sel_start = this->text_sel_end;
}
if (this->text_sel_start == this->text_sel_end) {
this->text_sel_end.nextEndOfWord();
} else {
this->text_sel_end.nextCursorPosition();
}
noSelection = true;
}
if (noSelection) {
} else {
if (success) {
} else { // nothing deleted
}
}
_("Delete"));
}
return TRUE;
case GDK_KEY_Left:
case GDK_KEY_KP_Left:
case GDK_KEY_KP_4:
if (this->text) {
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));
_("Kern to the left"));
} else {
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__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));
_("Kern to the right"));
} else {
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__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));
_("Kern up"));
} else {
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__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));
_("Kern down"));
} else {
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) {
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) {
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) {
cursor_moved = true;
break;
}
return TRUE;
case GDK_KEY_Page_Up:
case GDK_KEY_KP_Page_Up:
if (this->text) {
cursor_moved = true;
break;
}
return TRUE;
case GDK_KEY_Escape:
if (this->creating) {
this->creating = 0;
if (this->grabbed) {
}
} else {
}
this->nascent_object = FALSE;
return TRUE;
case GDK_KEY_bracketleft:
if (this->text) {
if (MOD__SHIFT(event)) {
// FIXME: alt+shift+[] does not work, don't know why
} else {
}
} else {
}
_("Rotate counterclockwise"));
return TRUE;
}
}
break;
case GDK_KEY_bracketright:
if (this->text) {
if (MOD__SHIFT(event)) {
// FIXME: alt+shift+[] does not work, don't know why
} else {
}
} else {
}
_("Rotate clockwise"));
return TRUE;
}
}
break;
case GDK_KEY_less:
case GDK_KEY_comma:
if (this->text) {
if (MOD__SHIFT(event))
sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10);
else
_("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);
_("Contract letter spacing"));
}
return TRUE;
}
}
break;
case GDK_KEY_greater:
case GDK_KEY_period:
if (this->text) {
if (MOD__SHIFT(event))
else
_("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);
_("Expand letter spacing"));\
}
return TRUE;
}
}
break;
default:
break;
}
if (cursor_moved) {
if (!MOD__SHIFT(event))
this->text_sel_start = this->text_sel_end;
}
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,
if ((group0_keyval == GDK_KEY_Up ||
group0_keyval == GDK_KEY_Down ||
group0_keyval == GDK_KEY_KP_Up ||
&& !MOD__CTRL_ONLY(event)) {
return TRUE;
if (this->creating) {
this->creating = 0;
if (this->grabbed) {
}
}
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
// }
}
/**
Attempts to paste system clipboard into the currently edited text, returns true on success
*/
{
if (!SP_IS_TEXT_CONTEXT(ec))
return false;
// there is an active text object in this context, or a new object was just created
// 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.
{
// 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 {
}
}
}
// using indices is slow in ustrings. Whatever.
for ( ; ; ) {
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());
}
_("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.
*/
{
if (!SP_IS_TEXT_CONTEXT(ec))
return "";
return "";
}
{
if (!SP_IS_TEXT_CONTEXT(ec))
return NULL;
return NULL;
if (obj) {
}
return NULL;
}
/**
Deletes the currently selected characters. Returns false if there is no
text selection currently.
*/
{
if (!SP_IS_TEXT_CONTEXT(ec))
return false;
return false;
return false;
if (success) {
} else { // nothing deleted
}
return true;
}
/**
* \param selection Should not be NULL.
*/
static void
{
}
}
if (layout)
} else {
}
// 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);
}
static void
sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, TextTool *tc)
{
}
{
return false;
return false; // will get picked up by the parent and applied to the whole text object
_("Set text style"));
return true;
}
static int
{
return QUERY_STYLE_NOTHING;
}
return QUERY_STYLE_NOTHING;
}
} else {
}
if (!begin_it.prevCharacter()) {
}
}
void *rawptr = 0;
continue;
}
}
}
return result;
}
{
return;
if (layout) { // undo can change the text length without us knowing it
}
}
{
// due to interruptible display, tc may already be destroyed during a display update before
// the cursor update (can't do both atomically, alas)
// scroll to show cursor
if (scroll_to_see) {
// unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
else
}
/* fixme: ... need another transformation to get canvas widget coordinate space? */
}
char const *trunc = "";
bool truncated = false;
if (layout->inputTruncated()) {
truncated = true;
trunc = _(" [truncated]");
}
if (frame) {
if (truncated) {
} else {
}
if (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 {
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
}
}
}
{
// due to interruptible display, tc may already be destroyed during a display update before
// the selection update (can't do both atomically, alas)
for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
}
quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, (tc->text)->i2dt_affine());
// FIXME: make the color settable in prefs
// for now, use semitrasparent blue, as cairo cannot do inversion :(
sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
}
}
{
} else {
}
}
return TRUE;
}
{
(void)ti;
/* We have to set it to zero,
* or selection changed signal messes everything up */
/* 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"));
}
}
*/
}
{
return FALSE;
}
{
return FALSE;
}
{
}
tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
_("Type text"));
}
void sp_text_context_place_cursor (TextTool *tc, SPObject *text, Inkscape::Text::Layout::iterator where)
{
}
{
}
{
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 :