2 * Sixth 3D engine. Author: Svjatoslav Agejenko.
3 * This project is released under Creative Commons Zero (CC0) license.
5 package eu.svjatoslav.sixth.e3d.gui.textEditorComponent;
7 import eu.svjatoslav.sixth.e3d.geometry.Point2D;
8 import eu.svjatoslav.sixth.e3d.gui.GuiComponent;
9 import eu.svjatoslav.sixth.e3d.gui.TextPointer;
10 import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
11 import eu.svjatoslav.sixth.e3d.gui.humaninput.KeyboardHelper;
12 import eu.svjatoslav.sixth.e3d.math.Transform;
13 import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.textcanvas.TextCanvas;
16 import java.awt.datatransfer.*;
17 import java.awt.event.KeyEvent;
18 import java.io.IOException;
19 import java.util.HashSet;
22 public class TextEditComponent extends GuiComponent implements ClipboardOwner {
24 private static final long serialVersionUID = -7118833957783600630L;
27 * Text rows that need to be repainted.
29 private final Set<Integer> dirtyRows = new HashSet<>();
32 private final TextCanvas textCanvas;
33 public int scrolledCharacters = 0, scrolledLines = 0;
34 public boolean selecting = false;
37 * Selection start and end pointers.
39 public TextPointer selectionStart = new TextPointer(0, 0);
40 public TextPointer selectionEnd = new TextPointer(0, 0);
43 public TextPointer cursorLocation = new TextPointer(0, 0);
44 Page page = new Page();
45 LookAndFeel lookAndFeel;
48 * If true, the page will be repainted on the next update.
50 boolean repaintPage = false;
52 public TextEditComponent(final Transform transform,
53 final ViewPanel viewPanel,
54 final Point2D sizeInWorldCoordinates,
55 LookAndFeel lookAndFeel) {
56 super(transform, viewPanel, sizeInWorldCoordinates.to3D());
58 this.lookAndFeel = lookAndFeel;
59 final int columns = (int) (sizeInWorldCoordinates.x / TextCanvas.FONT_CHAR_WIDTH);
60 final int rows = (int) (sizeInWorldCoordinates.y / TextCanvas.FONT_CHAR_HEIGHT);
62 textCanvas = new TextCanvas(
64 new TextPointer(rows, columns),
65 lookAndFeel.foreground, lookAndFeel.background);
67 textCanvas.setMouseInteractionController(this);
73 private void checkCursorBoundaries() {
74 if (cursorLocation.column < 0)
75 cursorLocation.column = 0;
76 if (cursorLocation.row < 0)
77 cursorLocation.row = 0;
79 // ensure chat cursor stays within vertical editor boundaries by
81 if ((cursorLocation.row - scrolledLines) < 0)
82 scroll(0, cursorLocation.row - scrolledLines);
84 if ((((cursorLocation.row - scrolledLines) + 1)) > textCanvas.getSize().row)
86 ((((((cursorLocation.row - scrolledLines) + 1) - textCanvas
89 // ensure chat cursor stays within horizontal editor boundaries by
90 // horizontal scrolling
91 if ((cursorLocation.column - scrolledCharacters) < 0)
92 scroll(cursorLocation.column - scrolledCharacters, 0);
94 if ((((cursorLocation.column - scrolledCharacters) + 1)) > textCanvas
96 scroll((((((cursorLocation.column - scrolledCharacters) + 1) - textCanvas
97 .getSize().column))), 0);
101 * Clear text selection.
103 public void clearSelection() {
104 selectionEnd = new TextPointer(selectionStart);
109 * Copies selected text to the clipboard.
111 public void copyToClipboard() {
112 if (selectionStart.compareTo(selectionEnd) == 0)
114 // System.out.println("Copy action.");
115 final StringBuilder msg = new StringBuilder();
117 ensureSelectionOrder();
119 for (int row = selectionStart.row; row <= selectionEnd.row; row++) {
120 final TextLine textLine = page.getLine(row);
122 if (row == selectionStart.row) {
123 if (row == selectionEnd.row)
124 msg.append(textLine.getSubString(selectionStart.column,
125 selectionEnd.column + 1));
127 msg.append(textLine.getSubString(selectionStart.column,
128 textLine.getLength()));
131 if (row == selectionEnd.row)
133 .getSubString(0, selectionEnd.column + 1));
135 msg.append(textLine.toString());
139 setClipboardContents(msg.toString());
142 public void cutToClipboard() {
148 public void deleteSelection() {
149 ensureSelectionOrder();
152 for (int line = selectionStart.row; line <= selectionEnd.row; line++) {
153 final TextLine currentLine = page.getLine(line - ym);
155 if (line == selectionStart.row) {
156 if (line == selectionEnd.row)
158 currentLine.cutSubString(selectionStart.column,
159 selectionEnd.column);
160 else if (selectionStart.column == 0) {
161 page.removeLine(line - ym);
164 currentLine.cutSubString(selectionStart.column,
165 currentLine.getLength() + 1);
166 } else if (line == selectionEnd.row)
167 currentLine.cutSubString(0, selectionEnd.column);
169 page.removeLine(line - ym);
175 cursorLocation = new TextPointer(selectionStart);
179 * Ensures that {@link #selectionStart} is smaller than
180 * {@link #selectionEnd}.
182 public void ensureSelectionOrder() {
183 if (selectionStart.compareTo(selectionEnd) > 0) {
184 final TextPointer temp = selectionEnd;
185 selectionEnd = selectionStart;
186 selectionStart = temp;
190 public String getClipboardContents() {
192 final Clipboard clipboard = Toolkit.getDefaultToolkit()
193 .getSystemClipboard();
194 // odd: the Object param of getContents is not currently used
195 final Transferable contents = clipboard.getContents(null);
196 final boolean hasTransferableText = (contents != null)
197 && contents.isDataFlavorSupported(DataFlavor.stringFlavor);
198 if (hasTransferableText)
200 result = (String) contents
201 .getTransferData(DataFlavor.stringFlavor);
202 } catch (final UnsupportedFlavorException | IOException ex) {
203 // highly unlikely since we are using a standard DataFlavor
204 System.out.println(ex);
206 // System.out.println(result);
211 * Place string into system clipboard so that it can be pasted into other
214 public void setClipboardContents(final String contents) {
215 final StringSelection stringSelection = new StringSelection(contents);
216 final Clipboard clipboard = Toolkit.getDefaultToolkit()
217 .getSystemClipboard();
218 clipboard.setContents(stringSelection, stringSelection);
221 public void goToLine(final int Line) {
222 // markNavigationLocation(Line);
223 scrolledLines = Line + 1;
224 cursorLocation.row = Line + 1;
225 cursorLocation.column = 0;
229 public void insertText(final String txt) {
233 for (final char c : txt.toCharArray()) {
235 if (c == KeyboardHelper.DEL) {
240 if (c == KeyboardHelper.ENTER) {
245 if (c == KeyboardHelper.BACKSPACE) {
251 if (KeyboardHelper.isText(c)) {
252 page.insertCharacter(cursorLocation.row, cursorLocation.column,
254 cursorLocation.column++;
263 public boolean keyPressed(final KeyEvent event, final ViewPanel viewPanel) {
264 super.keyPressed(event, viewPanel);
266 processKeyEvent(event);
270 checkCursorBoundaries();
277 * Empty implementation of the ClipboardOwner interface.
280 public void lostOwnership(final Clipboard aClipboard,
281 final Transferable aContents) {
285 public void markRowDirty() {
286 dirtyRows.add(cursorLocation.row);
289 public void pasteFromClipboard() {
290 insertText(getClipboardContents());
293 private void processBackspace() {
294 if (selectionStart.compareTo(selectionEnd) == 0) {
295 // erase single character
296 if (cursorLocation.column > 0) {
297 cursorLocation.column--;
298 page.removeCharacter(cursorLocation.row, cursorLocation.column);
299 // System.out.println(lines.get(currentCursor.line).toString());
300 } else if (cursorLocation.row > 0) {
301 cursorLocation.row--;
302 final int currentLineLength = page
303 .getLineLength(cursorLocation.row);
304 cursorLocation.column = currentLineLength;
305 page.getLine(cursorLocation.row)
306 .insertTextLine(currentLineLength,
307 page.getLine(cursorLocation.row + 1));
308 page.removeLine(cursorLocation.row + 1);
312 // dedent multiple lines
313 ensureSelectionOrder();
314 // scan if enough space exists
315 for (int y = selectionStart.row; y < selectionEnd.row; y++)
316 if (page.getLine(y).getIdent() < 4)
319 for (int y = selectionStart.row; y < selectionEnd.row; y++)
320 page.getLine(y).cutFromBeginning(4);
326 private void processCtrlCombinations(final int keyCode) {
328 if ((char) keyCode == 'A') { // CTRL + A -- select all
329 final int lastLineIndex = page.getLinesCount() - 1;
330 selectionStart = new TextPointer(0, 0);
331 selectionEnd = new TextPointer(lastLineIndex,
332 page.getLineLength(lastLineIndex));
337 if ((char) keyCode == 'X')
341 if ((char) keyCode == 'C')
345 if ((char) keyCode == 'V')
346 pasteFromClipboard();
348 if (keyCode == 39) { // RIGHT
349 // skip to the beginning of the next word
351 for (int x = cursorLocation.column; x < (page
352 .getLineLength(cursorLocation.row) - 1); x++)
353 if ((page.getChar(cursorLocation.row, x) == ' ')
354 && (page.getChar(cursorLocation.row, x + 1) != ' ')) {
355 // beginning of the next word is found
356 cursorLocation.column = x + 1;
360 cursorLocation.column = page.getLineLength(cursorLocation.row);
364 if (keyCode == 37) { // Left
366 // skip to the beginning of the previous word
367 for (int x = cursorLocation.column - 2; x >= 0; x--)
368 if ((page.getChar(cursorLocation.row, x) == ' ')
369 & (page.getChar(cursorLocation.row, x + 1) != ' ')) {
370 cursorLocation.column = x + 1;
374 cursorLocation.column = 0;
378 public void processDel() {
379 if (selectionStart.compareTo(selectionEnd) == 0) {
380 // is there still some text right to the cursor ?
381 if (cursorLocation.column < page.getLineLength(cursorLocation.row))
382 page.removeCharacter(cursorLocation.row, cursorLocation.column);
384 page.getLine(cursorLocation.row).insertTextLine(
385 cursorLocation.column,
386 page.getLine(cursorLocation.row + 1));
387 page.removeLine(cursorLocation.row + 1);
396 private void processEnter() {
397 final TextLine currentLine = page.getLine(cursorLocation.row);
398 // move everything right to the cursor into new line
399 final TextLine newLine = currentLine.getSubLine(cursorLocation.column,
400 currentLine.getLength());
401 page.insertLine(cursorLocation.row + 1, newLine);
403 // trim existing line
404 page.getLine(cursorLocation.row).cutUntilEnd(cursorLocation.column);
407 cursorLocation.row++;
408 cursorLocation.column = 0;
411 private void processKeyEvent(final KeyEvent event) {
412 final int modifiers = event.getModifiersEx();
413 final int keyCode = event.getKeyCode();
414 final char keyChar = event.getKeyChar();
416 // System.out.println("Keycode:" + keyCode s+ ", keychar:" + keyChar);
418 if (KeyboardHelper.isAltPressed(modifiers))
421 if (KeyboardHelper.isCtrlPressed(modifiers)) {
422 processCtrlCombinations(keyCode);
426 if (keyCode == KeyboardHelper.TAB) {
427 processTab(modifiers);
433 if (KeyboardHelper.isText(keyCode)) {
434 insertText(String.valueOf(keyChar));
438 if (KeyboardHelper.isShiftPressed(modifiers)) {
440 attemptSelectionStart:{
442 if (keyChar == 65535)
444 break attemptSelectionStart;
445 if (((keyChar >= 32) & (keyChar <= 128)) | (keyChar == 10)
446 | (keyChar == 8) | (keyChar == 9))
447 break attemptSelectionStart;
449 selectionStart = new TextPointer(cursorLocation);
450 selectionEnd = selectionStart;
457 if (keyCode == KeyboardHelper.HOME) {
458 cursorLocation.column = 0;
461 if (keyCode == KeyboardHelper.END) {
462 cursorLocation.column = page.getLineLength(cursorLocation.row);
466 // process cursor keys
467 if (keyCode == KeyboardHelper.DOWN) {
469 cursorLocation.row++;
473 if (keyCode == KeyboardHelper.UP) {
475 cursorLocation.row--;
479 if (keyCode == KeyboardHelper.RIGHT) {
480 cursorLocation.column++;
484 if (keyCode == KeyboardHelper.LEFT) {
485 cursorLocation.column--;
489 if (keyCode == KeyboardHelper.PGDOWN) {
490 cursorLocation.row += textCanvas.getSize().row;
495 if (keyCode == KeyboardHelper.PGUP) {
496 cursorLocation.row -= textCanvas.getSize().row;
502 private void processTab(final int modifiers) {
503 if (KeyboardHelper.isShiftPressed(modifiers)) {
504 if (selectionStart.compareTo(selectionEnd) != 0) {
505 // dedent multiple lines
506 ensureSelectionOrder();
510 // check that indentation is possible
511 for (int y = selectionStart.row; y < selectionEnd.row; y++) {
512 final TextLine textLine = page.getLine(y);
514 if (!textLine.isEmpty())
515 if (textLine.getIdent() < 4)
516 break identSelection;
519 for (int y = selectionStart.row; y < selectionEnd.row; y++)
520 page.getLine(y).cutFromBeginning(4);
523 // dedent current line
524 final TextLine textLine = page.getLine(cursorLocation.row);
526 if (cursorLocation.column >= 4)
527 if (textLine.isEmpty())
528 cursorLocation.column -= 4;
529 else if (textLine.getIdent() >= 4) {
530 cursorLocation.column -= 4;
531 textLine.cutFromBeginning(4);
538 } else if (selectionStart.compareTo(selectionEnd) != 0) {
539 // ident multiple lines
540 ensureSelectionOrder();
541 for (int y = selectionStart.row; y < selectionEnd.row; y++)
542 page.getLine(y).addIdent(4);
548 public void repaintPage() {
550 final int columnCount = textCanvas.getSize().column + 2;
551 final int rowCount = textCanvas.getSize().row + 2;
553 for (int row = 0; row < rowCount; row++)
554 for (int column = 0; column < columnCount; column++) {
555 final boolean isTabMargin = ((column + scrolledCharacters) % 4) == 0;
557 if ((column == (cursorLocation.column - scrolledCharacters))
558 & (row == (cursorLocation.row - scrolledLines))) {
560 textCanvas.setBackgroundColor(lookAndFeel.cursorBackground);
561 textCanvas.setForegroundColor(lookAndFeel.cursorForeground);
562 } else if (new TextPointer(row + scrolledLines, column).isBetween(
563 selectionStart, selectionEnd)) {
565 textCanvas.setBackgroundColor(lookAndFeel.selectionBackground);
566 textCanvas.setForegroundColor(lookAndFeel.selectionForeground);
569 textCanvas.setBackgroundColor(lookAndFeel.background);
570 textCanvas.setForegroundColor(lookAndFeel.foreground);
574 .setBackgroundColor(lookAndFeel.tabStopBackground);
578 final char charUnderCursor = page.getChar(row + scrolledLines,
579 column + scrolledCharacters);
581 textCanvas.putChar(row, column, charUnderCursor);
586 public void repaintRow(final int rowNumber) {
587 // TODO: Optimize this. No need to repaint entire page.
591 private void repaintWhatNeeded() {
598 dirtyRows.forEach(this::repaintRow);
603 * Scroll full page to given amount of lines or charancters.
605 public void scroll(final int charactersToScroll, final int linesToScroll) {
606 scrolledLines += linesToScroll;
607 scrolledCharacters += charactersToScroll;
609 if (scrolledLines < 0)
612 if (scrolledCharacters < 0)
613 scrolledCharacters = 0;
618 public void setText(final String text) {
619 // System.out.println("Set text:" + text);
620 cursorLocation = new TextPointer(0, 0);
621 scrolledCharacters = 0;
623 selectionStart = new TextPointer(0, 0);
624 selectionEnd = new TextPointer(0, 0);