multi-line text elements
* Can browse and break lines anywhere * Can past multi-line texts * Do not need lineIndex anymore and grouped lines are preserved permanently Related to #56
This commit is contained in:
parent
f18e8e6ac0
commit
5698f3f7cb
53
area.js
53
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, () => {
|
||||
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;
|
||||
this.currentElement.cursorPosition = clutterText.cursorPosition;
|
||||
this._updateTextCursorTimeout();
|
||||
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,45 +864,18 @@ 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();
|
||||
|
|
@ -899,7 +883,6 @@ var DrawingArea = new Lang.Class({
|
|||
this.grab_key_focus();
|
||||
this.updateActionMode();
|
||||
this.updatePointerCursor();
|
||||
}
|
||||
|
||||
this._redisplay();
|
||||
},
|
||||
|
|
|
|||
74
elements.js
74
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,43 +728,51 @@ 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) {
|
||||
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];
|
||||
cr.moveTo(this.x, this.y);
|
||||
layout.set_text(this.text, -1);
|
||||
PangoCairo.show_layout_line(cr, layout.get_line(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 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);
|
||||
let layoutCopy = layout.copy();
|
||||
if (this.cursorPosition != -1)
|
||||
layoutCopy.set_text(this.text.slice(0, this.cursorPosition), -1);
|
||||
|
||||
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.lineOffset,
|
||||
this.textWidth, - this.height);
|
||||
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.textWidth, - this.height);
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getContainsPoint: function(cr, x, y) {
|
||||
|
|
@ -774,12 +780,13 @@ 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" ` : '';
|
||||
|
||||
if (this.points.length == 2) {
|
||||
attributes += `fill="${color}" ` +
|
||||
`font-size="${height}" ` +
|
||||
`font-family="${this.font.get_family()}"`;
|
||||
|
|
@ -796,10 +803,25 @@ const TextElement = new Lang.Class({
|
|||
});
|
||||
if (this.font.get_weight() != Pango.Weight.NORMAL)
|
||||
attributes += ` font-weight="${this.font.get_weight()}"`;
|
||||
row += `<text ${attributes} x="${x}" `;
|
||||
row += `y="${y}"${transAttribute}>${this.text}</text>`;
|
||||
|
||||
// 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 ${attributes} x="${x}" y="${y}"${transAttribute}>${text}</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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue