2 * Sixth 3D engine. Copyright ©2012-2016, 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.geometry.Transform;
14 import eu.svjatoslav.sixth.e3d.gui.GuiComponent;
15 import eu.svjatoslav.sixth.e3d.gui.TextPointer;
16 import eu.svjatoslav.sixth.e3d.gui.ViewContext;
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 ViewContext viewContext, final Point2D size) {
44 super(transform, viewContext, 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 ViewContext viewContext) {
253 super.keyPressed(event, viewContext);
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;
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.getModifiers();
403 final int keyCode = event.getKeyCode();
404 final char keyChar = event.getKeyChar();
406 // System.out.println("Keycode:" + keyCode s+ ", keychar:" + keyChar);
408 if (KeyboardHelper.isAlt(modifiers))
411 if (KeyboardHelper.isCtrl(modifiers)) {
412 processCtrlCombinations(keyCode);
416 if (keyCode == KeyboardHelper.TAB) {
417 processTab(modifiers);
423 if (KeyboardHelper.isText(keyCode)) {
424 insertText(String.valueOf(keyChar));
428 // System.out.println("Co:" + String.valueOf(code) + " Ch:" +
429 // String.valueOf(keyChar));
431 if (KeyboardHelper.isShift(modifiers)) {
433 attemptSelectionStart:{
435 if (keyChar == 65535)
437 break attemptSelectionStart;
438 if (((keyChar >= 32) & (keyChar <= 128)) | (keyChar == 10)
439 | (keyChar == 8) | (keyChar == 9))
440 break attemptSelectionStart;
442 // System.out.println("Selection started:" + keyChar + " "
445 selectionStart = new TextPointer(cursorLocation);
446 selectionEnd = selectionStart;
453 if (keyCode == KeyboardHelper.HOME) {
454 cursorLocation.column = 0;
457 if (keyCode == KeyboardHelper.END) {
458 cursorLocation.column = page.getLineLength(cursorLocation.row);
462 // process cursor keys
463 if (keyCode == KeyboardHelper.DOWN) {
465 cursorLocation.row++;
469 if (keyCode == KeyboardHelper.UP) {
471 cursorLocation.row--;
475 if (keyCode == KeyboardHelper.RIGHT) {
476 cursorLocation.column++;
480 if (keyCode == KeyboardHelper.LEFT) {
481 cursorLocation.column--;
485 if (keyCode == KeyboardHelper.PGDOWN) {
486 cursorLocation.row += textCanvas.getSize().row;
491 if (keyCode == KeyboardHelper.PGUP) {
492 cursorLocation.row -= textCanvas.getSize().row;
499 private void processTab(final int modifiers) {
500 if (KeyboardHelper.isShift(modifiers)) {
501 if (selectionStart.compareTo(selectionEnd) != 0) {
502 // dedent multiple lines
503 ensureSelectionOrder();
507 // check that identation is possible
508 for (int y = selectionStart.row; y < selectionEnd.row; y++) {
509 final TextLine textLine = page.getLine(y);
511 if (!textLine.isEmpty())
512 if (textLine.getIdent() < 4)
513 break identSelection;
516 for (int y = selectionStart.row; y < selectionEnd.row; y++)
517 page.getLine(y).cutFromBeginning(4);
520 // dedent current line
521 final TextLine textLine = page.getLine(cursorLocation.row);
523 if (cursorLocation.column >= 4)
524 if (textLine.isEmpty())
525 cursorLocation.column -= 4;
526 else if (textLine.getIdent() >= 4) {
527 cursorLocation.column -= 4;
528 textLine.cutFromBeginning(4);
535 } else if (selectionStart.compareTo(selectionEnd) != 0) {
536 // ident multiple lines
537 ensureSelectionOrder();
538 for (int y = selectionStart.row; y < selectionEnd.row; y++)
539 page.getLine(y).addIdent(4);
545 public void repaintPage() {
547 final int chXe = textCanvas.getSize().column + 2;
548 final int chYe = textCanvas.getSize().row + 2;
550 for (int cy = 0; cy < chYe; cy++)
551 for (int cx = 0; cx < chXe; cx++) {
552 final boolean isTabMargin = ((cx + scrolledCharacters) % 4) == 0;
554 if ((cx == (cursorLocation.column - scrolledCharacters))
555 & (cy == (cursorLocation.row - scrolledLines))) {
557 textCanvas.setBackgroundColor(colorConfig.cursorBack);
558 textCanvas.setForegroundColor(colorConfig.cursorText);
559 } else if (new TextPointer(cy + scrolledLines, cx).isBetween(
560 selectionStart, selectionEnd)) {
562 textCanvas.setBackgroundColor(colorConfig.selectedBack);
563 textCanvas.setForegroundColor(colorConfig.selectedText);
566 textCanvas.setBackgroundColor(colorConfig.normalBack);
567 textCanvas.setForegroundColor(colorConfig.normalText);
571 .setBackgroundColor(colorConfig.tabulatorBack);
575 final char charUnderCursor = page.getChar(cy + scrolledLines,
576 cx + scrolledCharacters);
578 textCanvas.putChar(cy, cx, charUnderCursor);
583 public void repaintRow(final int rowNumber) {
588 private void repaintWhatNeeded() {
595 dirtyRows.forEach(this::repaintRow);
599 // public void setCaret(final int x, final int y) {
600 // selecting = false;
601 // cursorLocation.column = (x / characterWidth) + scrolledCharacters;
602 // cursorLocation.row = (y / characterHeight) + scrolledLines;
607 * Scroll full page to given amount of lines or charancters.
609 public void scroll(final int charactersToScroll, final int linesToScroll) {
610 scrolledLines += linesToScroll;
611 scrolledCharacters += charactersToScroll;
613 if (scrolledLines < 0)
616 if (scrolledCharacters < 0)
617 scrolledCharacters = 0;
622 public void setText(final String text) {
623 // System.out.println("Set text:" + text);
624 cursorLocation = new TextPointer(0, 0);
625 scrolledCharacters = 0;
627 selectionStart = new TextPointer(0, 0);
628 selectionEnd = new TextPointer(0, 0);