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<>();
31 private final TextCanvas textCanvas;
32 public int scrolledCharacters = 0, scrolledLines = 0;
33 public boolean selecting = false;
34 public TextPointer selectionStart = new TextPointer(0, 0);
35 public TextPointer selectionEnd = new TextPointer(0, 0);
36 public TextPointer cursorLocation = new TextPointer(0, 0);
37 Page page = new Page();
38 LookAndFeel lookAndFeel;
39 boolean repaintPage = false;
41 public TextEditComponent(final Transform transform,
42 final ViewPanel viewPanel,
43 final Point2D sizeInWorldCoordinates,
44 LookAndFeel lookAndFeel) {
45 super(transform, viewPanel, sizeInWorldCoordinates.to3D());
47 this.lookAndFeel = lookAndFeel;
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(
53 new TextPointer(rows, columns),
54 lookAndFeel.foreground, lookAndFeel.background);
56 textCanvas.setMouseInteractionController(this);
62 private void checkCursorBoundaries() {
63 if (cursorLocation.column < 0)
64 cursorLocation.column = 0;
65 if (cursorLocation.row < 0)
66 cursorLocation.row = 0;
68 // ensure chat cursor stays within vertical editor boundaries by
70 if ((cursorLocation.row - scrolledLines) < 0)
71 scroll(0, cursorLocation.row - scrolledLines);
73 if ((((cursorLocation.row - scrolledLines) + 1)) > textCanvas.getSize().row)
75 ((((((cursorLocation.row - scrolledLines) + 1) - textCanvas
78 // ensure chat cursor stays within horizontal editor boundaries by
79 // horizontal scrolling
80 if ((cursorLocation.column - scrolledCharacters) < 0)
81 scroll(cursorLocation.column - scrolledCharacters, 0);
83 if ((((cursorLocation.column - scrolledCharacters) + 1)) > textCanvas
85 scroll((((((cursorLocation.column - scrolledCharacters) + 1) - textCanvas
86 .getSize().column))), 0);
90 * Clear text selection.
92 public void clearSelection() {
93 selectionEnd = new TextPointer(selectionStart);
98 * Copies selected text to the clipboard.
100 public void copyToClipboard() {
101 if (selectionStart.compareTo(selectionEnd) == 0)
103 // System.out.println("Copy action.");
104 final StringBuilder msg = new StringBuilder();
106 ensureSelectionOrder();
108 for (int row = selectionStart.row; row <= selectionEnd.row; row++) {
109 final TextLine textLine = page.getLine(row);
111 if (row == selectionStart.row) {
112 if (row == selectionEnd.row)
113 msg.append(textLine.getSubString(selectionStart.column,
114 selectionEnd.column + 1));
116 msg.append(textLine.getSubString(selectionStart.column,
117 textLine.getLength()));
120 if (row == selectionEnd.row)
122 .getSubString(0, selectionEnd.column + 1));
124 msg.append(textLine.toString());
128 setClipboardContents(msg.toString());
131 public void cutToClipboard() {
137 public void deleteSelection() {
138 ensureSelectionOrder();
141 for (int line = selectionStart.row; line <= selectionEnd.row; line++) {
142 final TextLine currentLine = page.getLine(line - ym);
144 if (line == selectionStart.row) {
145 if (line == selectionEnd.row)
147 currentLine.cutSubString(selectionStart.column,
148 selectionEnd.column);
149 else if (selectionStart.column == 0) {
150 page.removeLine(line - ym);
153 currentLine.cutSubString(selectionStart.column,
154 currentLine.getLength() + 1);
155 } else if (line == selectionEnd.row)
156 currentLine.cutSubString(0, selectionEnd.column);
158 page.removeLine(line - ym);
164 cursorLocation = new TextPointer(selectionStart);
168 * Ensures that {@link #selectionStart} is smaller than
169 * {@link #selectionEnd}.
171 public void ensureSelectionOrder() {
172 if (selectionStart.compareTo(selectionEnd) > 0) {
173 final TextPointer temp = selectionEnd;
174 selectionEnd = selectionStart;
175 selectionStart = temp;
179 public String getClipboardContents() {
181 final Clipboard clipboard = Toolkit.getDefaultToolkit()
182 .getSystemClipboard();
183 // odd: the Object param of getContents is not currently used
184 final Transferable contents = clipboard.getContents(null);
185 final boolean hasTransferableText = (contents != null)
186 && contents.isDataFlavorSupported(DataFlavor.stringFlavor);
187 if (hasTransferableText)
189 result = (String) contents
190 .getTransferData(DataFlavor.stringFlavor);
191 } catch (final UnsupportedFlavorException | IOException ex) {
192 // highly unlikely since we are using a standard DataFlavor
193 System.out.println(ex);
195 // System.out.println(result);
200 * Place string into system clipboard so that it can be pasted into other
203 public void setClipboardContents(final String contents) {
204 final StringSelection stringSelection = new StringSelection(contents);
205 final Clipboard clipboard = Toolkit.getDefaultToolkit()
206 .getSystemClipboard();
207 clipboard.setContents(stringSelection, stringSelection);
210 public void goToLine(final int Line) {
211 // markNavigationLocation(Line);
212 scrolledLines = Line + 1;
213 cursorLocation.row = Line + 1;
214 cursorLocation.column = 0;
218 public void insertText(final String txt) {
222 for (final char c : txt.toCharArray()) {
224 if (c == KeyboardHelper.DEL) {
229 if (c == KeyboardHelper.ENTER) {
234 if (c == KeyboardHelper.BACKSPACE) {
240 if (KeyboardHelper.isText(c)) {
241 page.insertCharacter(cursorLocation.row, cursorLocation.column,
243 cursorLocation.column++;
252 public boolean keyPressed(final KeyEvent event, final ViewPanel viewPanel) {
253 super.keyPressed(event, viewPanel);
255 processKeyEvent(event);
259 checkCursorBoundaries();
266 * Empty implementation of the ClipboardOwner interface.
269 public void lostOwnership(final Clipboard aClipboard,
270 final Transferable aContents) {
274 public void markRowDirty() {
275 dirtyRows.add(cursorLocation.row);
278 public void pasteFromClipboard() {
279 insertText(getClipboardContents());
282 private void processBackspace() {
283 if (selectionStart.compareTo(selectionEnd) == 0) {
284 // erase single character
285 if (cursorLocation.column > 0) {
286 cursorLocation.column--;
287 page.removeCharacter(cursorLocation.row, cursorLocation.column);
288 // System.out.println(lines.get(currentCursor.line).toString());
289 } else if (cursorLocation.row > 0) {
290 cursorLocation.row--;
291 final int currentLineLength = page
292 .getLineLength(cursorLocation.row);
293 cursorLocation.column = currentLineLength;
294 page.getLine(cursorLocation.row)
295 .insertTextLine(currentLineLength,
296 page.getLine(cursorLocation.row + 1));
297 page.removeLine(cursorLocation.row + 1);
301 // dedent multiple lines
302 ensureSelectionOrder();
303 // scan if enough space exists
304 for (int y = selectionStart.row; y < selectionEnd.row; y++)
305 if (page.getLine(y).getIdent() < 4)
308 for (int y = selectionStart.row; y < selectionEnd.row; y++)
309 page.getLine(y).cutFromBeginning(4);
315 private void processCtrlCombinations(final int keyCode) {
317 if ((char) keyCode == 'A') { // CTRL + A -- select all
318 final int lastLineIndex = page.getLinesCount() - 1;
319 selectionStart = new TextPointer(0, 0);
320 selectionEnd = new TextPointer(lastLineIndex,
321 page.getLineLength(lastLineIndex));
326 if ((char) keyCode == 'X')
330 if ((char) keyCode == 'C')
334 if ((char) keyCode == 'V')
335 pasteFromClipboard();
337 if (keyCode == 39) { // RIGHT
338 // skip to the beginning of the next word
340 for (int x = cursorLocation.column; x < (page
341 .getLineLength(cursorLocation.row) - 1); x++)
342 if ((page.getChar(cursorLocation.row, x) == ' ')
343 && (page.getChar(cursorLocation.row, x + 1) != ' ')) {
344 // beginning of the next word is found
345 cursorLocation.column = x + 1;
349 cursorLocation.column = page.getLineLength(cursorLocation.row);
353 if (keyCode == 37) { // Left
355 // skip to the beginning of the previous word
356 for (int x = cursorLocation.column - 2; x >= 0; x--)
357 if ((page.getChar(cursorLocation.row, x) == ' ')
358 & (page.getChar(cursorLocation.row, x + 1) != ' ')) {
359 cursorLocation.column = x + 1;
363 cursorLocation.column = 0;
367 public void processDel() {
368 if (selectionStart.compareTo(selectionEnd) == 0) {
369 // is there still some text right to the cursor ?
370 if (cursorLocation.column < page.getLineLength(cursorLocation.row))
371 page.removeCharacter(cursorLocation.row, cursorLocation.column);
373 page.getLine(cursorLocation.row).insertTextLine(
374 cursorLocation.column,
375 page.getLine(cursorLocation.row + 1));
376 page.removeLine(cursorLocation.row + 1);
385 private void processEnter() {
386 final TextLine currentLine = page.getLine(cursorLocation.row);
387 // move everything right to the cursor into new line
388 final TextLine newLine = currentLine.getSubLine(cursorLocation.column,
389 currentLine.getLength());
390 page.insertLine(cursorLocation.row + 1, newLine);
392 // trim existing line
393 page.getLine(cursorLocation.row).cutUntilEnd(cursorLocation.column);
396 cursorLocation.row++;
397 cursorLocation.column = 0;
400 private void processKeyEvent(final KeyEvent event) {
401 final int modifiers = event.getModifiersEx();
402 final int keyCode = event.getKeyCode();
403 final char keyChar = event.getKeyChar();
405 // System.out.println("Keycode:" + keyCode s+ ", keychar:" + keyChar);
407 if (KeyboardHelper.isAltPressed(modifiers))
410 if (KeyboardHelper.isCtrlPressed(modifiers)) {
411 processCtrlCombinations(keyCode);
415 if (keyCode == KeyboardHelper.TAB) {
416 processTab(modifiers);
422 if (KeyboardHelper.isText(keyCode)) {
423 insertText(String.valueOf(keyChar));
427 if (KeyboardHelper.isShiftPressed(modifiers)) {
429 attemptSelectionStart:{
431 if (keyChar == 65535)
433 break attemptSelectionStart;
434 if (((keyChar >= 32) & (keyChar <= 128)) | (keyChar == 10)
435 | (keyChar == 8) | (keyChar == 9))
436 break attemptSelectionStart;
438 selectionStart = new TextPointer(cursorLocation);
439 selectionEnd = selectionStart;
446 if (keyCode == KeyboardHelper.HOME) {
447 cursorLocation.column = 0;
450 if (keyCode == KeyboardHelper.END) {
451 cursorLocation.column = page.getLineLength(cursorLocation.row);
455 // process cursor keys
456 if (keyCode == KeyboardHelper.DOWN) {
458 cursorLocation.row++;
462 if (keyCode == KeyboardHelper.UP) {
464 cursorLocation.row--;
468 if (keyCode == KeyboardHelper.RIGHT) {
469 cursorLocation.column++;
473 if (keyCode == KeyboardHelper.LEFT) {
474 cursorLocation.column--;
478 if (keyCode == KeyboardHelper.PGDOWN) {
479 cursorLocation.row += textCanvas.getSize().row;
484 if (keyCode == KeyboardHelper.PGUP) {
485 cursorLocation.row -= textCanvas.getSize().row;
492 private void processTab(final int modifiers) {
493 if (KeyboardHelper.isShiftPressed(modifiers)) {
494 if (selectionStart.compareTo(selectionEnd) != 0) {
495 // dedent multiple lines
496 ensureSelectionOrder();
500 // check that indentation is possible
501 for (int y = selectionStart.row; y < selectionEnd.row; y++) {
502 final TextLine textLine = page.getLine(y);
504 if (!textLine.isEmpty())
505 if (textLine.getIdent() < 4)
506 break identSelection;
509 for (int y = selectionStart.row; y < selectionEnd.row; y++)
510 page.getLine(y).cutFromBeginning(4);
513 // dedent current line
514 final TextLine textLine = page.getLine(cursorLocation.row);
516 if (cursorLocation.column >= 4)
517 if (textLine.isEmpty())
518 cursorLocation.column -= 4;
519 else if (textLine.getIdent() >= 4) {
520 cursorLocation.column -= 4;
521 textLine.cutFromBeginning(4);
528 } else if (selectionStart.compareTo(selectionEnd) != 0) {
529 // ident multiple lines
530 ensureSelectionOrder();
531 for (int y = selectionStart.row; y < selectionEnd.row; y++)
532 page.getLine(y).addIdent(4);
538 public void repaintPage() {
540 final int columnCount = textCanvas.getSize().column + 2;
541 final int rowCount = textCanvas.getSize().row + 2;
543 for (int row = 0; row < rowCount; row++)
544 for (int column = 0; column < columnCount; column++) {
545 final boolean isTabMargin = ((column + scrolledCharacters) % 4) == 0;
547 if ((column == (cursorLocation.column - scrolledCharacters))
548 & (row == (cursorLocation.row - scrolledLines))) {
550 textCanvas.setBackgroundColor(lookAndFeel.cursorBackground);
551 textCanvas.setForegroundColor(lookAndFeel.cursorForeground);
552 } else if (new TextPointer(row + scrolledLines, column).isBetween(
553 selectionStart, selectionEnd)) {
555 textCanvas.setBackgroundColor(lookAndFeel.selectionBackground);
556 textCanvas.setForegroundColor(lookAndFeel.selectionForeground);
559 textCanvas.setBackgroundColor(lookAndFeel.background);
560 textCanvas.setForegroundColor(lookAndFeel.foreground);
564 .setBackgroundColor(lookAndFeel.tabStopBackground);
568 final char charUnderCursor = page.getChar(row + scrolledLines,
569 column + scrolledCharacters);
571 textCanvas.putChar(row, column, charUnderCursor);
576 public void repaintRow(final int rowNumber) {
577 // TODO: Optimize this. No need to repaint entire page.
581 private void repaintWhatNeeded() {
588 dirtyRows.forEach(this::repaintRow);
592 // public void setCaret(final int x, final int y) {
593 // selecting = false;
594 // cursorLocation.column = (x / characterWidth) + scrolledCharacters;
595 // cursorLocation.row = (y / characterHeight) + scrolledLines;
600 * Scroll full page to given amount of lines or charancters.
602 public void scroll(final int charactersToScroll, final int linesToScroll) {
603 scrolledLines += linesToScroll;
604 scrolledCharacters += charactersToScroll;
606 if (scrolledLines < 0)
609 if (scrolledCharacters < 0)
610 scrolledCharacters = 0;
615 public void setText(final String text) {
616 // System.out.println("Set text:" + text);
617 cursorLocation = new TextPointer(0, 0);
618 scrolledCharacters = 0;
620 selectionStart = new TextPointer(0, 0);
621 selectionEnd = new TextPointer(0, 0);