Merge pull request #1457 from bromagosa/edition-improvements

text editing improvements
dev
Jens Mönig 2016-10-24 09:24:59 +02:00 zatwierdzone przez GitHub
commit 31aed55227
2 zmienionych plików z 215 dodań i 35 usunięć

Wyświetl plik

@ -7545,7 +7545,6 @@ InputSlotMorph.prototype.mouseDownLeft = function (pos) {
this.escalateEvent('mouseDownLeft', pos);
} else {
this.contents().edit();
this.contents().selectAll();
}
};
@ -7556,7 +7555,6 @@ InputSlotMorph.prototype.mouseClickLeft = function (pos) {
this.dropDownMenu();
} else {
this.contents().edit();
this.contents().selectAll();
}
};

Wyświetl plik

@ -1245,6 +1245,11 @@ function fontHeight(height) {
return minHeight * 1.2; // assuming 1/5 font size for ascenders
}
function isWordChar(aCharacter) {
// can't use \b or \w because they ignore diacritics
return aCharacter.match(/[A-zÀ-ÿ0-9]/);
}
function newCanvas(extentPoint, nonRetina) {
// answer a new empty instance of Canvas, don't display anywhere
// nonRetina - optional Boolean "false"
@ -4986,6 +4991,9 @@ CursorMorph.prototype.initializeClipboardHandler = function () {
'keydown',
function (event) {
myself.processKeyDown(event);
if (event.shiftKey) {
myself.world().currentKey = 16;
}
this.value = myself.target.selection();
this.select();
@ -4998,7 +5006,15 @@ CursorMorph.prototype.initializeClipboardHandler = function () {
},
false
);
this.clipboardHandler.addEventListener(
'keyup',
function (event) {
myself.world().currentKey = null;
},
false
);
this.clipboardHandler.addEventListener(
'input',
function (event) {
@ -5056,28 +5072,47 @@ CursorMorph.prototype.processKeyPress = function (event) {
CursorMorph.prototype.processKeyDown = function (event) {
// this.inspectKeyEvent(event);
var shift = event.shiftKey;
var shift = event.shiftKey,
wordNavigation = event.ctrlKey || event.altKey,
selecting = this.target.selection().length > 0;
this.keyDownEventUsed = false;
if (event.ctrlKey && (!event.altKey)) {
this.ctrl(event.keyCode, event.shiftKey);
// notify target's parent of key event
this.target.escalateEvent('reactToKeystroke', event);
return;
}
if (event.metaKey) {
this.cmd(event.keyCode, event.shiftKey);
// notify target's parent of key event
this.target.escalateEvent('reactToKeystroke', event);
return;
}
switch (event.keyCode) {
case 37:
this.goLeft(shift);
if (selecting && !shift && !wordNavigation) {
this.gotoSlot(Math.min(this.target.startMark, this.target.endMark));
this.target.clearSelection();
} else {
this.goLeft(
shift,
wordNavigation ?
this.slot - this.target.previousWordFrom(this.slot)
: 1);
}
this.keyDownEventUsed = true;
break;
case 39:
this.goRight(shift);
if (selecting && !shift && !wordNavigation) {
this.gotoSlot(Math.max(this.target.startMark, this.target.endMark));
this.target.clearSelection();
} else {
this.goRight(
shift,
wordNavigation ?
this.target.nextWordFrom(this.slot) - this.slot
: 1);
}
this.keyDownEventUsed = true;
break;
case 38:
@ -5168,9 +5203,9 @@ CursorMorph.prototype.gotoSlot = function (slot) {
}
};
CursorMorph.prototype.goLeft = function (shift) {
CursorMorph.prototype.goLeft = function (shift, howMany) {
this.updateSelection(shift);
this.gotoSlot(this.slot - 1);
this.gotoSlot(this.slot - (howMany || 1));
this.updateSelection(shift);
};
@ -5213,7 +5248,7 @@ CursorMorph.prototype.gotoPos = function (aPoint) {
CursorMorph.prototype.updateSelection = function (shift) {
if (shift) {
if (!this.target.endMark && !this.target.startMark) {
if (isNil(this.target.endMark) && isNil(this.target.startMark)) {
this.target.startMark = this.slot;
this.target.endMark = this.slot;
} else if (this.target.endMark !== this.slot) {
@ -7793,7 +7828,7 @@ StringMorph.prototype.renderWithBlanks = function (context, startX, y) {
});
};
// StringMorph mesuring:
// StringMorph measuring:
StringMorph.prototype.slotPosition = function (slot) {
// answer the position point of the given index ("slot")
@ -7818,8 +7853,10 @@ StringMorph.prototype.slotPosition = function (slot) {
};
StringMorph.prototype.slotAt = function (aPoint) {
// answer the slot (index) closest to the given point
// answer the slot (index) closest to the given point taking
// in account how far from the middle of the character it is,
// so the cursor can be moved accordingly
var txt = this.isPassword ?
this.password('*', this.text.length) : this.text,
idx = 0,
@ -7837,7 +7874,14 @@ StringMorph.prototype.slotAt = function (aPoint) {
}
}
}
return idx - 1;
// see where our click fell with respect to the middle of the char
if (aPoint.x - this.left() >
charX - context.measureText(txt[idx - 1]).width / 2) {
return idx;
} else {
return idx - 1;
}
};
StringMorph.prototype.upFrom = function (slot) {
@ -7860,6 +7904,41 @@ StringMorph.prototype.endOfLine = function () {
return this.text.length;
};
StringMorph.prototype.previousWordFrom = function (aSlot) {
// answer the slot (index) slots indicating the position of the
// previous word to the left of aSlot
var index = aSlot - 1;
// while the current character is non-word one, we skip it, so that
// if we are in the middle of a non-alphanumeric sequence, we'll get
// right to the beginning of the previous word
while (index > 0 && !isWordChar(this.text[index])) {
index -= 1
}
// while the current character is a word one, we skip it until we
// find the beginning of the current word
while (index > 0 && isWordChar(this.text[index - 1])) {
index -= 1
}
return index;
};
StringMorph.prototype.nextWordFrom = function (aSlot) {
var index = aSlot;
while (index < this.endOfLine() && !isWordChar(this.text[index])) {
index += 1
}
while (index < this.endOfLine() && isWordChar(this.text[index])) {
index += 1
}
return index;
};
StringMorph.prototype.rawHeight = function () {
// answer my corrected fontSize
return this.height() / 1.2;
@ -8025,13 +8104,13 @@ StringMorph.prototype.selectionStartSlot = function () {
StringMorph.prototype.clearSelection = function () {
if (!this.currentlySelecting &&
this.startMark === 0 &&
this.endMark === 0) {
isNil(this.startMark) &&
isNil(this.endMark)) {
return;
}
this.currentlySelecting = false;
this.startMark = 0;
this.endMark = 0;
this.startMark = null;
this.endMark = null;
this.drawNew();
this.changed();
};
@ -8047,8 +8126,13 @@ StringMorph.prototype.deleteSelection = function () {
};
StringMorph.prototype.selectAll = function () {
var cursor;
if (this.isEditable) {
this.startMark = 0;
cursor = this.root().cursor;
if (cursor) {
cursor.gotoSlot(this.text.length);
}
this.endMark = this.text.length;
this.drawNew();
this.changed();
@ -8056,13 +8140,31 @@ StringMorph.prototype.selectAll = function () {
};
StringMorph.prototype.mouseDownLeft = function (pos) {
if (this.isEditable) {
if (this.world().currentKey === 16) {
this.shiftClick(pos);
} else if (this.isEditable) {
this.clearSelection();
} else {
this.escalateEvent('mouseDownLeft', pos);
}
};
StringMorph.prototype.shiftClick = function (pos) {
var cursor = this.root().cursor;
if (cursor) {
if (!this.startMark) {
this.startMark = cursor.slot;
}
cursor.gotoPos(pos);
this.endMark = cursor.slot;
this.drawNew();
this.changed();
}
this.currentlySelecting = false;
this.escalateEvent('mouseDownLeft', pos);
};
StringMorph.prototype.mouseClickLeft = function (pos) {
var cursor;
if (this.isEditable) {
@ -8079,18 +8181,79 @@ StringMorph.prototype.mouseClickLeft = function (pos) {
}
};
StringMorph.prototype.mouseDoubleClick = function (pos) {
// selects the word at pos
// if there is no word, we select whatever is between
// the previous and next words
var slot = this.slotAt(pos);
if (this.isEditable) {
this.edit();
if (slot === this.text.length) {
slot -= 1;
}
if (isWordChar(this.text[slot])) {
this.selectWordAt(slot);
} else {
this.selectBetweenWordsAt(slot);
}
} else {
this.escalateEvent('mouseDoubleClick', pos);
}
};
StringMorph.prototype.selectWordAt = function (slot) {
var cursor = this.root().cursor;
if (slot === 0 || isWordChar(this.text[slot - 1])) {
cursor.gotoSlot(this.previousWordFrom(slot));
this.startMark = cursor.slot;
this.endMark = this.nextWordFrom(cursor.slot);
} else {
cursor.gotoSlot(slot);
this.startMark = slot;
this.endMark = this.nextWordFrom(slot);
}
this.drawNew();
this.changed();
};
StringMorph.prototype.selectBetweenWordsAt = function (slot) {
var cursor = this.root().cursor;
cursor.gotoSlot(this.nextWordFrom(this.previousWordFrom(slot)));
this.startMark = cursor.slot;
this.endMark = cursor.slot;
while (this.endMark < this.text.length
&& !isWordChar(this.text[this.endMark])) {
this.endMark += 1
}
this.drawNew();
this.changed();
};
StringMorph.prototype.enableSelecting = function () {
this.mouseDownLeft = function (pos) {
var crs = this.root().cursor,
already = crs ? crs.target === this : false;
this.clearSelection();
if (this.isEditable && (!this.isDraggable)) {
this.edit();
this.root().cursor.gotoPos(pos);
this.startMark = this.slotAt(pos);
this.endMark = this.startMark;
this.currentlySelecting = true;
if (!already) {this.escalateEvent('mouseDownLeft', pos); }
if (this.world().currentKey === 16) {
this.shiftClick(pos);
} else {
this.clearSelection();
if (this.isEditable && (!this.isDraggable)) {
this.edit();
this.root().cursor.gotoPos(pos);
this.startMark = this.slotAt(pos);
this.endMark = this.startMark;
this.currentlySelecting = true;
if (!already) {this.escalateEvent('mouseDownLeft', pos); }
}
}
};
this.mouseMove = function (pos) {
@ -8410,8 +8573,10 @@ TextMorph.prototype.slotPosition = function (slot) {
};
TextMorph.prototype.slotAt = function (aPoint) {
// answer the slot (index) closest to the given point
// answer the slot (index) closest to the given point taking
// in account how far from the middle of the character it is,
// so the cursor can be moved accordingly
var charX = 0,
row = 0,
col = 0,
@ -8423,11 +8588,19 @@ TextMorph.prototype.slotAt = function (aPoint) {
row += 1;
}
row = Math.max(row, 1);
while (aPoint.x - this.left() > charX) {
charX += context.measureText(this.lines[row - 1][col]).width;
col += 1;
}
return this.lineSlots[Math.max(row - 1, 0)] + col - 1;
// see where our click fell with respect to the middle of the char
if (aPoint.x - this.left() >
charX - context.measureText(this.lines[row - 1][col]).width / 2) {
return this.lineSlots[Math.max(row - 1, 0)] + col;
} else {
return this.lineSlots[Math.max(row - 1, 0)] + col - 1;
}
};
TextMorph.prototype.upFrom = function (slot) {
@ -8469,6 +8642,10 @@ TextMorph.prototype.endOfLine = function (slot) {
this.lines[this.columnRow(slot).y].length - 1;
};
TextMorph.prototype.previousWordFrom = StringMorph.prototype.previousWordFrom;
TextMorph.prototype.nextWordFrom = StringMorph.prototype.nextWordFrom;
// TextMorph editing:
TextMorph.prototype.edit = StringMorph.prototype.edit;
@ -8486,8 +8663,17 @@ TextMorph.prototype.selectAll = StringMorph.prototype.selectAll;
TextMorph.prototype.mouseDownLeft = StringMorph.prototype.mouseDownLeft;
TextMorph.prototype.shiftClick = StringMorph.prototype.shiftClick;
TextMorph.prototype.mouseClickLeft = StringMorph.prototype.mouseClickLeft;
TextMorph.prototype.mouseDoubleClick = StringMorph.prototype.mouseDoubleClick;
TextMorph.prototype.selectWordAt = StringMorph.prototype.selectWordAt;
TextMorph.prototype.selectBetweenWordsAt
= StringMorph.prototype.selectBetweenWordsAt;
TextMorph.prototype.enableSelecting = StringMorph.prototype.enableSelecting;
TextMorph.prototype.disableSelecting = StringMorph.prototype.disableSelecting;
@ -10569,7 +10755,6 @@ WorldMorph.prototype.init = function (aCanvas, fillPage) {
this.broken = [];
this.hand = new HandMorph(this);
this.keyboardReceiver = null;
this.lastEditedText = null;
this.cursor = null;
this.activeMenu = null;
this.activeHandle = null;
@ -11334,9 +11519,6 @@ WorldMorph.prototype.edit = function (aStringOrTextMorph) {
if (this.cursor) {
this.cursor.destroy();
}
if (this.lastEditedText) {
this.lastEditedText.clearSelection();
}
this.cursor = new CursorMorph(aStringOrTextMorph);
aStringOrTextMorph.parent.add(this.cursor);
this.keyboardReceiver = this.cursor;
@ -11399,10 +11581,10 @@ WorldMorph.prototype.slide = function (aStringOrTextMorph) {
WorldMorph.prototype.stopEditing = function () {
if (this.cursor) {
this.lastEditedText = this.cursor.target;
this.cursor.target.escalateEvent('reactToEdit', this.cursor.target);
this.cursor.target.clearSelection();
this.cursor.destroy();
this.cursor = null;
this.lastEditedText.escalateEvent('reactToEdit', this.lastEditedText);
}
this.keyboardReceiver = null;
if (this.virtualKeyboard) {