2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 package jexer
.backend
;
31 import java
.awt
.image
.BufferedImage
;
33 import jexer
.backend
.GlyphMaker
;
34 import jexer
.bits
.Cell
;
35 import jexer
.bits
.CellAttributes
;
36 import jexer
.bits
.Clipboard
;
37 import jexer
.bits
.GraphicsChars
;
38 import jexer
.bits
.StringUtils
;
41 * A logical screen composed of a 2D array of Cells.
43 public class LogicalScreen
implements Screen
{
45 // ------------------------------------------------------------------------
46 // Variables --------------------------------------------------------------
47 // ------------------------------------------------------------------------
50 * Width of the visible window.
55 * Height of the visible window.
60 * Drawing offset for x.
65 * Drawing offset for y.
70 * Ignore anything drawn right of clipRight.
72 private int clipRight
;
75 * Ignore anything drawn below clipBottom.
77 private int clipBottom
;
80 * Ignore anything drawn left of clipLeft.
85 * Ignore anything drawn above clipTop.
90 * The physical screen last sent out on flush().
92 protected Cell
[][] physical
;
95 * The logical screen being rendered to.
97 protected Cell
[][] logical
;
100 * Set if the user explicitly wants to redraw everything starting with a
101 * ECMATerminal.clearAll().
103 protected boolean reallyCleared
;
106 * If true, the cursor is visible and should be placed onscreen at
107 * (cursorX, cursorY) during a call to flushPhysical().
109 protected boolean cursorVisible
;
112 * Cursor X position if visible.
114 protected int cursorX
;
117 * Cursor Y position if visible.
119 protected int cursorY
;
122 * The last used height of a character cell in pixels, only used for
125 private int lastTextHeight
= -1;
128 * The glyph drawer for full-width chars.
130 private GlyphMaker glyphMaker
= null;
132 // ------------------------------------------------------------------------
133 // Constructors -----------------------------------------------------------
134 // ------------------------------------------------------------------------
137 * Public constructor. Sets everything to not-bold, white-on-black.
139 protected LogicalScreen() {
146 reallocate(width
, height
);
149 // ------------------------------------------------------------------------
150 // Screen -----------------------------------------------------------------
151 // ------------------------------------------------------------------------
154 * Get the width of a character cell in pixels.
156 * @return the width in pixels of a character cell
158 public int getTextWidth() {
159 // Default width is 16 pixels.
164 * Get the height of a character cell in pixels.
166 * @return the height in pixels of a character cell
168 public int getTextHeight() {
169 // Default height is 20 pixels.
174 * Set drawing offset for x.
176 * @param offsetX new drawing offset
178 public final void setOffsetX(final int offsetX
) {
179 this.offsetX
= offsetX
;
183 * Set drawing offset for y.
185 * @param offsetY new drawing offset
187 public final void setOffsetY(final int offsetY
) {
188 this.offsetY
= offsetY
;
192 * Get right drawing clipping boundary.
194 * @return drawing boundary
196 public final int getClipRight() {
201 * Set right drawing clipping boundary.
203 * @param clipRight new boundary
205 public final void setClipRight(final int clipRight
) {
206 this.clipRight
= clipRight
;
210 * Get bottom drawing clipping boundary.
212 * @return drawing boundary
214 public final int getClipBottom() {
219 * Set bottom drawing clipping boundary.
221 * @param clipBottom new boundary
223 public final void setClipBottom(final int clipBottom
) {
224 this.clipBottom
= clipBottom
;
228 * Get left drawing clipping boundary.
230 * @return drawing boundary
232 public final int getClipLeft() {
237 * Set left drawing clipping boundary.
239 * @param clipLeft new boundary
241 public final void setClipLeft(final int clipLeft
) {
242 this.clipLeft
= clipLeft
;
246 * Get top drawing clipping boundary.
248 * @return drawing boundary
250 public final int getClipTop() {
255 * Set top drawing clipping boundary.
257 * @param clipTop new boundary
259 public final void setClipTop(final int clipTop
) {
260 this.clipTop
= clipTop
;
266 * @return if true, the logical screen is not in sync with the physical
269 public final boolean isDirty() {
270 for (int x
= 0; x
< width
; x
++) {
271 for (int y
= 0; y
< height
; y
++) {
272 if (!logical
[x
][y
].equals(physical
[x
][y
])) {
275 if (logical
[x
][y
].isBlink()) {
276 // Blinking screens are always dirty. There is
277 // opportunity for a Netscape blink tag joke here...
287 * Get the attributes at one location.
289 * @param x column coordinate. 0 is the left-most column.
290 * @param y row coordinate. 0 is the top-most row.
291 * @return attributes at (x, y)
293 public final CellAttributes
getAttrXY(final int x
, final int y
) {
294 CellAttributes attr
= new CellAttributes();
295 if ((x
>= 0) && (x
< width
) && (y
>= 0) && (y
< height
)) {
296 attr
.setTo(logical
[x
][y
]);
302 * Get the cell at one location.
304 * @param x column coordinate. 0 is the left-most column.
305 * @param y row coordinate. 0 is the top-most row.
306 * @return the character + attributes
308 public Cell
getCharXY(final int x
, final int y
) {
309 Cell cell
= new Cell();
310 if ((x
>= 0) && (x
< width
) && (y
>= 0) && (y
< height
)) {
311 cell
.setTo(logical
[x
][y
]);
317 * Set the attributes at one location.
319 * @param x column coordinate. 0 is the left-most column.
320 * @param y row coordinate. 0 is the top-most row.
321 * @param attr attributes to use (bold, foreColor, backColor)
323 public final void putAttrXY(final int x
, final int y
,
324 final CellAttributes attr
) {
326 putAttrXY(x
, y
, attr
, true);
330 * Set the attributes at one location.
332 * @param x column coordinate. 0 is the left-most column.
333 * @param y row coordinate. 0 is the top-most row.
334 * @param attr attributes to use (bold, foreColor, backColor)
335 * @param clip if true, honor clipping/offset
337 public final void putAttrXY(final int x
, final int y
,
338 final CellAttributes attr
, final boolean clip
) {
355 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
356 logical
[X
][Y
].setTo(attr
);
358 // If this happens to be the cursor position, make the position
360 if ((cursorX
== X
) && (cursorY
== Y
)) {
361 physical
[cursorX
][cursorY
].unset();
362 unsetImageRow(cursorY
);
368 * Fill the entire screen with one character with attributes.
370 * @param ch character to draw
371 * @param attr attributes to use (bold, foreColor, backColor)
373 public final void putAll(final int ch
, final CellAttributes attr
) {
375 for (int x
= 0; x
< width
; x
++) {
376 for (int y
= 0; y
< height
; y
++) {
377 putCharXY(x
, y
, ch
, attr
);
383 * Render one character with attributes.
385 * @param x column coordinate. 0 is the left-most column.
386 * @param y row coordinate. 0 is the top-most row.
387 * @param ch character + attributes to draw
389 public final void putCharXY(final int x
, final int y
, final Cell ch
) {
398 if ((StringUtils
.width(ch
.getChar()) == 2) && (!ch
.isImage())) {
399 putFullwidthCharXY(x
, y
, ch
);
406 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
408 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
410 // Do not put control characters on the display
412 assert (ch
.getChar() >= 0x20);
413 assert (ch
.getChar() != 0x7F);
415 logical
[X
][Y
].setTo(ch
);
417 // If this happens to be the cursor position, make the position
419 if ((cursorX
== X
) && (cursorY
== Y
)) {
420 physical
[cursorX
][cursorY
].unset();
421 unsetImageRow(cursorY
);
427 * Render one character with attributes.
429 * @param x column coordinate. 0 is the left-most column.
430 * @param y row coordinate. 0 is the top-most row.
431 * @param ch character to draw
432 * @param attr attributes to use (bold, foreColor, backColor)
434 public final void putCharXY(final int x
, final int y
, final int ch
,
435 final CellAttributes attr
) {
445 if (StringUtils
.width(ch
) == 2) {
446 putFullwidthCharXY(x
, y
, ch
, attr
);
453 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
455 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
457 // Do not put control characters on the display
461 logical
[X
][Y
].setTo(attr
);
462 logical
[X
][Y
].setChar(ch
);
464 // If this happens to be the cursor position, make the position
466 if ((cursorX
== X
) && (cursorY
== Y
)) {
467 physical
[cursorX
][cursorY
].unset();
468 unsetImageRow(cursorY
);
474 * Render one character without changing the underlying attributes.
476 * @param x column coordinate. 0 is the left-most column.
477 * @param y row coordinate. 0 is the top-most row.
478 * @param ch character to draw
480 public final void putCharXY(final int x
, final int y
, final int ch
) {
489 if (StringUtils
.width(ch
) == 2) {
490 putFullwidthCharXY(x
, y
, ch
);
497 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
499 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
500 logical
[X
][Y
].setChar(ch
);
502 // If this happens to be the cursor position, make the position
504 if ((cursorX
== X
) && (cursorY
== Y
)) {
505 physical
[cursorX
][cursorY
].unset();
506 unsetImageRow(cursorY
);
512 * Render a string. Does not wrap if the string exceeds the line.
514 * @param x column coordinate. 0 is the left-most column.
515 * @param y row coordinate. 0 is the top-most row.
516 * @param str string to draw
517 * @param attr attributes to use (bold, foreColor, backColor)
519 public final void putStringXY(final int x
, final int y
, final String str
,
520 final CellAttributes attr
) {
523 for (int j
= 0; j
< str
.length();) {
524 int ch
= str
.codePointAt(j
);
525 j
+= Character
.charCount(ch
);
526 putCharXY(i
, y
, ch
, attr
);
527 i
+= StringUtils
.width(ch
);
535 * Render a string without changing the underlying attribute. Does not
536 * wrap if the string exceeds the line.
538 * @param x column coordinate. 0 is the left-most column.
539 * @param y row coordinate. 0 is the top-most row.
540 * @param str string to draw
542 public final void putStringXY(final int x
, final int y
, final String str
) {
545 for (int j
= 0; j
< str
.length();) {
546 int ch
= str
.codePointAt(j
);
547 j
+= Character
.charCount(ch
);
549 i
+= StringUtils
.width(ch
);
557 * Draw a vertical line from (x, y) to (x, y + n).
559 * @param x column coordinate. 0 is the left-most column.
560 * @param y row coordinate. 0 is the top-most row.
561 * @param n number of characters to draw
562 * @param ch character to draw
563 * @param attr attributes to use (bold, foreColor, backColor)
565 public final void vLineXY(final int x
, final int y
, final int n
,
566 final int ch
, final CellAttributes attr
) {
568 for (int i
= y
; i
< y
+ n
; i
++) {
569 putCharXY(x
, i
, ch
, attr
);
574 * Draw a horizontal line from (x, y) to (x + n, y).
576 * @param x column coordinate. 0 is the left-most column.
577 * @param y row coordinate. 0 is the top-most row.
578 * @param n number of characters to draw
579 * @param ch character to draw
580 * @param attr attributes to use (bold, foreColor, backColor)
582 public final void hLineXY(final int x
, final int y
, final int n
,
583 final int ch
, final CellAttributes attr
) {
585 for (int i
= x
; i
< x
+ n
; i
++) {
586 putCharXY(i
, y
, ch
, attr
);
591 * Change the width. Everything on-screen will be destroyed and must be
594 * @param width new screen width
596 public final synchronized void setWidth(final int width
) {
597 reallocate(width
, this.height
);
601 * Change the height. Everything on-screen will be destroyed and must be
604 * @param height new screen height
606 public final synchronized void setHeight(final int height
) {
607 reallocate(this.width
, height
);
611 * Change the width and height. Everything on-screen will be destroyed
612 * and must be redrawn.
614 * @param width new screen width
615 * @param height new screen height
617 public final void setDimensions(final int width
, final int height
) {
618 reallocate(width
, height
);
623 * Resize the physical screen to match the logical screen dimensions.
625 public void resizeToScreen() {
626 // Subclasses are expected to override this.
632 * @return current screen height
634 public final synchronized int getHeight() {
641 * @return current screen width
643 public final synchronized int getWidth() {
648 * Reset screen to not-bold, white-on-black. Also flushes the offset and
651 public final synchronized void reset() {
652 for (int row
= 0; row
< height
; row
++) {
653 for (int col
= 0; col
< width
; col
++) {
654 logical
[col
][row
].reset();
661 * Flush the offset and clip variables.
663 public final void resetClipping() {
673 * Clear the logical screen.
675 public final void clear() {
680 * Draw a box with a border and empty background.
682 * @param left left column of box. 0 is the left-most column.
683 * @param top top row of the box. 0 is the top-most row.
684 * @param right right column of box
685 * @param bottom bottom row of the box
686 * @param border attributes to use for the border
687 * @param background attributes to use for the background
689 public final void drawBox(final int left
, final int top
,
690 final int right
, final int bottom
,
691 final CellAttributes border
, final CellAttributes background
) {
693 drawBox(left
, top
, right
, bottom
, border
, background
, 1, false);
697 * Draw a box with a border and empty background.
699 * @param left left column of box. 0 is the left-most column.
700 * @param top top row of the box. 0 is the top-most row.
701 * @param right right column of box
702 * @param bottom bottom row of the box
703 * @param border attributes to use for the border
704 * @param background attributes to use for the background
705 * @param borderType if 1, draw a single-line border; if 2, draw a
706 * double-line border; if 3, draw double-line top/bottom edges and
707 * single-line left/right edges (like Qmodem)
708 * @param shadow if true, draw a "shadow" on the box
710 public final void drawBox(final int left
, final int top
,
711 final int right
, final int bottom
,
712 final CellAttributes border
, final CellAttributes background
,
713 final int borderType
, final boolean shadow
) {
715 int boxWidth
= right
- left
;
716 int boxHeight
= bottom
- top
;
725 switch (borderType
) {
727 cTopLeft
= GraphicsChars
.ULCORNER
;
728 cTopRight
= GraphicsChars
.URCORNER
;
729 cBottomLeft
= GraphicsChars
.LLCORNER
;
730 cBottomRight
= GraphicsChars
.LRCORNER
;
731 cHSide
= GraphicsChars
.SINGLE_BAR
;
732 cVSide
= GraphicsChars
.WINDOW_SIDE
;
736 cTopLeft
= GraphicsChars
.WINDOW_LEFT_TOP_DOUBLE
;
737 cTopRight
= GraphicsChars
.WINDOW_RIGHT_TOP_DOUBLE
;
738 cBottomLeft
= GraphicsChars
.WINDOW_LEFT_BOTTOM_DOUBLE
;
739 cBottomRight
= GraphicsChars
.WINDOW_RIGHT_BOTTOM_DOUBLE
;
740 cHSide
= GraphicsChars
.DOUBLE_BAR
;
741 cVSide
= GraphicsChars
.WINDOW_SIDE_DOUBLE
;
745 cTopLeft
= GraphicsChars
.WINDOW_LEFT_TOP
;
746 cTopRight
= GraphicsChars
.WINDOW_RIGHT_TOP
;
747 cBottomLeft
= GraphicsChars
.WINDOW_LEFT_BOTTOM
;
748 cBottomRight
= GraphicsChars
.WINDOW_RIGHT_BOTTOM
;
749 cHSide
= GraphicsChars
.WINDOW_TOP
;
750 cVSide
= GraphicsChars
.WINDOW_SIDE
;
753 throw new IllegalArgumentException("Invalid border type: "
757 // Place the corner characters
758 putCharXY(left
, top
, cTopLeft
, border
);
759 putCharXY(left
+ boxWidth
- 1, top
, cTopRight
, border
);
760 putCharXY(left
, top
+ boxHeight
- 1, cBottomLeft
, border
);
761 putCharXY(left
+ boxWidth
- 1, top
+ boxHeight
- 1, cBottomRight
,
764 // Draw the box lines
765 hLineXY(left
+ 1, top
, boxWidth
- 2, cHSide
, border
);
766 vLineXY(left
, top
+ 1, boxHeight
- 2, cVSide
, border
);
767 hLineXY(left
+ 1, top
+ boxHeight
- 1, boxWidth
- 2, cHSide
, border
);
768 vLineXY(left
+ boxWidth
- 1, top
+ 1, boxHeight
- 2, cVSide
, border
);
770 // Fill in the interior background
771 for (int i
= 1; i
< boxHeight
- 1; i
++) {
772 hLineXY(1 + left
, i
+ top
, boxWidth
- 2, ' ', background
);
777 drawBoxShadow(left
, top
, right
, bottom
);
784 * @param left left column of box. 0 is the left-most column.
785 * @param top top row of the box. 0 is the top-most row.
786 * @param right right column of box
787 * @param bottom bottom row of the box
789 public final void drawBoxShadow(final int left
, final int top
,
790 final int right
, final int bottom
) {
794 int boxWidth
= right
- left
;
795 int boxHeight
= bottom
- top
;
796 CellAttributes shadowAttr
= new CellAttributes();
798 // Shadows do not honor clipping but they DO honor offset.
799 int oldClipRight
= clipRight
;
800 int oldClipBottom
= clipBottom
;
801 // When offsetX or offsetY go negative, we need to increase the clip
803 clipRight
= width
- offsetX
;
804 clipBottom
= height
- offsetY
;
806 for (int i
= 0; i
< boxHeight
; i
++) {
807 Cell cell
= getCharXY(offsetX
+ boxLeft
+ boxWidth
,
808 offsetY
+ boxTop
+ 1 + i
);
809 if (cell
.getWidth() == Cell
.Width
.SINGLE
) {
810 putAttrXY(boxLeft
+ boxWidth
, boxTop
+ 1 + i
, shadowAttr
);
812 putCharXY(boxLeft
+ boxWidth
, boxTop
+ 1 + i
, ' ', shadowAttr
);
814 cell
= getCharXY(offsetX
+ boxLeft
+ boxWidth
+ 1,
815 offsetY
+ boxTop
+ 1 + i
);
816 if (cell
.getWidth() == Cell
.Width
.SINGLE
) {
817 putAttrXY(boxLeft
+ boxWidth
+ 1, boxTop
+ 1 + i
, shadowAttr
);
819 putCharXY(boxLeft
+ boxWidth
+ 1, boxTop
+ 1 + i
, ' ',
823 for (int i
= 0; i
< boxWidth
; i
++) {
824 Cell cell
= getCharXY(offsetX
+ boxLeft
+ 2 + i
,
825 offsetY
+ boxTop
+ boxHeight
);
826 if (cell
.getWidth() == Cell
.Width
.SINGLE
) {
827 putAttrXY(boxLeft
+ 2 + i
, boxTop
+ boxHeight
, shadowAttr
);
829 putCharXY(boxLeft
+ 2 + i
, boxTop
+ boxHeight
, ' ', shadowAttr
);
832 clipRight
= oldClipRight
;
833 clipBottom
= oldClipBottom
;
837 * Default implementation does nothing.
839 public void flushPhysical() {}
842 * Put the cursor at (x,y).
844 * @param visible if true, the cursor should be visible
845 * @param x column coordinate to put the cursor on
846 * @param y row coordinate to put the cursor on
848 public void putCursor(final boolean visible
, final int x
, final int y
) {
851 && (cursorY
<= height
- 1)
852 && (cursorX
<= width
- 1)
854 // Make the current cursor position dirty
855 physical
[cursorX
][cursorY
].unset();
856 unsetImageRow(cursorY
);
859 cursorVisible
= visible
;
867 public final void hideCursor() {
868 cursorVisible
= false;
872 * Get the cursor visibility.
874 * @return true if the cursor is visible
876 public boolean isCursorVisible() {
877 return cursorVisible
;
881 * Get the cursor X position.
883 * @return the cursor x column position
885 public int getCursorX() {
890 * Get the cursor Y position.
892 * @return the cursor y row position
894 public int getCursorY() {
899 * Set the window title. Default implementation does nothing.
901 * @param title the new title
903 public void setTitle(final String title
) {}
905 // ------------------------------------------------------------------------
906 // LogicalScreen ----------------------------------------------------------
907 // ------------------------------------------------------------------------
910 * Reallocate screen buffers.
912 * @param width new width
913 * @param height new height
915 private synchronized void reallocate(final int width
, final int height
) {
916 if (logical
!= null) {
917 for (int row
= 0; row
< this.height
; row
++) {
918 for (int col
= 0; col
< this.width
; col
++) {
919 logical
[col
][row
] = null;
924 logical
= new Cell
[width
][height
];
925 if (physical
!= null) {
926 for (int row
= 0; row
< this.height
; row
++) {
927 for (int col
= 0; col
< this.width
; col
++) {
928 physical
[col
][row
] = null;
933 physical
= new Cell
[width
][height
];
935 for (int row
= 0; row
< height
; row
++) {
936 for (int col
= 0; col
< width
; col
++) {
937 physical
[col
][row
] = new Cell();
938 logical
[col
][row
] = new Cell();
943 this.height
= height
;
950 reallyCleared
= true;
954 * Clear the physical screen.
956 public final void clearPhysical() {
957 for (int row
= 0; row
< height
; row
++) {
958 for (int col
= 0; col
< width
; col
++) {
959 physical
[col
][row
].unset();
965 * Unset every image cell on one row of the physical screen, forcing
966 * images on that row to be redrawn.
968 * @param y row coordinate. 0 is the top-most row.
970 public final void unsetImageRow(final int y
) {
971 if ((y
< 0) || (y
>= height
)) {
974 for (int x
= 0; x
< width
; x
++) {
975 if (logical
[x
][y
].isImage()) {
976 physical
[x
][y
].unset();
982 * Render one fullwidth cell.
984 * @param x column coordinate. 0 is the left-most column.
985 * @param y row coordinate. 0 is the top-most row.
986 * @param cell the cell to draw
988 public final void putFullwidthCharXY(final int x
, final int y
,
991 int cellWidth
= getTextWidth();
992 int cellHeight
= getTextHeight();
994 if (lastTextHeight
!= cellHeight
) {
995 glyphMaker
= GlyphMaker
.getInstance(cellHeight
);
996 lastTextHeight
= cellHeight
;
998 BufferedImage image
= glyphMaker
.getImage(cell
, cellWidth
* 2,
1000 BufferedImage leftImage
= image
.getSubimage(0, 0, cellWidth
,
1002 BufferedImage rightImage
= image
.getSubimage(cellWidth
, 0, cellWidth
,
1005 Cell left
= new Cell(cell
);
1006 left
.setImage(leftImage
);
1007 left
.setWidth(Cell
.Width
.LEFT
);
1008 putCharXY(x
, y
, left
);
1010 Cell right
= new Cell(cell
);
1011 right
.setImage(rightImage
);
1012 right
.setWidth(Cell
.Width
.RIGHT
);
1013 putCharXY(x
+ 1, y
, right
);
1017 * Render one fullwidth character with attributes.
1019 * @param x column coordinate. 0 is the left-most column.
1020 * @param y row coordinate. 0 is the top-most row.
1021 * @param ch character to draw
1022 * @param attr attributes to use (bold, foreColor, backColor)
1024 public final void putFullwidthCharXY(final int x
, final int y
,
1025 final int ch
, final CellAttributes attr
) {
1027 Cell cell
= new Cell(ch
, attr
);
1028 putFullwidthCharXY(x
, y
, cell
);
1032 * Render one fullwidth character with attributes.
1034 * @param x column coordinate. 0 is the left-most column.
1035 * @param y row coordinate. 0 is the top-most row.
1036 * @param ch character to draw
1038 public final void putFullwidthCharXY(final int x
, final int y
,
1041 Cell cell
= new Cell(ch
);
1042 cell
.setAttr(getAttrXY(x
, y
));
1043 putFullwidthCharXY(x
, y
, cell
);
1047 * Invert the cell color at a position, including both halves of a
1048 * double-width cell.
1050 * @param x column position
1051 * @param y row position
1053 public void invertCell(final int x
, final int y
) {
1054 invertCell(x
, y
, false);
1058 * Invert the cell color at a position.
1060 * @param x column position
1061 * @param y row position
1062 * @param onlyThisCell if true, only invert this cell, otherwise invert
1063 * both halves of a double-width cell if necessary
1065 public void invertCell(final int x
, final int y
,
1066 final boolean onlyThisCell
) {
1068 Cell cell
= getCharXY(x
, y
);
1069 if (cell
.isImage()) {
1072 if (cell
.getForeColorRGB() < 0) {
1073 cell
.setForeColor(cell
.getForeColor().invert());
1075 cell
.setForeColorRGB(cell
.getForeColorRGB() ^
0x00ffffff);
1077 if (cell
.getBackColorRGB() < 0) {
1078 cell
.setBackColor(cell
.getBackColor().invert());
1080 cell
.setBackColorRGB(cell
.getBackColorRGB() ^
0x00ffffff);
1082 putCharXY(x
, y
, cell
);
1083 if ((onlyThisCell
== true) || (cell
.getWidth() == Cell
.Width
.SINGLE
)) {
1087 // This cell is one half of a fullwidth glyph. Invert the other
1089 if (cell
.getWidth() == Cell
.Width
.LEFT
) {
1090 if (x
< width
- 1) {
1091 Cell rightHalf
= getCharXY(x
+ 1, y
);
1092 if (rightHalf
.getWidth() == Cell
.Width
.RIGHT
) {
1093 invertCell(x
+ 1, y
, true);
1098 if (cell
.getWidth() == Cell
.Width
.RIGHT
) {
1100 Cell leftHalf
= getCharXY(x
- 1, y
);
1101 if (leftHalf
.getWidth() == Cell
.Width
.LEFT
) {
1102 invertCell(x
- 1, y
, true);
1109 * Set a selection area on the screen.
1111 * @param x0 the starting X position of the selection
1112 * @param y0 the starting Y position of the selection
1113 * @param x1 the ending X position of the selection
1114 * @param y1 the ending Y position of the selection
1115 * @param rectangle if true, this is a rectangle select
1117 public void setSelection(final int x0
, final int y0
,
1118 final int x1
, final int y1
, final boolean rectangle
) {
1125 if (((x1
< x0
) && (y1
== y0
))
1128 // The user dragged from bottom-to-top and/or right-to-left.
1129 // Reverse the coordinates for the inverted section.
1136 for (int y
= startY
; y
<= endY
; y
++) {
1137 for (int x
= startX
; x
<= endX
; x
++) {
1142 if (endY
> startY
) {
1143 for (int x
= startX
; x
< width
; x
++) {
1144 invertCell(x
, startY
);
1146 for (int y
= startY
+ 1; y
< endY
; y
++) {
1147 for (int x
= 0; x
< width
; x
++) {
1151 for (int x
= 0; x
<= endX
; x
++) {
1152 invertCell(x
, endY
);
1155 assert (startY
== endY
);
1156 for (int x
= startX
; x
<= endX
; x
++) {
1157 invertCell(x
, startY
);
1164 * Copy the screen selection area to the clipboard.
1166 * @param clipboard the clipboard to use
1167 * @param x0 the starting X position of the selection
1168 * @param y0 the starting Y position of the selection
1169 * @param x1 the ending X position of the selection
1170 * @param y1 the ending Y position of the selection
1171 * @param rectangle if true, this is a rectangle select
1173 public void copySelection(final Clipboard clipboard
,
1174 final int x0
, final int y0
, final int x1
, final int y1
,
1175 final boolean rectangle
) {
1177 StringBuilder sb
= new StringBuilder();
1184 if (((x1
< x0
) && (y1
== y0
))
1187 // The user dragged from bottom-to-top and/or right-to-left.
1188 // Reverse the coordinates for the inverted section.
1195 for (int y
= startY
; y
<= endY
; y
++) {
1196 for (int x
= startX
; x
<= endX
; x
++) {
1197 sb
.append(Character
.toChars(getCharXY(x
, y
).getChar()));
1202 if (endY
> startY
) {
1203 for (int x
= startX
; x
< width
; x
++) {
1204 sb
.append(Character
.toChars(getCharXY(x
, startY
).getChar()));
1207 for (int y
= startY
+ 1; y
< endY
; y
++) {
1208 for (int x
= 0; x
< width
; x
++) {
1209 sb
.append(Character
.toChars(getCharXY(x
, y
).getChar()));
1213 for (int x
= 0; x
<= endX
; x
++) {
1214 sb
.append(Character
.toChars(getCharXY(x
, endY
).getChar()));
1217 assert (startY
== endY
);
1218 for (int x
= startX
; x
<= endX
; x
++) {
1219 sb
.append(Character
.toChars(getCharXY(x
, startY
).getChar()));
1223 clipboard
.copyText(sb
.toString());