feat(CustomSingleLineTextField, CustomMultiLineTextArea): add word navigation, page up/down, word delete and tab keys
This commit is contained in:
@@ -183,7 +183,7 @@ public class CustomMultiLineTextArea extends Component {
|
||||
deleteSelection();
|
||||
insertChar('\n');
|
||||
}
|
||||
else if (ch == '\b') {
|
||||
else if (ch == '\b' && !e.isControlDown()) {
|
||||
backspace();
|
||||
}
|
||||
}
|
||||
@@ -196,10 +196,19 @@ public class CustomMultiLineTextArea extends Component {
|
||||
|
||||
final boolean shiftPressed = e.isShiftDown();
|
||||
final boolean ctrlPressed = e.isControlDown();
|
||||
final boolean altPressed = e.isAltDown();
|
||||
|
||||
switch (e.getKeyCode()) {
|
||||
case KeyEvent.VK_LEFT:
|
||||
if (ctrlPressed || altPressed) {
|
||||
if (CustomMultiLineTextArea.this.caretPosition > 0) {
|
||||
moveCaretToPreviousWordStart();
|
||||
if (!shiftPressed) {
|
||||
CustomMultiLineTextArea.this.selectionAnchor = CustomMultiLineTextArea.this.caretPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CustomMultiLineTextArea.this.caretPosition > 0) {
|
||||
CustomMultiLineTextArea.this.caretPosition--;
|
||||
if (!shiftPressed) {
|
||||
CustomMultiLineTextArea.this.selectionAnchor = CustomMultiLineTextArea.this.caretPosition;
|
||||
@@ -209,7 +218,15 @@ public class CustomMultiLineTextArea extends Component {
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_RIGHT:
|
||||
if (ctrlPressed || altPressed) {
|
||||
if (CustomMultiLineTextArea.this.caretPosition < CustomMultiLineTextArea.this.text.length()) {
|
||||
moveCaretToNextWordEnd();
|
||||
if (!shiftPressed) {
|
||||
CustomMultiLineTextArea.this.selectionAnchor = CustomMultiLineTextArea.this.caretPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CustomMultiLineTextArea.this.caretPosition < CustomMultiLineTextArea.this.text.length()) {
|
||||
CustomMultiLineTextArea.this.caretPosition++;
|
||||
if (!shiftPressed) {
|
||||
CustomMultiLineTextArea.this.selectionAnchor = CustomMultiLineTextArea.this.caretPosition;
|
||||
@@ -230,20 +247,50 @@ public class CustomMultiLineTextArea extends Component {
|
||||
CustomMultiLineTextArea.this.selectionAnchor = CustomMultiLineTextArea.this.caretPosition;
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_PAGE_UP:
|
||||
moveCaretByPage(true);
|
||||
if (!shiftPressed) {
|
||||
CustomMultiLineTextArea.this.selectionAnchor = CustomMultiLineTextArea.this.caretPosition;
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_PAGE_DOWN:
|
||||
moveCaretByPage(false);
|
||||
if (!shiftPressed) {
|
||||
CustomMultiLineTextArea.this.selectionAnchor = CustomMultiLineTextArea.this.caretPosition;
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_HOME:
|
||||
if (ctrlPressed) {
|
||||
CustomMultiLineTextArea.this.caretPosition = 0;
|
||||
ensureCaretVisible();
|
||||
CustomMultiLineTextArea.this.dirty = true;
|
||||
}
|
||||
else {
|
||||
moveCaretToLineStart();
|
||||
}
|
||||
if (!shiftPressed) {
|
||||
CustomMultiLineTextArea.this.selectionAnchor = CustomMultiLineTextArea.this.caretPosition;
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_END:
|
||||
if (ctrlPressed) {
|
||||
CustomMultiLineTextArea.this.caretPosition = CustomMultiLineTextArea.this.text.length();
|
||||
ensureCaretVisible();
|
||||
CustomMultiLineTextArea.this.dirty = true;
|
||||
}
|
||||
else {
|
||||
moveCaretToLineEnd();
|
||||
}
|
||||
if (!shiftPressed) {
|
||||
CustomMultiLineTextArea.this.selectionAnchor = CustomMultiLineTextArea.this.caretPosition;
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_DELETE:
|
||||
if (!CustomMultiLineTextArea.this.readonly) {
|
||||
if (ctrlPressed && !CustomMultiLineTextArea.this.readonly) {
|
||||
deleteWordForward();
|
||||
e.consume();
|
||||
}
|
||||
else if (!CustomMultiLineTextArea.this.readonly) {
|
||||
if (hasSelection()) {
|
||||
if (e.isShiftDown()) {
|
||||
cut();
|
||||
@@ -261,6 +308,18 @@ public class CustomMultiLineTextArea extends Component {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_BACK_SPACE:
|
||||
if (ctrlPressed && !CustomMultiLineTextArea.this.readonly) {
|
||||
deleteWordBackward();
|
||||
e.consume();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_TAB:
|
||||
if (!CustomMultiLineTextArea.this.readonly) {
|
||||
insertChar('\t');
|
||||
e.consume();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_C:
|
||||
if (ctrlPressed) {
|
||||
copy();
|
||||
@@ -447,6 +506,69 @@ public class CustomMultiLineTextArea extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteWordBackward() {
|
||||
if (hasSelection()) {
|
||||
deleteSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.caretPosition == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int position = this.caretPosition;
|
||||
|
||||
if (isWordCharacter(this.text.charAt(position - 1))) {
|
||||
while (position > 0 && isWordCharacter(this.text.charAt(position - 1))) {
|
||||
position--;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (position > 0 && !isWordCharacter(this.text.charAt(position - 1))) {
|
||||
position--;
|
||||
}
|
||||
}
|
||||
|
||||
this.text = this.text.substring(0, position) + this.text.substring(this.caretPosition);
|
||||
this.caretPosition = position;
|
||||
this.selectionAnchor = position;
|
||||
this.cachedWrappedLines = null;
|
||||
ensureCaretVisible();
|
||||
notifyTextChanged();
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private void deleteWordForward() {
|
||||
if (hasSelection()) {
|
||||
deleteSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
final int length = this.text.length();
|
||||
if (this.caretPosition >= length) {
|
||||
return;
|
||||
}
|
||||
|
||||
int position = this.caretPosition;
|
||||
|
||||
if (isWordCharacter(this.text.charAt(position))) {
|
||||
while (position < length && isWordCharacter(this.text.charAt(position))) {
|
||||
position++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (position < length && !isWordCharacter(this.text.charAt(position))) {
|
||||
position++;
|
||||
}
|
||||
}
|
||||
|
||||
this.text = this.text.substring(0, this.caretPosition) + this.text.substring(position);
|
||||
this.cachedWrappedLines = null;
|
||||
ensureCaretVisible();
|
||||
notifyTextChanged();
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private void notifyTextChanged() {
|
||||
this.recalculateScrollOffset();
|
||||
if (this.onTextChanged != null) {
|
||||
@@ -491,6 +613,62 @@ public class CustomMultiLineTextArea extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
private void moveCaretByPage(final boolean up) {
|
||||
final List<String> wrappedLines = getWrappedLines();
|
||||
final int lineHeight = getLineHeight();
|
||||
final int linesPerPage = Math.max(1, getVisibleHeight() / lineHeight);
|
||||
|
||||
final int currentWrappedLine = getWrappedLineIndexAtCaret();
|
||||
final int lineStart = getCharOffsetAtWrappedLineStart(currentWrappedLine);
|
||||
final int offsetInLine = this.caretPosition - lineStart;
|
||||
|
||||
final int targetWrappedLine = Math.max(0, Math.min(wrappedLines.size() - 1, currentWrappedLine + (up ? -linesPerPage : linesPerPage)));
|
||||
|
||||
if (targetWrappedLine != currentWrappedLine) {
|
||||
final int targetLineStart = getCharOffsetAtWrappedLineStart(targetWrappedLine);
|
||||
final int targetLineLength = wrappedLines.get(targetWrappedLine).length();
|
||||
this.caretPosition = targetLineStart + Math.min(offsetInLine, targetLineLength);
|
||||
ensureCaretVisible();
|
||||
this.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void moveCaretToPreviousWordStart() {
|
||||
int position = this.caretPosition;
|
||||
|
||||
while (position > 0 && !isWordCharacter(this.text.charAt(position - 1))) {
|
||||
position--;
|
||||
}
|
||||
|
||||
while (position > 0 && isWordCharacter(this.text.charAt(position - 1))) {
|
||||
position--;
|
||||
}
|
||||
|
||||
this.caretPosition = position;
|
||||
ensureCaretVisible();
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private void moveCaretToNextWordEnd() {
|
||||
int position = this.caretPosition;
|
||||
final int length = this.text.length();
|
||||
|
||||
if (position < length && isWordCharacter(this.text.charAt(position))) {
|
||||
while (position < length && isWordCharacter(this.text.charAt(position))) {
|
||||
position++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (position < length && !isWordCharacter(this.text.charAt(position))) {
|
||||
position++;
|
||||
}
|
||||
}
|
||||
|
||||
this.caretPosition = position;
|
||||
ensureCaretVisible();
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private void moveCaretToLineStart() {
|
||||
final int currentWrappedLine = getWrappedLineIndexAtCaret();
|
||||
this.caretPosition = getCharOffsetAtWrappedLineStart(currentWrappedLine);
|
||||
|
||||
@@ -142,7 +142,7 @@ public class CustomSingleLineTextField extends Component {
|
||||
deleteSelection();
|
||||
insertChar(ch);
|
||||
}
|
||||
else if (ch == '\b') {
|
||||
else if (ch == '\b' && !e.isControlDown()) {
|
||||
backspace();
|
||||
}
|
||||
}
|
||||
@@ -155,6 +155,7 @@ public class CustomSingleLineTextField extends Component {
|
||||
|
||||
final boolean shiftPressed = e.isShiftDown();
|
||||
final boolean ctrlPressed = e.isControlDown();
|
||||
final boolean altPressed = e.isAltDown();
|
||||
|
||||
switch (e.getKeyCode()) {
|
||||
case KeyEvent.VK_ENTER:
|
||||
@@ -165,7 +166,15 @@ public class CustomSingleLineTextField extends Component {
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_LEFT:
|
||||
if (ctrlPressed || altPressed) {
|
||||
if (CustomSingleLineTextField.this.caretPosition > 0) {
|
||||
moveCaretToPreviousWordStart();
|
||||
if (!shiftPressed) {
|
||||
CustomSingleLineTextField.this.selectionAnchor = CustomSingleLineTextField.this.caretPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CustomSingleLineTextField.this.caretPosition > 0) {
|
||||
CustomSingleLineTextField.this.caretPosition--;
|
||||
if (!shiftPressed) {
|
||||
CustomSingleLineTextField.this.selectionAnchor = CustomSingleLineTextField.this.caretPosition;
|
||||
@@ -175,7 +184,15 @@ public class CustomSingleLineTextField extends Component {
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_RIGHT:
|
||||
if (ctrlPressed || altPressed) {
|
||||
if (CustomSingleLineTextField.this.caretPosition < CustomSingleLineTextField.this.text.length()) {
|
||||
moveCaretToNextWordEnd();
|
||||
if (!shiftPressed) {
|
||||
CustomSingleLineTextField.this.selectionAnchor = CustomSingleLineTextField.this.caretPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CustomSingleLineTextField.this.caretPosition < CustomSingleLineTextField.this.text.length()) {
|
||||
CustomSingleLineTextField.this.caretPosition++;
|
||||
if (!shiftPressed) {
|
||||
CustomSingleLineTextField.this.selectionAnchor = CustomSingleLineTextField.this.caretPosition;
|
||||
@@ -201,7 +218,11 @@ public class CustomSingleLineTextField extends Component {
|
||||
CustomSingleLineTextField.this.dirty = true;
|
||||
break;
|
||||
case KeyEvent.VK_DELETE:
|
||||
if (!CustomSingleLineTextField.this.readonly) {
|
||||
if (ctrlPressed && !CustomSingleLineTextField.this.readonly) {
|
||||
deleteWordForward();
|
||||
e.consume();
|
||||
}
|
||||
else if (!CustomSingleLineTextField.this.readonly) {
|
||||
if (hasSelection()) {
|
||||
if (e.isShiftDown()) {
|
||||
cut();
|
||||
@@ -218,6 +239,12 @@ public class CustomSingleLineTextField extends Component {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_BACK_SPACE:
|
||||
if (ctrlPressed && !CustomSingleLineTextField.this.readonly) {
|
||||
deleteWordBackward();
|
||||
e.consume();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_C:
|
||||
if (ctrlPressed) {
|
||||
copy();
|
||||
@@ -364,6 +391,67 @@ public class CustomSingleLineTextField extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteWordBackward() {
|
||||
if (hasSelection()) {
|
||||
deleteSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.caretPosition == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int position = this.caretPosition;
|
||||
|
||||
if (isWordCharacter(this.text.charAt(position - 1))) {
|
||||
while (position > 0 && isWordCharacter(this.text.charAt(position - 1))) {
|
||||
position--;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (position > 0 && !isWordCharacter(this.text.charAt(position - 1))) {
|
||||
position--;
|
||||
}
|
||||
}
|
||||
|
||||
this.text = this.text.substring(0, position) + this.text.substring(this.caretPosition);
|
||||
this.caretPosition = position;
|
||||
this.selectionAnchor = position;
|
||||
ensureCaretVisible();
|
||||
notifyTextChanged();
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private void deleteWordForward() {
|
||||
if (hasSelection()) {
|
||||
deleteSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
final int length = this.text.length();
|
||||
if (this.caretPosition >= length) {
|
||||
return;
|
||||
}
|
||||
|
||||
int position = this.caretPosition;
|
||||
|
||||
if (isWordCharacter(this.text.charAt(position))) {
|
||||
while (position < length && isWordCharacter(this.text.charAt(position))) {
|
||||
position++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (position < length && !isWordCharacter(this.text.charAt(position))) {
|
||||
position++;
|
||||
}
|
||||
}
|
||||
|
||||
this.text = this.text.substring(0, this.caretPosition) + this.text.substring(position);
|
||||
ensureCaretVisible();
|
||||
notifyTextChanged();
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private void notifyTextChanged() {
|
||||
if (this.onTextChanged != null) {
|
||||
this.onTextChanged.accept(this.text);
|
||||
@@ -529,6 +617,42 @@ public class CustomSingleLineTextField extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
private void moveCaretToPreviousWordStart() {
|
||||
int position = this.caretPosition;
|
||||
|
||||
while (position > 0 && !isWordCharacter(this.text.charAt(position - 1))) {
|
||||
position--;
|
||||
}
|
||||
|
||||
while (position > 0 && isWordCharacter(this.text.charAt(position - 1))) {
|
||||
position--;
|
||||
}
|
||||
|
||||
this.caretPosition = position;
|
||||
ensureCaretVisible();
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private void moveCaretToNextWordEnd() {
|
||||
int position = this.caretPosition;
|
||||
final int length = this.text.length();
|
||||
|
||||
if (position < length && isWordCharacter(this.text.charAt(position))) {
|
||||
while (position < length && isWordCharacter(this.text.charAt(position))) {
|
||||
position++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (position < length && !isWordCharacter(this.text.charAt(position))) {
|
||||
position++;
|
||||
}
|
||||
}
|
||||
|
||||
this.caretPosition = position;
|
||||
ensureCaretVisible();
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private boolean isWordCharacter(final char ch) {
|
||||
return Character.isLetterOrDigit(ch) || ch == '_';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user