diff --git a/area.js b/area.js
index 14b44cf..d447ae7 100644
--- a/area.js
+++ b/area.js
@@ -813,7 +813,9 @@ var DrawingArea = new Lang.Class({
this.textHasCursor = true;
this._redisplay();
- // Do not hide and do not set opacity to 0 because ibusCandidatePopup need a mapped text entry to init correctly its position.
+ // Do not hide and do not set opacity to 0 because:
+ // 1. ibusCandidatePopup need a mapped text entry to init correctly its position.
+ // 2. 'cursor-changed' signal is no emitted if the text entry is not visible.
this.textEntry = new St.Entry({ opacity: 1, x: stageX + x, y: stageY + y });
this.insert_child_below(this.textEntry, null);
this.textEntry.grab_key_focus();
@@ -832,17 +834,26 @@ var DrawingArea = new Lang.Class({
this.textEntry.connect('destroy', () => ibusCandidatePopup.disconnect(this.ibusHandler));
}
+ this.textEntry.clutterText.set_single_line_mode(false);
this.textEntry.clutterText.connect('activate', (clutterText) => {
this._stopWriting();
});
- this.textEntry.clutterText.connect('text-changed', (clutterText) => {
- GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
- this.currentElement.text = clutterText.text;
- this.currentElement.cursorPosition = clutterText.cursorPosition;
- this._updateTextCursorTimeout();
- this._redisplay();
- });
+ let showCursorOnPositionChanged = true;
+ this.textEntry.clutterText.connect('text-changed', clutterText => {
+ this.textEntry.y = stageY + y + (this.textEntry.clutterText.get_layout().get_line_count() - 1) * this.currentElement.height;
+ this.currentElement.text = clutterText.text;
+ showCursorOnPositionChanged = false;
+ this._redisplay();
+ });
+
+ this.textEntry.clutterText.connect('cursor-changed', clutterText => {
+ this.currentElement.cursorPosition = clutterText.cursorPosition;
+ this._updateTextCursorTimeout();
+ let cursorPosition = clutterText.cursorPosition == -1 ? clutterText.text.length : clutterText.cursorPosition;
+ this.textHasCursor = showCursorOnPositionChanged || GLib.unichar_isspace(clutterText.text.charAt(cursorPosition - 1));
+ showCursorOnPositionChanged = true;
+ this._redisplay();
});
this.textEntry.clutterText.connect('key-press-event', (clutterText, event) => {
@@ -853,53 +864,25 @@ var DrawingArea = new Lang.Class({
} else if (event.has_shift_modifier() &&
(event.get_key_symbol() == Clutter.KEY_Return ||
event.get_key_symbol() == Clutter.KEY_KP_Enter)) {
- let startNewLine = true;
- this._stopWriting(startNewLine);
- clutterText.text = "";
+ clutterText.insert_unichar('\n');
return Clutter.EVENT_STOP;
}
- // 'cursor-changed' signal is not emitted if the text entry is not visible.
- // So key events related to the cursor must be listened.
- if (event.get_key_symbol() == Clutter.KEY_Left || event.get_key_symbol() == Clutter.KEY_Right ||
- event.get_key_symbol() == Clutter.KEY_Home || event.get_key_symbol() == Clutter.KEY_End) {
- GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
- this.currentElement.cursorPosition = clutterText.cursorPosition;
- this._updateTextCursorTimeout();
- this.textHasCursor = true;
- this._redisplay();
- });
- }
-
return Clutter.EVENT_PROPAGATE;
});
},
- _stopWriting: function(startNewLine) {
+ _stopWriting: function() {
if (this.currentElement.text.length > 0)
this.elements.push(this.currentElement);
- if (startNewLine && this.currentElement.points.length == 2) {
- this.currentElement.lineIndex = this.currentElement.lineIndex || 0;
- // copy object, the original keep existing in this.elements
- this.currentElement = Object.create(this.currentElement);
- this.currentElement.lineIndex ++;
- // define a new 'points' array, the original keep existing in this.elements
- this.currentElement.points = [
- [this.currentElement.points[0][0], this.currentElement.points[0][1] + this.currentElement.height],
- [this.currentElement.points[1][0], this.currentElement.points[1][1] + this.currentElement.height]
- ];
- this.currentElement.text = "";
- this.textEntry.set_y(this.currentElement.y);
- } else {
- this.currentElement = null;
- this._stopTextCursorTimeout();
- this.textEntry.destroy();
- delete this.textEntry;
- this.grab_key_focus();
- this.updateActionMode();
- this.updatePointerCursor();
- }
+ this.currentElement = null;
+ this._stopTextCursorTimeout();
+ this.textEntry.destroy();
+ delete this.textEntry;
+ this.grab_key_focus();
+ this.updateActionMode();
+ this.updatePointerCursor();
this._redisplay();
},
diff --git a/elements.js b/elements.js
index 5890187..1267826 100644
--- a/elements.js
+++ b/elements.js
@@ -645,7 +645,6 @@ const _DrawingElement = new Lang.Class({
},
// The figure rotation center before transformations (original).
- // this.textWidth is computed during Cairo building.
_getOriginalCenter: function() {
if (!this._originalCenter) {
let points = this.points;
@@ -710,7 +709,6 @@ const TextElement = new Lang.Class({
eraser: this.eraser,
transformations: this.transformations,
text: this.text,
- lineIndex: this.lineIndex !== undefined ? this.lineIndex : undefined,
textRightAligned: this.textRightAligned,
font: this.font.to_string(),
points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100])
@@ -730,42 +728,50 @@ const TextElement = new Lang.Class({
return Math.abs(this.points[1][1] - this.points[0][1]);
},
- // When rotating grouped lines, lineOffset is used to retrieve the rotation center of the first line.
- get lineOffset() {
- return (this.lineIndex || 0) * this.height;
+ // this.lineWidths is computed during Cairo building.
+ _getLineX: function(index) {
+ return this.points[1][0] - (this.textRightAligned && this.lineWidths && this.lineWidths[index] ? this.lineWidths[index] : 0);
},
_drawCairo: function(cr, params) {
- if (this.points.length == 2) {
- let layout = PangoCairo.create_layout(cr);
- let fontSize = this.height * Pango.SCALE;
- this.font.set_absolute_size(fontSize);
- layout.set_font_description(this.font);
- layout.set_text(this.text, -1);
- this.textWidth = layout.get_pixel_size()[0];
- cr.moveTo(this.x, this.y);
- layout.set_text(this.text, -1);
- PangoCairo.show_layout_line(cr, layout.get_line(0));
+ if (this.points.length != 2)
+ return;
+
+ let layout = PangoCairo.create_layout(cr);
+ let fontSize = this.height * Pango.SCALE;
+ this.font.set_absolute_size(fontSize);
+ layout.set_font_description(this.font);
+ layout.set_text(this.text, -1);
+ this.textWidth = layout.get_pixel_size()[0];
+ this.lineWidths = layout.get_lines_readonly().map(layoutLine => layoutLine.get_pixel_extents()[1].width)
+
+ layout.get_lines_readonly().forEach((layoutLine, index) => {
+ cr.moveTo(this._getLineX(index), this.y + this.height * index);
+ PangoCairo.show_layout_line(cr, layoutLine);
+ });
+
+ // Cannot use 'layout.index_to_line_x(cursorPosition, 0)' because character position != byte index
+ if (params.showTextCursor) {
+ let layoutCopy = layout.copy();
+ if (this.cursorPosition != -1)
+ layoutCopy.set_text(this.text.slice(0, this.cursorPosition), -1);
- if (params.showTextCursor) {
- let cursorPosition = this.cursorPosition == -1 ? this.text.length : this.cursorPosition;
- layout.set_text(this.text.slice(0, cursorPosition), -1);
- let width = layout.get_pixel_size()[0];
- cr.rectangle(this.x + width, this.y,
- this.height / 25, - this.height);
- cr.fill();
- }
-
- if (params.showTextRectangle) {
- cr.rectangle(this.x, this.y - this.lineOffset,
- this.textWidth, - this.height);
- setDummyStroke(cr);
- } else if (params.drawTextRectangle) {
- cr.rectangle(this.x, this.y,
- this.textWidth, - this.height);
- // Only draw the rectangle to find the element, not to show it.
- cr.setLineWidth(0);
- }
+ let cursorLineIndex = layoutCopy.get_line_count() - 1;
+ let cursorX = this._getLineX(cursorLineIndex) + layoutCopy.get_line_readonly(cursorLineIndex).get_pixel_extents()[1].width;
+ let cursorY = this.y + this.height * cursorLineIndex;
+ cr.rectangle(cursorX, cursorY, this.height / 25, - this.height);
+ cr.fill();
+ }
+
+ if (params.showTextRectangle) {
+ cr.rectangle(this.x, this.y - this.height,
+ this.textWidth, this.height * layout.get_line_count());
+ setDummyStroke(cr);
+ } else if (params.drawTextRectangle) {
+ cr.rectangle(this.x, this.y - this.height,
+ this.textWidth, this.height * layout.get_line_count());
+ // Only draw the rectangle to find the element, not to show it.
+ cr.setLineWidth(0);
}
},
@@ -774,32 +780,48 @@ const TextElement = new Lang.Class({
},
_drawSvg: function(transAttribute, bgcolorString) {
- let row = "\n ";
- let [x, y, height] = [Math.round(this.x*100)/100, Math.round(this.y*100)/100, Math.round(this.height*100)/100];
+ if (this.points.length != 2)
+ return "";
+
+ let row = "";
+ let height = Math.round(this.height * 100) / 100;
let color = this.eraser ? bgcolorString : this.color.toJSON();
let attributes = this.eraser ? `class="eraser" ` : '';
+ attributes += `fill="${color}" ` +
+ `font-size="${height}" ` +
+ `font-family="${this.font.get_family()}"`;
- if (this.points.length == 2) {
- attributes += `fill="${color}" ` +
- `font-size="${height}" ` +
- `font-family="${this.font.get_family()}"`;
-
- // this.font.to_string() is not valid to fill the svg 'font' shorthand property.
- // Each property must be filled separately.
- ['Stretch', 'Style', 'Variant'].forEach(attribute => {
- let lower = attribute.toLowerCase();
- if (this.font[`get_${lower}`]() != Pango[attribute].NORMAL) {
- let font = new Pango.FontDescription();
- font[`set_${lower}`](this.font[`get_${lower}`]());
- attributes += ` font-${lower}="${font.to_string()}"`;
- }
- });
- if (this.font.get_weight() != Pango.Weight.NORMAL)
- attributes += ` font-weight="${this.font.get_weight()}"`;
- row += `${this.text}`;
+ // this.font.to_string() is not valid to fill the svg 'font' shorthand property.
+ // Each property must be filled separately.
+ ['Stretch', 'Style', 'Variant'].forEach(attribute => {
+ let lower = attribute.toLowerCase();
+ if (this.font[`get_${lower}`]() != Pango[attribute].NORMAL) {
+ let font = new Pango.FontDescription();
+ font[`set_${lower}`](this.font[`get_${lower}`]());
+ attributes += ` font-${lower}="${font.to_string()}"`;
+ }
+ });
+ if (this.font.get_weight() != Pango.Weight.NORMAL)
+ attributes += ` font-weight="${this.font.get_weight()}"`;
+
+ // It is a fallback for thumbnails. The following layout is not the same than the Cairo one and
+ // layoutLine.get_pixel_extents does not return the correct value with non-latin fonts.
+ // An alternative would be to store this.lineWidths in the json.
+ if (this.textRightAligned && !this.lineWidths) {
+ let clutterText = new Clutter.Text({ text: this.text });
+ let layout = clutterText.get_layout();
+ let fontSize = height * Pango.SCALE;
+ this.font.set_absolute_size(fontSize);
+ layout.set_font_description(this.font);
+ this.lineWidths = layout.get_lines_readonly().map(layoutLine => layoutLine.get_pixel_extents()[1].width);
}
+ this.text.split(/\r\n|\r|\n/).forEach((text, index) => {
+ let x = Math.round(this._getLineX(index) * 100) / 100;
+ let y = Math.round((this.y + this.height * index) * 100) / 100;
+ row += `\n ${text}`;
+ });
+
return row;
},
@@ -827,7 +849,7 @@ const TextElement = new Lang.Class({
_getOriginalCenter: function() {
if (!this._originalCenter) {
let points = this.points;
- this._originalCenter = this.textWidth ? [points[1][0], Math.max(points[0][1], points[1][1]) - this.lineOffset] :
+ this._originalCenter = points.length == 2 ? [points[1][0], Math.max(points[0][1], points[1][1])] :
points.length >= 3 ? getCentroid(points) :
getNaiveCenter(points);
}