2 * Sixth 3D engine. Copyright ©2012-2018, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public License
6 * or later as published by the Free Software Foundation.
10 package eu.svjatoslav.sixth.e3d.gui.textEditorComponent;
12 import eu.svjatoslav.sixth.e3d.geometry.Point2D;
13 import eu.svjatoslav.sixth.e3d.gui.GuiComponent;
14 import eu.svjatoslav.sixth.e3d.gui.TextPointer;
15 import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
16 import eu.svjatoslav.sixth.e3d.math.Transform;
17 import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
18 import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.textcanvas.TextCanvas;
21 import java.awt.datatransfer.*;
22 import java.awt.event.KeyEvent;
23 import java.io.IOException;
24 import java.util.HashSet;
27 public class TextEditComponent extends GuiComponent implements ClipboardOwner {
29 private static final long serialVersionUID = -7118833957783600630L;
30 // lines that need to be repainted
31 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;
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 size) {
44 super(transform, viewPanel, size.to3D());
46 cursorLocation = new TextPointer(0, 0);
48 // initialize visual panel
50 final int columns = (int) (size.x / TextCanvas.FONT_CHAR_WIDTH);
51 final int rows = (int) (size.y / TextCanvas.FONT_CHAR_HEIGHT);
53 textCanvas = new TextCanvas(new Transform(), new TextPointer(rows,
54 columns), Color.WHITE, colorConfig.normalBack);
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 void keyPressed(final KeyEvent event, final ViewPanel viewPanel) {
253 super.keyPressed(event, viewPanel);
255 processKeyEvent(event);
259 checkCursorBoundaries();
265 * Empty implementation of the ClipboardOwner interface.
268 public void lostOwnership(final Clipboard aClipboard,
269 final Transferable aContents) {
273 public void markRowDirty() {
274 dirtyRows.add(cursorLocation.row);
277 public void pasteFromClipboard() {
278 insertText(getClipboardContents());
281 private void processBackspace() {
282 if (selectionStart.compareTo(selectionEnd) == 0) {
283 // erase single character
284 if (cursorLocation.column > 0) {
285 cursorLocation.column--;
286 page.removeCharacter(cursorLocation.row, cursorLocation.column);
287 // System.out.println(lines.get(currentCursor.line).toString());
288 } else if (cursorLocation.row > 0) {
289 cursorLocation.row--;
290 final int currentLineLength = page
291 .getLineLength(cursorLocation.row);
292 cursorLocation.column = currentLineLength;
293 page.getLine(cursorLocation.row)
294 .insertTextLine(currentLineLength,
295 page.getLine(cursorLocation.row + 1));
296 page.removeLine(cursorLocation.row + 1);
300 // dedent multiple lines
301 ensureSelectionOrder();
302 // scan if enough space exists
303 for (int y = selectionStart.row; y < selectionEnd.row; y++)
304 if (page.getLine(y).getIdent() < 4)
307 for (int y = selectionStart.row; y < selectionEnd.row; y++)
308 page.getLine(y).cutFromBeginning(4);
314 private void processCtrlCombinations(final int keyCode) {
316 if ((char) keyCode == 'A') { // CTRL + A -- select all
317 final int lastLineIndex = page.getLinesCount() - 1;
318 selectionStart = new TextPointer(0, 0);
319 selectionEnd = new TextPointer(lastLineIndex,
320 page.getLineLength(lastLineIndex));
325 if ((char) keyCode == 'X')
329 if ((char) keyCode == 'C')
333 if ((char) keyCode == 'V')
334 pasteFromClipboard();
336 if (keyCode == 39) { // RIGHT
337 // skip to the beginning of the next word
339 for (int x = cursorLocation.column; x < (page
340 .getLineLength(cursorLocation.row) - 1); x++)
341 if ((page.getChar(cursorLocation.row, x) == ' ')
342 && (page.getChar(cursorLocation.row, x + 1) != ' ')) {
343 // beginning of the next word is found
344 cursorLocation.column = x + 1;
348 cursorLocation.column = page.getLineLength(cursorLocation.row);
352 if (keyCode == 37) { // Left
354 // skip to the beginning of the previous word
355 for (int x = cursorLocation.column - 2; x >= 0; x--)
356 if ((page.getChar(cursorLocation.row, x) == ' ')
357 & (page.getChar(cursorLocation.row, x + 1) != ' ')) {
358 cursorLocation.column = x + 1;
362 cursorLocation.column = 0;
366 public void processDel() {
367 if (selectionStart.compareTo(selectionEnd) == 0) {
368 // is there still some text right to the cursor ?
369 if (cursorLocation.column < page.getLineLength(cursorLocation.row))
370 page.removeCharacter(cursorLocation.row, cursorLocation.column);
372 page.getLine(cursorLocation.row).insertTextLine(
373 cursorLocation.column,
374 page.getLine(cursorLocation.row + 1));
375 page.removeLine(cursorLocation.row + 1);
384 private void processEnter() {
385 final TextLine currentLine = page.getLine(cursorLocation.row);
386 // move everything right to the cursor into new line
387 final TextLine newLine = currentLine.getSubLine(cursorLocation.column,
388 currentLine.getLength());
389 page.insertLine(cursorLocation.row + 1, newLine);
391 // trim existing line
392 page.getLine(cursorLocation.row).cutUntilEnd(cursorLocation.column);
395 cursorLocation.row++;
396 cursorLocation.column = 0;
399 private void processKeyEvent(final KeyEvent event) {
400 final int modifiers = event.getModifiers();
402 final int keyCode = event.getKeyCode();
403 final char keyChar = event.getKeyChar();
405 // System.out.println("Keycode:" + keyCode s+ ", keychar:" + keyChar);
407 if (KeyboardHelper.isAlt(modifiers))
410 if (KeyboardHelper.isCtrl(modifiers)) {
411 processCtrlCombinations(keyCode);
415 if (keyCode == KeyboardHelper.TAB) {
416 processTab(modifiers);
422 if (KeyboardHelper.isText(keyCode)) {
423 insertText(String.valueOf(keyChar));
427 // System.out.println("Co:" + String.valueOf(code) + " Ch:" +
428 // String.valueOf(keyChar));
430 if (KeyboardHelper.isShift(modifiers)) {
432 attemptSelectionStart:{
434 if (keyChar == 65535)
436 break attemptSelectionStart;
437 if (((keyChar >= 32) & (keyChar <= 128)) | (keyChar == 10)
438 | (keyChar == 8) | (keyChar == 9))
439 break attemptSelectionStart;
441 // System.out.println("Selection started:" + keyChar + " "
444 selectionStart = new TextPointer(cursorLocation);
445 selectionEnd = selectionStart;
452 if (keyCode == KeyboardHelper.HOME) {
453 cursorLocation.column = 0;
456 if (keyCode == KeyboardHelper.END) {
457 cursorLocation.column = page.getLineLength(cursorLocation.row);
461 // process cursor keys
462 if (keyCode == KeyboardHelper.DOWN) {
464 cursorLocation.row++;
468 if (keyCode == KeyboardHelper.UP) {
470 cursorLocation.row--;
474 if (keyCode == KeyboardHelper.RIGHT) {
475 cursorLocation.column++;
479 if (keyCode == KeyboardHelper.LEFT) {
480 cursorLocation.column--;
484 if (keyCode == KeyboardHelper.PGDOWN) {
485 cursorLocation.row += textCanvas.getSize().row;
490 if (keyCode == KeyboardHelper.PGUP) {
491 cursorLocation.row -= textCanvas.getSize().row;
498 private void processTab(final int modifiers) {
499 if (KeyboardHelper.isShift(modifiers)) {
500 if (selectionStart.compareTo(selectionEnd) != 0) {
501 // dedent multiple lines
502 ensureSelectionOrder();
506 // check that identation is possible
507 for (int y = selectionStart.row; y < selectionEnd.row; y++) {
508 final TextLine textLine = page.getLine(y);
510 if (!textLine.isEmpty())
511 if (textLine.getIdent() < 4)
512 break identSelection;
515 for (int y = selectionStart.row; y < selectionEnd.row; y++)
516 page.getLine(y).cutFromBeginning(4);
519 // dedent current line
520 final TextLine textLine = page.getLine(cursorLocation.row);
522 if (cursorLocation.column >= 4)
523 if (textLine.isEmpty())
524 cursorLocation.column -= 4;
525 else if (textLine.getIdent() >= 4) {
526 cursorLocation.column -= 4;
527 textLine.cutFromBeginning(4);
534 } else if (selectionStart.compareTo(selectionEnd) != 0) {
535 // ident multiple lines
536 ensureSelectionOrder();
537 for (int y = selectionStart.row; y < selectionEnd.row; y++)
538 page.getLine(y).addIdent(4);
544 public void repaintPage() {
546 final int chXe = textCanvas.getSize().column + 2;
547 final int chYe = textCanvas.getSize().row + 2;
549 for (int cy = 0; cy < chYe; cy++)
550 for (int cx = 0; cx < chXe; cx++) {
551 final boolean isTabMargin = ((cx + scrolledCharacters) % 4) == 0;
553 if ((cx == (cursorLocation.column - scrolledCharacters))
554 & (cy == (cursorLocation.row - scrolledLines))) {
556 textCanvas.setBackgroundColor(colorConfig.cursorBack);
557 textCanvas.setForegroundColor(colorConfig.cursorText);
558 } else if (new TextPointer(cy + scrolledLines, cx).isBetween(
559 selectionStart, selectionEnd)) {
561 textCanvas.setBackgroundColor(colorConfig.selectedBack);
562 textCanvas.setForegroundColor(colorConfig.selectedText);
565 textCanvas.setBackgroundColor(colorConfig.normalBack);
566 textCanvas.setForegroundColor(colorConfig.normalText);
570 .setBackgroundColor(colorConfig.tabulatorBack);
574 final char charUnderCursor = page.getChar(cy + scrolledLines,
575 cx + scrolledCharacters);
577 textCanvas.putChar(cy, cx, charUnderCursor);
582 public void repaintRow(final int rowNumber) {
587 private void repaintWhatNeeded() {
594 dirtyRows.forEach(this::repaintRow);
598 // public void setCaret(final int x, final int y) {
599 // selecting = false;
600 // cursorLocation.column = (x / characterWidth) + scrolledCharacters;
601 // cursorLocation.row = (y / characterHeight) + scrolledLines;
606 * Scroll full page to given amount of lines or charancters.
608 public void scroll(final int charactersToScroll, final int linesToScroll) {
609 scrolledLines += linesToScroll;
610 scrolledCharacters += charactersToScroll;
612 if (scrolledLines < 0)
615 if (scrolledCharacters < 0)
616 scrolledCharacters = 0;
621 public void setText(final String text) {
622 // System.out.println("Set text:" + text);
623 cursorLocation = new TextPointer(0, 0);
624 scrolledCharacters = 0;
626 selectionStart = new TextPointer(0, 0);
627 selectionEnd = new TextPointer(0, 0);