Merge branch 'upstream' into subtree
[nikiroo-utils.git] / backend / LogicalScreen.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.backend;
30
31 import java.awt.image.BufferedImage;
32
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;
39
40 /**
41 * A logical screen composed of a 2D array of Cells.
42 */
43 public class LogicalScreen implements Screen {
44
45 // ------------------------------------------------------------------------
46 // Variables --------------------------------------------------------------
47 // ------------------------------------------------------------------------
48
49 /**
50 * Width of the visible window.
51 */
52 protected int width;
53
54 /**
55 * Height of the visible window.
56 */
57 protected int height;
58
59 /**
60 * Drawing offset for x.
61 */
62 private int offsetX;
63
64 /**
65 * Drawing offset for y.
66 */
67 private int offsetY;
68
69 /**
70 * Ignore anything drawn right of clipRight.
71 */
72 private int clipRight;
73
74 /**
75 * Ignore anything drawn below clipBottom.
76 */
77 private int clipBottom;
78
79 /**
80 * Ignore anything drawn left of clipLeft.
81 */
82 private int clipLeft;
83
84 /**
85 * Ignore anything drawn above clipTop.
86 */
87 private int clipTop;
88
89 /**
90 * The physical screen last sent out on flush().
91 */
92 protected Cell [][] physical;
93
94 /**
95 * The logical screen being rendered to.
96 */
97 protected Cell [][] logical;
98
99 /**
100 * Set if the user explicitly wants to redraw everything starting with a
101 * ECMATerminal.clearAll().
102 */
103 protected boolean reallyCleared;
104
105 /**
106 * If true, the cursor is visible and should be placed onscreen at
107 * (cursorX, cursorY) during a call to flushPhysical().
108 */
109 protected boolean cursorVisible;
110
111 /**
112 * Cursor X position if visible.
113 */
114 protected int cursorX;
115
116 /**
117 * Cursor Y position if visible.
118 */
119 protected int cursorY;
120
121 /**
122 * The last used height of a character cell in pixels, only used for
123 * full-width chars.
124 */
125 private int lastTextHeight = -1;
126
127 /**
128 * The glyph drawer for full-width chars.
129 */
130 private GlyphMaker glyphMaker = null;
131
132 // ------------------------------------------------------------------------
133 // Constructors -----------------------------------------------------------
134 // ------------------------------------------------------------------------
135
136 /**
137 * Public constructor. Sets everything to not-bold, white-on-black.
138 */
139 protected LogicalScreen() {
140 offsetX = 0;
141 offsetY = 0;
142 width = 80;
143 height = 24;
144 logical = null;
145 physical = null;
146 reallocate(width, height);
147 }
148
149 // ------------------------------------------------------------------------
150 // Screen -----------------------------------------------------------------
151 // ------------------------------------------------------------------------
152
153 /**
154 * Get the width of a character cell in pixels.
155 *
156 * @return the width in pixels of a character cell
157 */
158 public int getTextWidth() {
159 // Default width is 16 pixels.
160 return 16;
161 }
162
163 /**
164 * Get the height of a character cell in pixels.
165 *
166 * @return the height in pixels of a character cell
167 */
168 public int getTextHeight() {
169 // Default height is 20 pixels.
170 return 20;
171 }
172
173 /**
174 * Set drawing offset for x.
175 *
176 * @param offsetX new drawing offset
177 */
178 public final void setOffsetX(final int offsetX) {
179 this.offsetX = offsetX;
180 }
181
182 /**
183 * Set drawing offset for y.
184 *
185 * @param offsetY new drawing offset
186 */
187 public final void setOffsetY(final int offsetY) {
188 this.offsetY = offsetY;
189 }
190
191 /**
192 * Get right drawing clipping boundary.
193 *
194 * @return drawing boundary
195 */
196 public final int getClipRight() {
197 return clipRight;
198 }
199
200 /**
201 * Set right drawing clipping boundary.
202 *
203 * @param clipRight new boundary
204 */
205 public final void setClipRight(final int clipRight) {
206 this.clipRight = clipRight;
207 }
208
209 /**
210 * Get bottom drawing clipping boundary.
211 *
212 * @return drawing boundary
213 */
214 public final int getClipBottom() {
215 return clipBottom;
216 }
217
218 /**
219 * Set bottom drawing clipping boundary.
220 *
221 * @param clipBottom new boundary
222 */
223 public final void setClipBottom(final int clipBottom) {
224 this.clipBottom = clipBottom;
225 }
226
227 /**
228 * Get left drawing clipping boundary.
229 *
230 * @return drawing boundary
231 */
232 public final int getClipLeft() {
233 return clipLeft;
234 }
235
236 /**
237 * Set left drawing clipping boundary.
238 *
239 * @param clipLeft new boundary
240 */
241 public final void setClipLeft(final int clipLeft) {
242 this.clipLeft = clipLeft;
243 }
244
245 /**
246 * Get top drawing clipping boundary.
247 *
248 * @return drawing boundary
249 */
250 public final int getClipTop() {
251 return clipTop;
252 }
253
254 /**
255 * Set top drawing clipping boundary.
256 *
257 * @param clipTop new boundary
258 */
259 public final void setClipTop(final int clipTop) {
260 this.clipTop = clipTop;
261 }
262
263 /**
264 * Get dirty flag.
265 *
266 * @return if true, the logical screen is not in sync with the physical
267 * screen
268 */
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])) {
273 return true;
274 }
275 if (logical[x][y].isBlink()) {
276 // Blinking screens are always dirty. There is
277 // opportunity for a Netscape blink tag joke here...
278 return true;
279 }
280 }
281 }
282
283 return false;
284 }
285
286 /**
287 * Get the attributes at one location.
288 *
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)
292 */
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]);
297 }
298 return attr;
299 }
300
301 /**
302 * Get the cell at one location.
303 *
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
307 */
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]);
312 }
313 return cell;
314 }
315
316 /**
317 * Set the attributes at one location.
318 *
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)
322 */
323 public final void putAttrXY(final int x, final int y,
324 final CellAttributes attr) {
325
326 putAttrXY(x, y, attr, true);
327 }
328
329 /**
330 * Set the attributes at one location.
331 *
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
336 */
337 public final void putAttrXY(final int x, final int y,
338 final CellAttributes attr, final boolean clip) {
339
340 int X = x;
341 int Y = y;
342
343 if (clip) {
344 if ((x < clipLeft)
345 || (x >= clipRight)
346 || (y < clipTop)
347 || (y >= clipBottom)
348 ) {
349 return;
350 }
351 X += offsetX;
352 Y += offsetY;
353 }
354
355 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
356 logical[X][Y].setTo(attr);
357
358 // If this happens to be the cursor position, make the position
359 // dirty.
360 if ((cursorX == X) && (cursorY == Y)) {
361 physical[cursorX][cursorY].unset();
362 unsetImageRow(cursorY);
363 }
364 }
365 }
366
367 /**
368 * Fill the entire screen with one character with attributes.
369 *
370 * @param ch character to draw
371 * @param attr attributes to use (bold, foreColor, backColor)
372 */
373 public final void putAll(final int ch, final CellAttributes attr) {
374
375 for (int x = 0; x < width; x++) {
376 for (int y = 0; y < height; y++) {
377 putCharXY(x, y, ch, attr);
378 }
379 }
380 }
381
382 /**
383 * Render one character with attributes.
384 *
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
388 */
389 public final void putCharXY(final int x, final int y, final Cell ch) {
390 if ((x < clipLeft)
391 || (x >= clipRight)
392 || (y < clipTop)
393 || (y >= clipBottom)
394 ) {
395 return;
396 }
397
398 if ((StringUtils.width(ch.getChar()) == 2) && (!ch.isImage())) {
399 putFullwidthCharXY(x, y, ch);
400 return;
401 }
402
403 int X = x + offsetX;
404 int Y = y + offsetY;
405
406 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
407
408 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
409
410 // Do not put control characters on the display
411 if (!ch.isImage()) {
412 assert (ch.getChar() >= 0x20);
413 assert (ch.getChar() != 0x7F);
414 }
415 logical[X][Y].setTo(ch);
416
417 // If this happens to be the cursor position, make the position
418 // dirty.
419 if ((cursorX == X) && (cursorY == Y)) {
420 physical[cursorX][cursorY].unset();
421 unsetImageRow(cursorY);
422 }
423 }
424 }
425
426 /**
427 * Render one character with attributes.
428 *
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)
433 */
434 public final void putCharXY(final int x, final int y, final int ch,
435 final CellAttributes attr) {
436
437 if ((x < clipLeft)
438 || (x >= clipRight)
439 || (y < clipTop)
440 || (y >= clipBottom)
441 ) {
442 return;
443 }
444
445 if (StringUtils.width(ch) == 2) {
446 putFullwidthCharXY(x, y, ch, attr);
447 return;
448 }
449
450 int X = x + offsetX;
451 int Y = y + offsetY;
452
453 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
454
455 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
456
457 // Do not put control characters on the display
458 assert (ch >= 0x20);
459 assert (ch != 0x7F);
460
461 logical[X][Y].setTo(attr);
462 logical[X][Y].setChar(ch);
463
464 // If this happens to be the cursor position, make the position
465 // dirty.
466 if ((cursorX == X) && (cursorY == Y)) {
467 physical[cursorX][cursorY].unset();
468 unsetImageRow(cursorY);
469 }
470 }
471 }
472
473 /**
474 * Render one character without changing the underlying attributes.
475 *
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
479 */
480 public final void putCharXY(final int x, final int y, final int ch) {
481 if ((x < clipLeft)
482 || (x >= clipRight)
483 || (y < clipTop)
484 || (y >= clipBottom)
485 ) {
486 return;
487 }
488
489 if (StringUtils.width(ch) == 2) {
490 putFullwidthCharXY(x, y, ch);
491 return;
492 }
493
494 int X = x + offsetX;
495 int Y = y + offsetY;
496
497 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
498
499 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
500 logical[X][Y].setChar(ch);
501
502 // If this happens to be the cursor position, make the position
503 // dirty.
504 if ((cursorX == X) && (cursorY == Y)) {
505 physical[cursorX][cursorY].unset();
506 unsetImageRow(cursorY);
507 }
508 }
509 }
510
511 /**
512 * Render a string. Does not wrap if the string exceeds the line.
513 *
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)
518 */
519 public final void putStringXY(final int x, final int y, final String str,
520 final CellAttributes attr) {
521
522 int i = x;
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);
528 if (i == width) {
529 break;
530 }
531 }
532 }
533
534 /**
535 * Render a string without changing the underlying attribute. Does not
536 * wrap if the string exceeds the line.
537 *
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
541 */
542 public final void putStringXY(final int x, final int y, final String str) {
543
544 int i = x;
545 for (int j = 0; j < str.length();) {
546 int ch = str.codePointAt(j);
547 j += Character.charCount(ch);
548 putCharXY(i, y, ch);
549 i += StringUtils.width(ch);
550 if (i == width) {
551 break;
552 }
553 }
554 }
555
556 /**
557 * Draw a vertical line from (x, y) to (x, y + n).
558 *
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)
564 */
565 public final void vLineXY(final int x, final int y, final int n,
566 final int ch, final CellAttributes attr) {
567
568 for (int i = y; i < y + n; i++) {
569 putCharXY(x, i, ch, attr);
570 }
571 }
572
573 /**
574 * Draw a horizontal line from (x, y) to (x + n, y).
575 *
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)
581 */
582 public final void hLineXY(final int x, final int y, final int n,
583 final int ch, final CellAttributes attr) {
584
585 for (int i = x; i < x + n; i++) {
586 putCharXY(i, y, ch, attr);
587 }
588 }
589
590 /**
591 * Change the width. Everything on-screen will be destroyed and must be
592 * redrawn.
593 *
594 * @param width new screen width
595 */
596 public final synchronized void setWidth(final int width) {
597 reallocate(width, this.height);
598 }
599
600 /**
601 * Change the height. Everything on-screen will be destroyed and must be
602 * redrawn.
603 *
604 * @param height new screen height
605 */
606 public final synchronized void setHeight(final int height) {
607 reallocate(this.width, height);
608 }
609
610 /**
611 * Change the width and height. Everything on-screen will be destroyed
612 * and must be redrawn.
613 *
614 * @param width new screen width
615 * @param height new screen height
616 */
617 public final void setDimensions(final int width, final int height) {
618 reallocate(width, height);
619 resizeToScreen();
620 }
621
622 /**
623 * Resize the physical screen to match the logical screen dimensions.
624 */
625 public void resizeToScreen() {
626 // Subclasses are expected to override this.
627 }
628
629 /**
630 * Get the height.
631 *
632 * @return current screen height
633 */
634 public final synchronized int getHeight() {
635 return this.height;
636 }
637
638 /**
639 * Get the width.
640 *
641 * @return current screen width
642 */
643 public final synchronized int getWidth() {
644 return this.width;
645 }
646
647 /**
648 * Reset screen to not-bold, white-on-black. Also flushes the offset and
649 * clip variables.
650 */
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();
655 }
656 }
657 resetClipping();
658 }
659
660 /**
661 * Flush the offset and clip variables.
662 */
663 public final void resetClipping() {
664 offsetX = 0;
665 offsetY = 0;
666 clipLeft = 0;
667 clipTop = 0;
668 clipRight = width;
669 clipBottom = height;
670 }
671
672 /**
673 * Clear the logical screen.
674 */
675 public final void clear() {
676 reset();
677 }
678
679 /**
680 * Draw a box with a border and empty background.
681 *
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
688 */
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) {
692
693 drawBox(left, top, right, bottom, border, background, 1, false);
694 }
695
696 /**
697 * Draw a box with a border and empty background.
698 *
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
709 */
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) {
714
715 int boxWidth = right - left;
716 int boxHeight = bottom - top;
717
718 char cTopLeft;
719 char cTopRight;
720 char cBottomLeft;
721 char cBottomRight;
722 char cHSide;
723 char cVSide;
724
725 switch (borderType) {
726 case 1:
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;
733 break;
734
735 case 2:
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;
742 break;
743
744 case 3:
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;
751 break;
752 default:
753 throw new IllegalArgumentException("Invalid border type: "
754 + borderType);
755 }
756
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,
762 border);
763
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);
769
770 // Fill in the interior background
771 for (int i = 1; i < boxHeight - 1; i++) {
772 hLineXY(1 + left, i + top, boxWidth - 2, ' ', background);
773 }
774
775 if (shadow) {
776 // Draw a shadow
777 drawBoxShadow(left, top, right, bottom);
778 }
779 }
780
781 /**
782 * Draw a box shadow.
783 *
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
788 */
789 public final void drawBoxShadow(final int left, final int top,
790 final int right, final int bottom) {
791
792 int boxTop = top;
793 int boxLeft = left;
794 int boxWidth = right - left;
795 int boxHeight = bottom - top;
796 CellAttributes shadowAttr = new CellAttributes();
797
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
802 // bounds.
803 clipRight = width - offsetX;
804 clipBottom = height - offsetY;
805
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);
811 } else {
812 putCharXY(boxLeft + boxWidth, boxTop + 1 + i, ' ', shadowAttr);
813 }
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);
818 } else {
819 putCharXY(boxLeft + boxWidth + 1, boxTop + 1 + i, ' ',
820 shadowAttr);
821 }
822 }
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);
828 } else {
829 putCharXY(boxLeft + 2 + i, boxTop + boxHeight, ' ', shadowAttr);
830 }
831 }
832 clipRight = oldClipRight;
833 clipBottom = oldClipBottom;
834 }
835
836 /**
837 * Default implementation does nothing.
838 */
839 public void flushPhysical() {}
840
841 /**
842 * Put the cursor at (x,y).
843 *
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
847 */
848 public void putCursor(final boolean visible, final int x, final int y) {
849 if ((cursorY >= 0)
850 && (cursorX >= 0)
851 && (cursorY <= height - 1)
852 && (cursorX <= width - 1)
853 ) {
854 // Make the current cursor position dirty
855 physical[cursorX][cursorY].unset();
856 unsetImageRow(cursorY);
857 }
858
859 cursorVisible = visible;
860 cursorX = x;
861 cursorY = y;
862 }
863
864 /**
865 * Hide the cursor.
866 */
867 public final void hideCursor() {
868 cursorVisible = false;
869 }
870
871 /**
872 * Get the cursor visibility.
873 *
874 * @return true if the cursor is visible
875 */
876 public boolean isCursorVisible() {
877 return cursorVisible;
878 }
879
880 /**
881 * Get the cursor X position.
882 *
883 * @return the cursor x column position
884 */
885 public int getCursorX() {
886 return cursorX;
887 }
888
889 /**
890 * Get the cursor Y position.
891 *
892 * @return the cursor y row position
893 */
894 public int getCursorY() {
895 return cursorY;
896 }
897
898 /**
899 * Set the window title. Default implementation does nothing.
900 *
901 * @param title the new title
902 */
903 public void setTitle(final String title) {}
904
905 // ------------------------------------------------------------------------
906 // LogicalScreen ----------------------------------------------------------
907 // ------------------------------------------------------------------------
908
909 /**
910 * Reallocate screen buffers.
911 *
912 * @param width new width
913 * @param height new height
914 */
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;
920 }
921 }
922 logical = null;
923 }
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;
929 }
930 }
931 physical = null;
932 }
933 physical = new Cell[width][height];
934
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();
939 }
940 }
941
942 this.width = width;
943 this.height = height;
944
945 clipLeft = 0;
946 clipTop = 0;
947 clipRight = width;
948 clipBottom = height;
949
950 reallyCleared = true;
951 }
952
953 /**
954 * Clear the physical screen.
955 */
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();
960 }
961 }
962 }
963
964 /**
965 * Unset every image cell on one row of the physical screen, forcing
966 * images on that row to be redrawn.
967 *
968 * @param y row coordinate. 0 is the top-most row.
969 */
970 public final void unsetImageRow(final int y) {
971 if ((y < 0) || (y >= height)) {
972 return;
973 }
974 for (int x = 0; x < width; x++) {
975 if (logical[x][y].isImage()) {
976 physical[x][y].unset();
977 }
978 }
979 }
980
981 /**
982 * Render one fullwidth cell.
983 *
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
987 */
988 public final void putFullwidthCharXY(final int x, final int y,
989 final Cell cell) {
990
991 int cellWidth = getTextWidth();
992 int cellHeight = getTextHeight();
993
994 if (lastTextHeight != cellHeight) {
995 glyphMaker = GlyphMaker.getInstance(cellHeight);
996 lastTextHeight = cellHeight;
997 }
998 BufferedImage image = glyphMaker.getImage(cell, cellWidth * 2,
999 cellHeight);
1000 BufferedImage leftImage = image.getSubimage(0, 0, cellWidth,
1001 cellHeight);
1002 BufferedImage rightImage = image.getSubimage(cellWidth, 0, cellWidth,
1003 cellHeight);
1004
1005 Cell left = new Cell(cell);
1006 left.setImage(leftImage);
1007 left.setWidth(Cell.Width.LEFT);
1008 putCharXY(x, y, left);
1009
1010 Cell right = new Cell(cell);
1011 right.setImage(rightImage);
1012 right.setWidth(Cell.Width.RIGHT);
1013 putCharXY(x + 1, y, right);
1014 }
1015
1016 /**
1017 * Render one fullwidth character with attributes.
1018 *
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)
1023 */
1024 public final void putFullwidthCharXY(final int x, final int y,
1025 final int ch, final CellAttributes attr) {
1026
1027 Cell cell = new Cell(ch, attr);
1028 putFullwidthCharXY(x, y, cell);
1029 }
1030
1031 /**
1032 * Render one fullwidth character with attributes.
1033 *
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
1037 */
1038 public final void putFullwidthCharXY(final int x, final int y,
1039 final int ch) {
1040
1041 Cell cell = new Cell(ch);
1042 cell.setAttr(getAttrXY(x, y));
1043 putFullwidthCharXY(x, y, cell);
1044 }
1045
1046 /**
1047 * Invert the cell color at a position, including both halves of a
1048 * double-width cell.
1049 *
1050 * @param x column position
1051 * @param y row position
1052 */
1053 public void invertCell(final int x, final int y) {
1054 invertCell(x, y, false);
1055 }
1056
1057 /**
1058 * Invert the cell color at a position.
1059 *
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
1064 */
1065 public void invertCell(final int x, final int y,
1066 final boolean onlyThisCell) {
1067
1068 Cell cell = getCharXY(x, y);
1069 if (cell.isImage()) {
1070 cell.invertImage();
1071 }
1072 if (cell.getForeColorRGB() < 0) {
1073 cell.setForeColor(cell.getForeColor().invert());
1074 } else {
1075 cell.setForeColorRGB(cell.getForeColorRGB() ^ 0x00ffffff);
1076 }
1077 if (cell.getBackColorRGB() < 0) {
1078 cell.setBackColor(cell.getBackColor().invert());
1079 } else {
1080 cell.setBackColorRGB(cell.getBackColorRGB() ^ 0x00ffffff);
1081 }
1082 putCharXY(x, y, cell);
1083 if ((onlyThisCell == true) || (cell.getWidth() == Cell.Width.SINGLE)) {
1084 return;
1085 }
1086
1087 // This cell is one half of a fullwidth glyph. Invert the other
1088 // half.
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);
1094 return;
1095 }
1096 }
1097 }
1098 if (cell.getWidth() == Cell.Width.RIGHT) {
1099 if (x > 0) {
1100 Cell leftHalf = getCharXY(x - 1, y);
1101 if (leftHalf.getWidth() == Cell.Width.LEFT) {
1102 invertCell(x - 1, y, true);
1103 }
1104 }
1105 }
1106 }
1107
1108 /**
1109 * Set a selection area on the screen.
1110 *
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
1116 */
1117 public void setSelection(final int x0, final int y0,
1118 final int x1, final int y1, final boolean rectangle) {
1119
1120 int startX = x0;
1121 int startY = y0;
1122 int endX = x1;
1123 int endY = y1;
1124
1125 if (((x1 < x0) && (y1 == y0))
1126 || (y1 < y0)
1127 ) {
1128 // The user dragged from bottom-to-top and/or right-to-left.
1129 // Reverse the coordinates for the inverted section.
1130 startX = x1;
1131 startY = y1;
1132 endX = x0;
1133 endY = y0;
1134 }
1135 if (rectangle) {
1136 for (int y = startY; y <= endY; y++) {
1137 for (int x = startX; x <= endX; x++) {
1138 invertCell(x, y);
1139 }
1140 }
1141 } else {
1142 if (endY > startY) {
1143 for (int x = startX; x < width; x++) {
1144 invertCell(x, startY);
1145 }
1146 for (int y = startY + 1; y < endY; y++) {
1147 for (int x = 0; x < width; x++) {
1148 invertCell(x, y);
1149 }
1150 }
1151 for (int x = 0; x <= endX; x++) {
1152 invertCell(x, endY);
1153 }
1154 } else {
1155 assert (startY == endY);
1156 for (int x = startX; x <= endX; x++) {
1157 invertCell(x, startY);
1158 }
1159 }
1160 }
1161 }
1162
1163 /**
1164 * Copy the screen selection area to the clipboard.
1165 *
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
1172 */
1173 public void copySelection(final Clipboard clipboard,
1174 final int x0, final int y0, final int x1, final int y1,
1175 final boolean rectangle) {
1176
1177 StringBuilder sb = new StringBuilder();
1178
1179 int startX = x0;
1180 int startY = y0;
1181 int endX = x1;
1182 int endY = y1;
1183
1184 if (((x1 < x0) && (y1 == y0))
1185 || (y1 < y0)
1186 ) {
1187 // The user dragged from bottom-to-top and/or right-to-left.
1188 // Reverse the coordinates for the inverted section.
1189 startX = x1;
1190 startY = y1;
1191 endX = x0;
1192 endY = y0;
1193 }
1194 if (rectangle) {
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()));
1198 }
1199 sb.append("\n");
1200 }
1201 } else {
1202 if (endY > startY) {
1203 for (int x = startX; x < width; x++) {
1204 sb.append(Character.toChars(getCharXY(x, startY).getChar()));
1205 }
1206 sb.append("\n");
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()));
1210 }
1211 sb.append("\n");
1212 }
1213 for (int x = 0; x <= endX; x++) {
1214 sb.append(Character.toChars(getCharXY(x, endY).getChar()));
1215 }
1216 } else {
1217 assert (startY == endY);
1218 for (int x = startX; x <= endX; x++) {
1219 sb.append(Character.toChars(getCharXY(x, startY).getChar()));
1220 }
1221 }
1222 }
1223 clipboard.copyText(sb.toString());
1224 }
1225
1226 }