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.math.Transform;
12 import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
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;
25 // lines that need to be repainted
26 private final Set<Integer> dirtyRows = new HashSet<>();
27 private final TextCanvas textCanvas;
28 public int scrolledCharacters = 0, scrolledLines = 0;
29 public boolean selecting = false;
30 public TextPointer selectionStart = new TextPointer(0, 0);
31 public TextPointer selectionEnd = new TextPointer(0, 0);
32 public TextPointer cursorLocation;
33 Page page = new Page();
34 ColorConfig colorConfig = new ColorConfig();
35 boolean repaintPage = false;
37 public TextEditComponent(final Transform transform,
38 final ViewPanel viewPanel, final Point2D size) {
39 super(transform, viewPanel, size.to3D());
41 cursorLocation = new TextPointer(0, 0);
43 // initialize visual panel
45 final int columns = (int) (size.x / TextCanvas.FONT_CHAR_WIDTH);
46 final int rows = (int) (size.y / TextCanvas.FONT_CHAR_HEIGHT);
48 textCanvas = new TextCanvas(new Transform(), new TextPointer(rows,
49 columns), Color.WHITE, colorConfig.normalBack);
51 textCanvas.setMouseInteractionController(this);
57 private void checkCursorBoundaries() {
58 if (cursorLocation.column < 0)
59 cursorLocation.column = 0;
60 if (cursorLocation.row < 0)
61 cursorLocation.row = 0;
63 // ensure chat cursor stays within vertical editor boundaries by
65 if ((cursorLocation.row - scrolledLines) < 0)
66 scroll(0, cursorLocation.row - scrolledLines);
68 if ((((cursorLocation.row - scrolledLines) + 1)) > textCanvas.getSize().row)
70 ((((((cursorLocation.row - scrolledLines) + 1) - textCanvas
73 // ensure chat cursor stays within horizontal editor boundaries by
74 // horizontal scrolling
75 if ((cursorLocation.column - scrolledCharacters) < 0)
76 scroll(cursorLocation.column - scrolledCharacters, 0);
78 if ((((cursorLocation.column - scrolledCharacters) + 1)) > textCanvas
80 scroll((((((cursorLocation.column - scrolledCharacters) + 1) - textCanvas
81 .getSize().column))), 0);
85 * Clear text selection.
87 public void clearSelection() {
88 selectionEnd = new TextPointer(selectionStart);
93 * Copies selected text to the clipboard.
95 public void copyToClipboard() {
96 if (selectionStart.compareTo(selectionEnd) == 0)
98 // System.out.println("Copy action.");
99 final StringBuilder msg = new StringBuilder();
101 ensureSelectionOrder();
103 for (int row = selectionStart.row; row <= selectionEnd.row; row++) {
104 final TextLine textLine = page.getLine(row);
106 if (row == selectionStart.row) {
107 if (row == selectionEnd.row)
108 msg.append(textLine.getSubString(selectionStart.column,
109 selectionEnd.column + 1));
111 msg.append(textLine.getSubString(selectionStart.column,
112 textLine.getLength()));
115 if (row == selectionEnd.row)
117 .getSubString(0, selectionEnd.column + 1));
119 msg.append(textLine.toString());
123 setClipboardContents(msg.toString());
126 public void cutToClipboard() {
132 public void deleteSelection() {
133 ensureSelectionOrder();
136 for (int line = selectionStart.row; line <= selectionEnd.row; line++) {
137 final TextLine currentLine = page.getLine(line - ym);
139 if (line == selectionStart.row) {
140 if (line == selectionEnd.row)
142 currentLine.cutSubString(selectionStart.column,
143 selectionEnd.column);
144 else if (selectionStart.column == 0) {
145 page.removeLine(line - ym);
148 currentLine.cutSubString(selectionStart.column,
149 currentLine.getLength() + 1);
150 } else if (line == selectionEnd.row)
151 currentLine.cutSubString(0, selectionEnd.column);
153 page.removeLine(line - ym);
159 cursorLocation = new TextPointer(selectionStart);
163 * Ensures that {@link #selectionStart} is smaller than
164 * {@link #selectionEnd}.
166 public void ensureSelectionOrder() {
167 if (selectionStart.compareTo(selectionEnd) > 0) {
168 final TextPointer temp = selectionEnd;
169 selectionEnd = selectionStart;
170 selectionStart = temp;
174 public String getClipboardContents() {
176 final Clipboard clipboard = Toolkit.getDefaultToolkit()
177 .getSystemClipboard();
178 // odd: the Object param of getContents is not currently used
179 final Transferable contents = clipboard.getContents(null);
180 final boolean hasTransferableText = (contents != null)
181 && contents.isDataFlavorSupported(DataFlavor.stringFlavor);
182 if (hasTransferableText)
184 result = (String) contents
185 .getTransferData(DataFlavor.stringFlavor);
186 } catch (final UnsupportedFlavorException | IOException ex) {
187 // highly unlikely since we are using a standard DataFlavor
188 System.out.println(ex);
190 // System.out.println(result);
195 * Place string into system clipboard so that it can be pasted into other
198 public void setClipboardContents(final String contents) {
199 final StringSelection stringSelection = new StringSelection(contents);
200 final Clipboard clipboard = Toolkit.getDefaultToolkit()
201 .getSystemClipboard();
202 clipboard.setContents(stringSelection, stringSelection);
205 public void goToLine(final int Line) {
206 // markNavigationLocation(Line);
207 scrolledLines = Line + 1;
208 cursorLocation.row = Line + 1;
209 cursorLocation.column = 0;
213 public void insertText(final String txt) {
217 for (final char c : txt.toCharArray()) {
219 if (c == KeyboardHelper.DEL) {
224 if (c == KeyboardHelper.ENTER) {
229 if (c == KeyboardHelper.BACKSPACE) {
235 if (KeyboardHelper.isText(c)) {
236 page.insertCharacter(cursorLocation.row, cursorLocation.column,
238 cursorLocation.column++;
247 public boolean keyPressed(final KeyEvent event, final ViewPanel viewPanel) {
248 super.keyPressed(event, viewPanel);
250 processKeyEvent(event);
254 checkCursorBoundaries();
261 * Empty implementation of the ClipboardOwner interface.
264 public void lostOwnership(final Clipboard aClipboard,
265 final Transferable aContents) {
269 public void markRowDirty() {
270 dirtyRows.add(cursorLocation.row);
273 public void pasteFromClipboard() {
274 insertText(getClipboardContents());
277 private void processBackspace() {
278 if (selectionStart.compareTo(selectionEnd) == 0) {
279 // erase single character
280 if (cursorLocation.column > 0) {
281 cursorLocation.column--;
282 page.removeCharacter(cursorLocation.row, cursorLocation.column);
283 // System.out.println(lines.get(currentCursor.line).toString());
284 } else if (cursorLocation.row > 0) {
285 cursorLocation.row--;
286 final int currentLineLength = page
287 .getLineLength(cursorLocation.row);
288 cursorLocation.column = currentLineLength;
289 page.getLine(cursorLocation.row)
290 .insertTextLine(currentLineLength,
291 page.getLine(cursorLocation.row + 1));
292 page.removeLine(cursorLocation.row + 1);
296 // dedent multiple lines
297 ensureSelectionOrder();
298 // scan if enough space exists
299 for (int y = selectionStart.row; y < selectionEnd.row; y++)
300 if (page.getLine(y).getIdent() < 4)
303 for (int y = selectionStart.row; y < selectionEnd.row; y++)
304 page.getLine(y).cutFromBeginning(4);
310 private void processCtrlCombinations(final int keyCode) {
312 if ((char) keyCode == 'A') { // CTRL + A -- select all
313 final int lastLineIndex = page.getLinesCount() - 1;
314 selectionStart = new TextPointer(0, 0);
315 selectionEnd = new TextPointer(lastLineIndex,
316 page.getLineLength(lastLineIndex));
321 if ((char) keyCode == 'X')
325 if ((char) keyCode == 'C')
329 if ((char) keyCode == 'V')
330 pasteFromClipboard();
332 if (keyCode == 39) { // RIGHT
333 // skip to the beginning of the next word
335 for (int x = cursorLocation.column; x < (page
336 .getLineLength(cursorLocation.row) - 1); x++)
337 if ((page.getChar(cursorLocation.row, x) == ' ')
338 && (page.getChar(cursorLocation.row, x + 1) != ' ')) {
339 // beginning of the next word is found
340 cursorLocation.column = x + 1;
344 cursorLocation.column = page.getLineLength(cursorLocation.row);
348 if (keyCode == 37) { // Left
350 // skip to the beginning of the previous word
351 for (int x = cursorLocation.column - 2; x >= 0; x--)
352 if ((page.getChar(cursorLocation.row, x) == ' ')
353 & (page.getChar(cursorLocation.row, x + 1) != ' ')) {
354 cursorLocation.column = x + 1;
358 cursorLocation.column = 0;
362 public void processDel() {
363 if (selectionStart.compareTo(selectionEnd) == 0) {
364 // is there still some text right to the cursor ?
365 if (cursorLocation.column < page.getLineLength(cursorLocation.row))
366 page.removeCharacter(cursorLocation.row, cursorLocation.column);
368 page.getLine(cursorLocation.row).insertTextLine(
369 cursorLocation.column,
370 page.getLine(cursorLocation.row + 1));
371 page.removeLine(cursorLocation.row + 1);
380 private void processEnter() {
381 final TextLine currentLine = page.getLine(cursorLocation.row);
382 // move everything right to the cursor into new line
383 final TextLine newLine = currentLine.getSubLine(cursorLocation.column,
384 currentLine.getLength());
385 page.insertLine(cursorLocation.row + 1, newLine);
387 // trim existing line
388 page.getLine(cursorLocation.row).cutUntilEnd(cursorLocation.column);
391 cursorLocation.row++;
392 cursorLocation.column = 0;
395 private void processKeyEvent(final KeyEvent event) {
396 final int modifiers = event.getModifiers();
398 final int keyCode = event.getKeyCode();
399 final char keyChar = event.getKeyChar();
401 // System.out.println("Keycode:" + keyCode s+ ", keychar:" + keyChar);
403 if (KeyboardHelper.isAlt(modifiers))
406 if (KeyboardHelper.isCtrl(modifiers)) {
407 processCtrlCombinations(keyCode);
411 if (keyCode == KeyboardHelper.TAB) {
412 processTab(modifiers);
418 if (KeyboardHelper.isText(keyCode)) {
419 insertText(String.valueOf(keyChar));
423 // System.out.println("Co:" + String.valueOf(code) + " Ch:" +
424 // String.valueOf(keyChar));
426 if (KeyboardHelper.isShift(modifiers)) {
428 attemptSelectionStart:{
430 if (keyChar == 65535)
432 break attemptSelectionStart;
433 if (((keyChar >= 32) & (keyChar <= 128)) | (keyChar == 10)
434 | (keyChar == 8) | (keyChar == 9))
435 break attemptSelectionStart;
437 // System.out.println("Selection started:" + keyChar + " "
440 selectionStart = new TextPointer(cursorLocation);
441 selectionEnd = selectionStart;
448 if (keyCode == KeyboardHelper.HOME) {
449 cursorLocation.column = 0;
452 if (keyCode == KeyboardHelper.END) {
453 cursorLocation.column = page.getLineLength(cursorLocation.row);
457 // process cursor keys
458 if (keyCode == KeyboardHelper.DOWN) {
460 cursorLocation.row++;
464 if (keyCode == KeyboardHelper.UP) {
466 cursorLocation.row--;
470 if (keyCode == KeyboardHelper.RIGHT) {
471 cursorLocation.column++;
475 if (keyCode == KeyboardHelper.LEFT) {
476 cursorLocation.column--;
480 if (keyCode == KeyboardHelper.PGDOWN) {
481 cursorLocation.row += textCanvas.getSize().row;
486 if (keyCode == KeyboardHelper.PGUP) {
487 cursorLocation.row -= textCanvas.getSize().row;
494 private void processTab(final int modifiers) {
495 if (KeyboardHelper.isShift(modifiers)) {
496 if (selectionStart.compareTo(selectionEnd) != 0) {
497 // dedent multiple lines
498 ensureSelectionOrder();
502 // check that identation is possible
503 for (int y = selectionStart.row; y < selectionEnd.row; y++) {
504 final TextLine textLine = page.getLine(y);
506 if (!textLine.isEmpty())
507 if (textLine.getIdent() < 4)
508 break identSelection;
511 for (int y = selectionStart.row; y < selectionEnd.row; y++)
512 page.getLine(y).cutFromBeginning(4);
515 // dedent current line
516 final TextLine textLine = page.getLine(cursorLocation.row);
518 if (cursorLocation.column >= 4)
519 if (textLine.isEmpty())
520 cursorLocation.column -= 4;
521 else if (textLine.getIdent() >= 4) {
522 cursorLocation.column -= 4;
523 textLine.cutFromBeginning(4);
530 } else if (selectionStart.compareTo(selectionEnd) != 0) {
531 // ident multiple lines
532 ensureSelectionOrder();
533 for (int y = selectionStart.row; y < selectionEnd.row; y++)
534 page.getLine(y).addIdent(4);
540 public void repaintPage() {
542 final int chXe = textCanvas.getSize().column + 2;
543 final int chYe = textCanvas.getSize().row + 2;
545 for (int cy = 0; cy < chYe; cy++)
546 for (int cx = 0; cx < chXe; cx++) {
547 final boolean isTabMargin = ((cx + scrolledCharacters) % 4) == 0;
549 if ((cx == (cursorLocation.column - scrolledCharacters))
550 & (cy == (cursorLocation.row - scrolledLines))) {
552 textCanvas.setBackgroundColor(colorConfig.cursorBack);
553 textCanvas.setForegroundColor(colorConfig.cursorText);
554 } else if (new TextPointer(cy + scrolledLines, cx).isBetween(
555 selectionStart, selectionEnd)) {
557 textCanvas.setBackgroundColor(colorConfig.selectedBack);
558 textCanvas.setForegroundColor(colorConfig.selectedText);
561 textCanvas.setBackgroundColor(colorConfig.normalBack);
562 textCanvas.setForegroundColor(colorConfig.normalText);
566 .setBackgroundColor(colorConfig.tabulatorBack);
570 final char charUnderCursor = page.getChar(cy + scrolledLines,
571 cx + scrolledCharacters);
573 textCanvas.putChar(cy, cx, charUnderCursor);
578 public void repaintRow(final int rowNumber) {
583 private void repaintWhatNeeded() {
590 dirtyRows.forEach(this::repaintRow);
594 // public void setCaret(final int x, final int y) {
595 // selecting = false;
596 // cursorLocation.column = (x / characterWidth) + scrolledCharacters;
597 // cursorLocation.row = (y / characterHeight) + scrolledLines;
602 * Scroll full page to given amount of lines or charancters.
604 public void scroll(final int charactersToScroll, final int linesToScroll) {
605 scrolledLines += linesToScroll;
606 scrolledCharacters += charactersToScroll;
608 if (scrolledLines < 0)
611 if (scrolledCharacters < 0)
612 scrolledCharacters = 0;
617 public void setText(final String text) {
618 // System.out.println("Set text:" + text);
619 cursorLocation = new TextPointer(0, 0);
620 scrolledCharacters = 0;
622 selectionStart = new TextPointer(0, 0);
623 selectionEnd = new TextPointer(0, 0);