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.Color;
14 import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.textcanvas.TextCanvas;
17 import java.awt.datatransfer.*;
18 import java.awt.event.KeyEvent;
19 import java.io.IOException;
20 import java.util.HashSet;
23 public class TextEditComponent extends GuiComponent implements ClipboardOwner {
25 private static final long serialVersionUID = -7118833957783600630L;
28 * Text rows that need to be repainted.
30 private final Set<Integer> dirtyRows = new HashSet<>();
32 private final TextCanvas textCanvas;
33 public int scrolledCharacters = 0, scrolledLines = 0;
34 public boolean selecting = false;
35 public TextPointer selectionStart = new TextPointer(0, 0);
36 public TextPointer selectionEnd = new TextPointer(0, 0);
37 public TextPointer cursorLocation = new TextPointer(0, 0);
38 Page page = new Page();
39 ColorConfig colorConfig = new ColorConfig();
40 boolean repaintPage = false;
42 public TextEditComponent(final Transform transform,
43 final ViewPanel viewPanel, final Point2D sizeInWorldCoordinates) {
44 super(transform, viewPanel, sizeInWorldCoordinates.to3D());
46 // initialize visual panel
48 final int columns = (int) (sizeInWorldCoordinates.x / TextCanvas.FONT_CHAR_WIDTH);
49 final int rows = (int) (sizeInWorldCoordinates.y / TextCanvas.FONT_CHAR_HEIGHT);
51 textCanvas = new TextCanvas(new Transform(), new TextPointer(rows,
52 columns), Color.WHITE, colorConfig.normalBack);
54 textCanvas.setMouseInteractionController(this);
60 private void checkCursorBoundaries() {
61 if (cursorLocation.column < 0)
62 cursorLocation.column = 0;
63 if (cursorLocation.row < 0)
64 cursorLocation.row = 0;
66 // ensure chat cursor stays within vertical editor boundaries by
68 if ((cursorLocation.row - scrolledLines) < 0)
69 scroll(0, cursorLocation.row - scrolledLines);
71 if ((((cursorLocation.row - scrolledLines) + 1)) > textCanvas.getSize().row)
73 ((((((cursorLocation.row - scrolledLines) + 1) - textCanvas
76 // ensure chat cursor stays within horizontal editor boundaries by
77 // horizontal scrolling
78 if ((cursorLocation.column - scrolledCharacters) < 0)
79 scroll(cursorLocation.column - scrolledCharacters, 0);
81 if ((((cursorLocation.column - scrolledCharacters) + 1)) > textCanvas
83 scroll((((((cursorLocation.column - scrolledCharacters) + 1) - textCanvas
84 .getSize().column))), 0);
88 * Clear text selection.
90 public void clearSelection() {
91 selectionEnd = new TextPointer(selectionStart);
96 * Copies selected text to the clipboard.
98 public void copyToClipboard() {
99 if (selectionStart.compareTo(selectionEnd) == 0)
101 // System.out.println("Copy action.");
102 final StringBuilder msg = new StringBuilder();
104 ensureSelectionOrder();
106 for (int row = selectionStart.row; row <= selectionEnd.row; row++) {
107 final TextLine textLine = page.getLine(row);
109 if (row == selectionStart.row) {
110 if (row == selectionEnd.row)
111 msg.append(textLine.getSubString(selectionStart.column,
112 selectionEnd.column + 1));
114 msg.append(textLine.getSubString(selectionStart.column,
115 textLine.getLength()));
118 if (row == selectionEnd.row)
120 .getSubString(0, selectionEnd.column + 1));
122 msg.append(textLine.toString());
126 setClipboardContents(msg.toString());
129 public void cutToClipboard() {
135 public void deleteSelection() {
136 ensureSelectionOrder();
139 for (int line = selectionStart.row; line <= selectionEnd.row; line++) {
140 final TextLine currentLine = page.getLine(line - ym);
142 if (line == selectionStart.row) {
143 if (line == selectionEnd.row)
145 currentLine.cutSubString(selectionStart.column,
146 selectionEnd.column);
147 else if (selectionStart.column == 0) {
148 page.removeLine(line - ym);
151 currentLine.cutSubString(selectionStart.column,
152 currentLine.getLength() + 1);
153 } else if (line == selectionEnd.row)
154 currentLine.cutSubString(0, selectionEnd.column);
156 page.removeLine(line - ym);
162 cursorLocation = new TextPointer(selectionStart);
166 * Ensures that {@link #selectionStart} is smaller than
167 * {@link #selectionEnd}.
169 public void ensureSelectionOrder() {
170 if (selectionStart.compareTo(selectionEnd) > 0) {
171 final TextPointer temp = selectionEnd;
172 selectionEnd = selectionStart;
173 selectionStart = temp;
177 public String getClipboardContents() {
179 final Clipboard clipboard = Toolkit.getDefaultToolkit()
180 .getSystemClipboard();
181 // odd: the Object param of getContents is not currently used
182 final Transferable contents = clipboard.getContents(null);
183 final boolean hasTransferableText = (contents != null)
184 && contents.isDataFlavorSupported(DataFlavor.stringFlavor);
185 if (hasTransferableText)
187 result = (String) contents
188 .getTransferData(DataFlavor.stringFlavor);
189 } catch (final UnsupportedFlavorException | IOException ex) {
190 // highly unlikely since we are using a standard DataFlavor
191 System.out.println(ex);
193 // System.out.println(result);
198 * Place string into system clipboard so that it can be pasted into other
201 public void setClipboardContents(final String contents) {
202 final StringSelection stringSelection = new StringSelection(contents);
203 final Clipboard clipboard = Toolkit.getDefaultToolkit()
204 .getSystemClipboard();
205 clipboard.setContents(stringSelection, stringSelection);
208 public void goToLine(final int Line) {
209 // markNavigationLocation(Line);
210 scrolledLines = Line + 1;
211 cursorLocation.row = Line + 1;
212 cursorLocation.column = 0;
216 public void insertText(final String txt) {
220 for (final char c : txt.toCharArray()) {
222 if (c == KeyboardHelper.DEL) {
227 if (c == KeyboardHelper.ENTER) {
232 if (c == KeyboardHelper.BACKSPACE) {
238 if (KeyboardHelper.isText(c)) {
239 page.insertCharacter(cursorLocation.row, cursorLocation.column,
241 cursorLocation.column++;
250 public boolean keyPressed(final KeyEvent event, final ViewPanel viewPanel) {
251 super.keyPressed(event, viewPanel);
253 processKeyEvent(event);
257 checkCursorBoundaries();
264 * Empty implementation of the ClipboardOwner interface.
267 public void lostOwnership(final Clipboard aClipboard,
268 final Transferable aContents) {
272 public void markRowDirty() {
273 dirtyRows.add(cursorLocation.row);
276 public void pasteFromClipboard() {
277 insertText(getClipboardContents());
280 private void processBackspace() {
281 if (selectionStart.compareTo(selectionEnd) == 0) {
282 // erase single character
283 if (cursorLocation.column > 0) {
284 cursorLocation.column--;
285 page.removeCharacter(cursorLocation.row, cursorLocation.column);
286 // System.out.println(lines.get(currentCursor.line).toString());
287 } else if (cursorLocation.row > 0) {
288 cursorLocation.row--;
289 final int currentLineLength = page
290 .getLineLength(cursorLocation.row);
291 cursorLocation.column = currentLineLength;
292 page.getLine(cursorLocation.row)
293 .insertTextLine(currentLineLength,
294 page.getLine(cursorLocation.row + 1));
295 page.removeLine(cursorLocation.row + 1);
299 // dedent multiple lines
300 ensureSelectionOrder();
301 // scan if enough space exists
302 for (int y = selectionStart.row; y < selectionEnd.row; y++)
303 if (page.getLine(y).getIdent() < 4)
306 for (int y = selectionStart.row; y < selectionEnd.row; y++)
307 page.getLine(y).cutFromBeginning(4);
313 private void processCtrlCombinations(final int keyCode) {
315 if ((char) keyCode == 'A') { // CTRL + A -- select all
316 final int lastLineIndex = page.getLinesCount() - 1;
317 selectionStart = new TextPointer(0, 0);
318 selectionEnd = new TextPointer(lastLineIndex,
319 page.getLineLength(lastLineIndex));
324 if ((char) keyCode == 'X')
328 if ((char) keyCode == 'C')
332 if ((char) keyCode == 'V')
333 pasteFromClipboard();
335 if (keyCode == 39) { // RIGHT
336 // skip to the beginning of the next word
338 for (int x = cursorLocation.column; x < (page
339 .getLineLength(cursorLocation.row) - 1); x++)
340 if ((page.getChar(cursorLocation.row, x) == ' ')
341 && (page.getChar(cursorLocation.row, x + 1) != ' ')) {
342 // beginning of the next word is found
343 cursorLocation.column = x + 1;
347 cursorLocation.column = page.getLineLength(cursorLocation.row);
351 if (keyCode == 37) { // Left
353 // skip to the beginning of the previous word
354 for (int x = cursorLocation.column - 2; x >= 0; x--)
355 if ((page.getChar(cursorLocation.row, x) == ' ')
356 & (page.getChar(cursorLocation.row, x + 1) != ' ')) {
357 cursorLocation.column = x + 1;
361 cursorLocation.column = 0;
365 public void processDel() {
366 if (selectionStart.compareTo(selectionEnd) == 0) {
367 // is there still some text right to the cursor ?
368 if (cursorLocation.column < page.getLineLength(cursorLocation.row))
369 page.removeCharacter(cursorLocation.row, cursorLocation.column);
371 page.getLine(cursorLocation.row).insertTextLine(
372 cursorLocation.column,
373 page.getLine(cursorLocation.row + 1));
374 page.removeLine(cursorLocation.row + 1);
383 private void processEnter() {
384 final TextLine currentLine = page.getLine(cursorLocation.row);
385 // move everything right to the cursor into new line
386 final TextLine newLine = currentLine.getSubLine(cursorLocation.column,
387 currentLine.getLength());
388 page.insertLine(cursorLocation.row + 1, newLine);
390 // trim existing line
391 page.getLine(cursorLocation.row).cutUntilEnd(cursorLocation.column);
394 cursorLocation.row++;
395 cursorLocation.column = 0;
398 private void processKeyEvent(final KeyEvent event) {
399 final int modifiers = event.getModifiersEx();
400 final int keyCode = event.getKeyCode();
401 final char keyChar = event.getKeyChar();
403 // System.out.println("Keycode:" + keyCode s+ ", keychar:" + keyChar);
405 if (KeyboardHelper.isAltPressed(modifiers))
408 if (KeyboardHelper.isCtrlPressed(modifiers)) {
409 processCtrlCombinations(keyCode);
413 if (keyCode == KeyboardHelper.TAB) {
414 processTab(modifiers);
420 if (KeyboardHelper.isText(keyCode)) {
421 insertText(String.valueOf(keyChar));
425 if (KeyboardHelper.isShiftPressed(modifiers)) {
427 attemptSelectionStart:{
429 if (keyChar == 65535)
431 break attemptSelectionStart;
432 if (((keyChar >= 32) & (keyChar <= 128)) | (keyChar == 10)
433 | (keyChar == 8) | (keyChar == 9))
434 break attemptSelectionStart;
436 selectionStart = new TextPointer(cursorLocation);
437 selectionEnd = selectionStart;
444 if (keyCode == KeyboardHelper.HOME) {
445 cursorLocation.column = 0;
448 if (keyCode == KeyboardHelper.END) {
449 cursorLocation.column = page.getLineLength(cursorLocation.row);
453 // process cursor keys
454 if (keyCode == KeyboardHelper.DOWN) {
456 cursorLocation.row++;
460 if (keyCode == KeyboardHelper.UP) {
462 cursorLocation.row--;
466 if (keyCode == KeyboardHelper.RIGHT) {
467 cursorLocation.column++;
471 if (keyCode == KeyboardHelper.LEFT) {
472 cursorLocation.column--;
476 if (keyCode == KeyboardHelper.PGDOWN) {
477 cursorLocation.row += textCanvas.getSize().row;
482 if (keyCode == KeyboardHelper.PGUP) {
483 cursorLocation.row -= textCanvas.getSize().row;
490 private void processTab(final int modifiers) {
491 if (KeyboardHelper.isShiftPressed(modifiers)) {
492 if (selectionStart.compareTo(selectionEnd) != 0) {
493 // dedent multiple lines
494 ensureSelectionOrder();
498 // check that identation is possible
499 for (int y = selectionStart.row; y < selectionEnd.row; y++) {
500 final TextLine textLine = page.getLine(y);
502 if (!textLine.isEmpty())
503 if (textLine.getIdent() < 4)
504 break identSelection;
507 for (int y = selectionStart.row; y < selectionEnd.row; y++)
508 page.getLine(y).cutFromBeginning(4);
511 // dedent current line
512 final TextLine textLine = page.getLine(cursorLocation.row);
514 if (cursorLocation.column >= 4)
515 if (textLine.isEmpty())
516 cursorLocation.column -= 4;
517 else if (textLine.getIdent() >= 4) {
518 cursorLocation.column -= 4;
519 textLine.cutFromBeginning(4);
526 } else if (selectionStart.compareTo(selectionEnd) != 0) {
527 // ident multiple lines
528 ensureSelectionOrder();
529 for (int y = selectionStart.row; y < selectionEnd.row; y++)
530 page.getLine(y).addIdent(4);
536 public void repaintPage() {
538 final int chXe = textCanvas.getSize().column + 2;
539 final int chYe = textCanvas.getSize().row + 2;
541 for (int cy = 0; cy < chYe; cy++)
542 for (int cx = 0; cx < chXe; cx++) {
543 final boolean isTabMargin = ((cx + scrolledCharacters) % 4) == 0;
545 if ((cx == (cursorLocation.column - scrolledCharacters))
546 & (cy == (cursorLocation.row - scrolledLines))) {
548 textCanvas.setBackgroundColor(colorConfig.cursorBack);
549 textCanvas.setForegroundColor(colorConfig.cursorText);
550 } else if (new TextPointer(cy + scrolledLines, cx).isBetween(
551 selectionStart, selectionEnd)) {
553 textCanvas.setBackgroundColor(colorConfig.selectedBack);
554 textCanvas.setForegroundColor(colorConfig.selectedText);
557 textCanvas.setBackgroundColor(colorConfig.normalBack);
558 textCanvas.setForegroundColor(colorConfig.normalText);
562 .setBackgroundColor(colorConfig.tabulatorBack);
566 final char charUnderCursor = page.getChar(cy + scrolledLines,
567 cx + scrolledCharacters);
569 textCanvas.putChar(cy, cx, charUnderCursor);
574 public void repaintRow(final int rowNumber) {
579 private void repaintWhatNeeded() {
586 dirtyRows.forEach(this::repaintRow);
590 // public void setCaret(final int x, final int y) {
591 // selecting = false;
592 // cursorLocation.column = (x / characterWidth) + scrolledCharacters;
593 // cursorLocation.row = (y / characterHeight) + scrolledLines;
598 * Scroll full page to given amount of lines or charancters.
600 public void scroll(final int charactersToScroll, final int linesToScroll) {
601 scrolledLines += linesToScroll;
602 scrolledCharacters += charactersToScroll;
604 if (scrolledLines < 0)
607 if (scrolledCharacters < 0)
608 scrolledCharacters = 0;
613 public void setText(final String text) {
614 // System.out.println("Set text:" + text);
615 cursorLocation = new TextPointer(0, 0);
616 scrolledCharacters = 0;
618 selectionStart = new TextPointer(0, 0);
619 selectionEnd = new TextPointer(0, 0);