From c03d17c51562ea9f48ca0442065ace4db4378e80 Mon Sep 17 00:00:00 2001 From: George Bateman Date: Tue, 23 Feb 2016 19:17:08 +0000 Subject: [PATCH] Implement rectangular selections --- .../processing/app/syntax/InputHandler.java | 49 ++++++++++++++--- .../processing/app/syntax/JEditTextArea.java | 52 +++++++++++++++++-- 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/app/src/processing/app/syntax/InputHandler.java b/app/src/processing/app/syntax/InputHandler.java index 5cde91f2a9..fed88e5861 100644 --- a/app/src/processing/app/syntax/InputHandler.java +++ b/app/src/processing/app/syntax/InputHandler.java @@ -424,9 +424,7 @@ public void actionPerformed(ActionEvent evt) { return; } - if (textArea.getSelectionStart() != textArea.getSelectionStop()) { - textArea.setSelectedText(""); - } else { + if (!selectionBackspaceDelete(textArea, false)) { int caret = textArea.getCaretPosition(); if (caret == 0) { textArea.getToolkit().beep(); @@ -498,9 +496,7 @@ public void actionPerformed(ActionEvent evt) { return; } - if (textArea.getSelectionStart() != textArea.getSelectionStop()) { - textArea.setSelectedText(""); - } else { + if (!selectionBackspaceDelete(textArea, true)) { int caret = textArea.getCaretPosition(); if (caret == textArea.getDocumentLength()) { textArea.getToolkit().beep(); @@ -1183,4 +1179,45 @@ public static int findWordEnd(String line, int pos, String noWordSep) { } return wordEnd; } + + /** + * Called by the backspace and delete listeners to detect and handle + * selections, including rectangular ones. + * @return true iff this was a selection. + */ + static public boolean selectionBackspaceDelete(JEditTextArea textArea, + boolean delete) { + final int selectionStart = textArea.getSelectionStart(); + final int selectionStop = textArea.getSelectionStop(); + if (selectionStart == selectionStop) return false; + + if (!textArea.rectSelect) { + textArea.setSelectedText(""); + return true; + } + + Element map = textArea.document.getDefaultRootElement(); + Element startElement = map.getElement(textArea.getSelectionStartLine()); + // Distance from start of line to the selection. + int start = selectionStart - startElement.getStartOffset(); + int end = selectionStop - + map.getElement(textArea.getSelectionStopLine()).getStartOffset(); + + if (start != end) { + // Start and end of selection are not vertically aligned. + // Therefore there is a real rectangle selected, which we delete. + textArea.setSelectedText(""); + } else if (delete && selectionStart != startElement.getEndOffset() - 1) { + // Widen the selection forwards one space, then clear it. + textArea.setSelectionStart(selectionStart + 1); + textArea.setSelectedText(""); + } else if (!delete && start != 0) { + // Widen the selection backwards one space, then clear it. + textArea.setSelectionStart(selectionStart - 1); + textArea.setSelectedText(""); + } else { + textArea.getToolkit().beep(); + } + return true; + } } diff --git a/app/src/processing/app/syntax/JEditTextArea.java b/app/src/processing/app/syntax/JEditTextArea.java index 5fb7392db5..93b242edba 100644 --- a/app/src/processing/app/syntax/JEditTextArea.java +++ b/app/src/processing/app/syntax/JEditTextArea.java @@ -1455,6 +1455,10 @@ public void setSelectedText(String selectedText) { start = tmp; } + // Text with new lines gets pasted in with one line of source text + // to each line of the destination. + final boolean putBlockIntoBlock = selectedText != null + && selectedText.contains("\n"); int lastNewline = 0; int currNewline = 0; @@ -1466,28 +1470,45 @@ public void setSelectedText(String selectedText) { document.remove(rectStart,Math.min(lineEnd - rectStart, end - start)); - if (selectedText != null) { + if (selectedText == null) continue; + + if (putBlockIntoBlock) { currNewline = selectedText.indexOf('\n', lastNewline); if (currNewline == -1) { currNewline = selectedText.length(); } document.insertString(rectStart, selectedText.substring(lastNewline, currNewline), null); lastNewline = Math.min(selectedText.length(), currNewline + 1); + } else { + if (overwrite && end == start && selectedText.length() == 1) { + int newLineEnd = lineElement.getEndOffset() - 1; + document.remove(rectStart, Math.min(lineEnd - rectStart, selectedText.length())); + } + document.insertString(rectStart, selectedText, null); } } - if (selectedText != null && - currNewline != selectedText.length()) { + if (putBlockIntoBlock && currNewline != selectedText.length()) { int offset = map.getElement(selectionEndLine).getEndOffset() - 1; document.insertString(offset, "\n", null); document.insertString(offset + 1,selectedText.substring(currNewline + 1), null); } + + if (putBlockIntoBlock) { + // Since what we've pasted in is not guaranteed to be perfectly + // rectangular, the right-hand edge of it can't be selected. + setCaretPosition(selectionEnd); + } else { + select(selectionStart + selectedText.length(), selectionEnd); + } } else { document.remove(selectionStart, selectionEnd - selectionStart); if (selectedText != null) { document.insertString(selectionStart, selectedText,null); } + setCaretPosition(selectionEnd); } + } catch(BadLocationException bl) { bl.printStackTrace(); throw new InternalError("Cannot replace selection"); @@ -1496,7 +1517,6 @@ public void setSelectedText(String selectedText) { // No matter what happens... stops us from leaving document in a bad state document.endCompoundEdit(); } - setCaretPosition(selectionEnd); } @@ -1564,6 +1584,7 @@ public final void setMagicCaretPosition(int magicCaret) { public void overwriteSetSelectedText(String str) { // Don't overstrike if there is a selection + // Unless it's a rectangular one, which is handled in setSelectedText(). if(!overwrite || selectionStart != selectionEnd) { setSelectedText(str); @@ -1989,7 +2010,28 @@ public void processKeyEvent(KeyEvent event) { inputHandler.keyTyped(event); break; case KeyEvent.KEY_PRESSED: - inputHandler.keyPressed(event); + // Pressing enter with a rectangular selection handled here to avoid + // making it input handler-specific. + char c = event.getKeyChar(); + if (!rectSelect || (c != '\n' && c != '\r') || + (event.getModifiers() & InputEvent.META_MASK) != 0) { + inputHandler.keyPressed(event); + return; + } + document.beginCompoundEdit(); + setSelectedText(""); + Element map = document.getDefaultRootElement(); + final int indent = selectionStart - + map.getElement(selectionStartLine).getStartOffset(); + final int startLine = selectionStartLine; + final int endLine = 2*selectionEndLine - selectionStartLine; + for (int i = startLine; i <= endLine; i += 2) { + int caret = Math.min(map.getElement(i).getStartOffset() + indent, + map.getElement(i).getEndOffset()); + select(caret, caret); + inputHandler.keyPressed(event); + } + document.endCompoundEdit(); break; case KeyEvent.KEY_RELEASED: inputHandler.keyReleased(event);