#49 cell attributes to int
[nikiroo-utils.git] / src / jexer / 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.GraphicsChars;
37 import jexer.bits.StringUtils;
38
39 /**
40 * A logical screen composed of a 2D array of Cells.
41 */
42 public class LogicalScreen implements Screen {
43
44 // ------------------------------------------------------------------------
45 // Variables --------------------------------------------------------------
46 // ------------------------------------------------------------------------
47
48 /**
49 * Width of the visible window.
50 */
51 protected int width;
52
53 /**
54 * Height of the visible window.
55 */
56 protected int height;
57
58 /**
59 * Drawing offset for x.
60 */
61 private int offsetX;
62
63 /**
64 * Drawing offset for y.
65 */
66 private int offsetY;
67
68 /**
69 * Ignore anything drawn right of clipRight.
70 */
71 private int clipRight;
72
73 /**
74 * Ignore anything drawn below clipBottom.
75 */
76 private int clipBottom;
77
78 /**
79 * Ignore anything drawn left of clipLeft.
80 */
81 private int clipLeft;
82
83 /**
84 * Ignore anything drawn above clipTop.
85 */
86 private int clipTop;
87
88 /**
89 * The physical screen last sent out on flush().
90 */
91 protected Cell [][] physical;
92
93 /**
94 * The logical screen being rendered to.
95 */
96 protected Cell [][] logical;
97
98 /**
99 * Set if the user explicitly wants to redraw everything starting with a
100 * ECMATerminal.clearAll().
101 */
102 protected boolean reallyCleared;
103
104 /**
105 * If true, the cursor is visible and should be placed onscreen at
106 * (cursorX, cursorY) during a call to flushPhysical().
107 */
108 protected boolean cursorVisible;
109
110 /**
111 * Cursor X position if visible.
112 */
113 protected int cursorX;
114
115 /**
116 * Cursor Y position if visible.
117 */
118 protected int cursorY;
119
120 /**
121 * The last used height of a character cell in pixels, only used for
122 * full-width chars.
123 */
124 private int lastTextHeight = -1;
125
126 /**
127 * The glyph drawer for full-width chars.
128 */
129 private GlyphMaker glyphMaker = null;
130
131 // ------------------------------------------------------------------------
132 // Constructors -----------------------------------------------------------
133 // ------------------------------------------------------------------------
134
135 /**
136 * Public constructor. Sets everything to not-bold, white-on-black.
137 */
138 protected LogicalScreen() {
139 offsetX = 0;
140 offsetY = 0;
141 width = 80;
142 height = 24;
143 logical = null;
144 physical = null;
145 reallocate(width, height);
146 }
147
148 // ------------------------------------------------------------------------
149 // Screen -----------------------------------------------------------------
150 // ------------------------------------------------------------------------
151
152 /**
153 * Get the width of a character cell in pixels.
154 *
155 * @return the width in pixels of a character cell
156 */
157 public int getTextWidth() {
158 // Default width is 16 pixels.
159 return 16;
160 }
161
162 /**
163 * Get the height of a character cell in pixels.
164 *
165 * @return the height in pixels of a character cell
166 */
167 public int getTextHeight() {
168 // Default height is 20 pixels.
169 return 20;
170 }
171
172 /**
173 * Set drawing offset for x.
174 *
175 * @param offsetX new drawing offset
176 */
177 public final void setOffsetX(final int offsetX) {
178 this.offsetX = offsetX;
179 }
180
181 /**
182 * Set drawing offset for y.
183 *
184 * @param offsetY new drawing offset
185 */
186 public final void setOffsetY(final int offsetY) {
187 this.offsetY = offsetY;
188 }
189
190 /**
191 * Get right drawing clipping boundary.
192 *
193 * @return drawing boundary
194 */
195 public final int getClipRight() {
196 return clipRight;
197 }
198
199 /**
200 * Set right drawing clipping boundary.
201 *
202 * @param clipRight new boundary
203 */
204 public final void setClipRight(final int clipRight) {
205 this.clipRight = clipRight;
206 }
207
208 /**
209 * Get bottom drawing clipping boundary.
210 *
211 * @return drawing boundary
212 */
213 public final int getClipBottom() {
214 return clipBottom;
215 }
216
217 /**
218 * Set bottom drawing clipping boundary.
219 *
220 * @param clipBottom new boundary
221 */
222 public final void setClipBottom(final int clipBottom) {
223 this.clipBottom = clipBottom;
224 }
225
226 /**
227 * Get left drawing clipping boundary.
228 *
229 * @return drawing boundary
230 */
231 public final int getClipLeft() {
232 return clipLeft;
233 }
234
235 /**
236 * Set left drawing clipping boundary.
237 *
238 * @param clipLeft new boundary
239 */
240 public final void setClipLeft(final int clipLeft) {
241 this.clipLeft = clipLeft;
242 }
243
244 /**
245 * Get top drawing clipping boundary.
246 *
247 * @return drawing boundary
248 */
249 public final int getClipTop() {
250 return clipTop;
251 }
252
253 /**
254 * Set top drawing clipping boundary.
255 *
256 * @param clipTop new boundary
257 */
258 public final void setClipTop(final int clipTop) {
259 this.clipTop = clipTop;
260 }
261
262 /**
263 * Get dirty flag.
264 *
265 * @return if true, the logical screen is not in sync with the physical
266 * screen
267 */
268 public final boolean isDirty() {
269 for (int x = 0; x < width; x++) {
270 for (int y = 0; y < height; y++) {
271 if (!logical[x][y].equals(physical[x][y])) {
272 return true;
273 }
274 if (logical[x][y].isBlink()) {
275 // Blinking screens are always dirty. There is
276 // opportunity for a Netscape blink tag joke here...
277 return true;
278 }
279 }
280 }
281
282 return false;
283 }
284
285 /**
286 * Get the attributes at one location.
287 *
288 * @param x column coordinate. 0 is the left-most column.
289 * @param y row coordinate. 0 is the top-most row.
290 * @return attributes at (x, y)
291 */
292 public final CellAttributes getAttrXY(final int x, final int y) {
293 CellAttributes attr = new CellAttributes();
294 if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
295 attr.setTo(logical[x][y]);
296 }
297 return attr;
298 }
299
300 /**
301 * Get the cell at one location.
302 *
303 * @param x column coordinate. 0 is the left-most column.
304 * @param y row coordinate. 0 is the top-most row.
305 * @return the character + attributes
306 */
307 public Cell getCharXY(final int x, final int y) {
308 Cell cell = new Cell();
309 if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
310 cell.setTo(logical[x][y]);
311 }
312 return cell;
313 }
314
315 /**
316 * Set the attributes at one location.
317 *
318 * @param x column coordinate. 0 is the left-most column.
319 * @param y row coordinate. 0 is the top-most row.
320 * @param attr attributes to use (bold, foreColor, backColor)
321 */
322 public final void putAttrXY(final int x, final int y,
323 final CellAttributes attr) {
324
325 putAttrXY(x, y, attr, true);
326 }
327
328 /**
329 * Set the attributes at one location.
330 *
331 * @param x column coordinate. 0 is the left-most column.
332 * @param y row coordinate. 0 is the top-most row.
333 * @param attr attributes to use (bold, foreColor, backColor)
334 * @param clip if true, honor clipping/offset
335 */
336 public final void putAttrXY(final int x, final int y,
337 final CellAttributes attr, final boolean clip) {
338
339 int X = x;
340 int Y = y;
341
342 if (clip) {
343 if ((x < clipLeft)
344 || (x >= clipRight)
345 || (y < clipTop)
346 || (y >= clipBottom)
347 ) {
348 return;
349 }
350 X += offsetX;
351 Y += offsetY;
352 }
353
354 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
355 logical[X][Y].setTo(attr);
356
357 // If this happens to be the cursor position, make the position
358 // dirty.
359 if ((cursorX == X) && (cursorY == Y)) {
360 physical[cursorX][cursorY].unset();
361 unsetImageRow(cursorY);
362 }
363 }
364 }
365
366 /**
367 * Fill the entire screen with one character with attributes.
368 *
369 * @param ch character to draw
370 * @param attr attributes to use (bold, foreColor, backColor)
371 */
372 public final void putAll(final char ch, final CellAttributes attr) {
373
374 for (int x = 0; x < width; x++) {
375 for (int y = 0; y < height; y++) {
376 putCharXY(x, y, ch, attr);
377 }
378 }
379 }
380
381 /**
382 * Render one character with attributes.
383 *
384 * @param x column coordinate. 0 is the left-most column.
385 * @param y row coordinate. 0 is the top-most row.
386 * @param ch character + attributes to draw
387 */
388 public final void putCharXY(final int x, final int y, final Cell ch) {
389 if ((x < clipLeft)
390 || (x >= clipRight)
391 || (y < clipTop)
392 || (y >= clipBottom)
393 ) {
394 return;
395 }
396
397 if ((StringUtils.width(ch.getChar()) == 2) && (!ch.isImage())) {
398 putFullwidthCharXY(x, y, ch);
399 return;
400 }
401
402 int X = x + offsetX;
403 int Y = y + offsetY;
404
405 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
406
407 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
408
409 // Do not put control characters on the display
410 if (!ch.isImage()) {
411 assert (ch.getChar() >= 0x20);
412 assert (ch.getChar() != 0x7F);
413 }
414 logical[X][Y].setTo(ch);
415
416 // If this happens to be the cursor position, make the position
417 // dirty.
418 if ((cursorX == X) && (cursorY == Y)) {
419 physical[cursorX][cursorY].unset();
420 unsetImageRow(cursorY);
421 }
422 }
423 }
424
425 /**
426 * Render one character with attributes.
427 *
428 * @param x column coordinate. 0 is the left-most column.
429 * @param y row coordinate. 0 is the top-most row.
430 * @param ch character to draw
431 * @param attr attributes to use (bold, foreColor, backColor)
432 */
433 public final void putCharXY(final int x, final int y, final char ch,
434 final CellAttributes attr) {
435
436 if ((x < clipLeft)
437 || (x >= clipRight)
438 || (y < clipTop)
439 || (y >= clipBottom)
440 ) {
441 return;
442 }
443
444 if (StringUtils.width(ch) == 2) {
445 putFullwidthCharXY(x, y, ch, attr);
446 return;
447 }
448
449 int X = x + offsetX;
450 int Y = y + offsetY;
451
452 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
453
454 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
455
456 // Do not put control characters on the display
457 assert (ch >= 0x20);
458 assert (ch != 0x7F);
459
460 logical[X][Y].setTo(attr);
461 logical[X][Y].setChar(ch);
462
463 // If this happens to be the cursor position, make the position
464 // dirty.
465 if ((cursorX == X) && (cursorY == Y)) {
466 physical[cursorX][cursorY].unset();
467 unsetImageRow(cursorY);
468 }
469 }
470 }
471
472 /**
473 * Render one character without changing the underlying attributes.
474 *
475 * @param x column coordinate. 0 is the left-most column.
476 * @param y row coordinate. 0 is the top-most row.
477 * @param ch character to draw
478 */
479 public final void putCharXY(final int x, final int y, final char ch) {
480
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(); j++) {
524 char ch = str.charAt(j);
525 putCharXY(i, y, ch, attr);
526 i += StringUtils.width(ch);
527 if (i == width) {
528 break;
529 }
530 }
531 }
532
533 /**
534 * Render a string without changing the underlying attribute. Does not
535 * wrap if the string exceeds the line.
536 *
537 * @param x column coordinate. 0 is the left-most column.
538 * @param y row coordinate. 0 is the top-most row.
539 * @param str string to draw
540 */
541 public final void putStringXY(final int x, final int y, final String str) {
542
543 int i = x;
544 for (int j = 0; j < str.length(); j++) {
545 char ch = str.charAt(j);
546 putCharXY(i, y, ch);
547 i += StringUtils.width(ch);
548 if (i == width) {
549 break;
550 }
551 }
552 }
553
554 /**
555 * Draw a vertical line from (x, y) to (x, y + n).
556 *
557 * @param x column coordinate. 0 is the left-most column.
558 * @param y row coordinate. 0 is the top-most row.
559 * @param n number of characters to draw
560 * @param ch character to draw
561 * @param attr attributes to use (bold, foreColor, backColor)
562 */
563 public final void vLineXY(final int x, final int y, final int n,
564 final char ch, final CellAttributes attr) {
565
566 for (int i = y; i < y + n; i++) {
567 putCharXY(x, i, ch, attr);
568 }
569 }
570
571 /**
572 * Draw a horizontal line from (x, y) to (x + n, y).
573 *
574 * @param x column coordinate. 0 is the left-most column.
575 * @param y row coordinate. 0 is the top-most row.
576 * @param n number of characters to draw
577 * @param ch character to draw
578 * @param attr attributes to use (bold, foreColor, backColor)
579 */
580 public final void hLineXY(final int x, final int y, final int n,
581 final char ch, final CellAttributes attr) {
582
583 for (int i = x; i < x + n; i++) {
584 putCharXY(i, y, ch, attr);
585 }
586 }
587
588 /**
589 * Change the width. Everything on-screen will be destroyed and must be
590 * redrawn.
591 *
592 * @param width new screen width
593 */
594 public final synchronized void setWidth(final int width) {
595 reallocate(width, this.height);
596 }
597
598 /**
599 * Change the height. Everything on-screen will be destroyed and must be
600 * redrawn.
601 *
602 * @param height new screen height
603 */
604 public final synchronized void setHeight(final int height) {
605 reallocate(this.width, height);
606 }
607
608 /**
609 * Change the width and height. Everything on-screen will be destroyed
610 * and must be redrawn.
611 *
612 * @param width new screen width
613 * @param height new screen height
614 */
615 public final void setDimensions(final int width, final int height) {
616 reallocate(width, height);
617 resizeToScreen();
618 }
619
620 /**
621 * Resize the physical screen to match the logical screen dimensions.
622 */
623 public void resizeToScreen() {
624 // Subclasses are expected to override this.
625 }
626
627 /**
628 * Get the height.
629 *
630 * @return current screen height
631 */
632 public final synchronized int getHeight() {
633 return this.height;
634 }
635
636 /**
637 * Get the width.
638 *
639 * @return current screen width
640 */
641 public final synchronized int getWidth() {
642 return this.width;
643 }
644
645 /**
646 * Reset screen to not-bold, white-on-black. Also flushes the offset and
647 * clip variables.
648 */
649 public final synchronized void reset() {
650 for (int row = 0; row < height; row++) {
651 for (int col = 0; col < width; col++) {
652 logical[col][row].reset();
653 }
654 }
655 resetClipping();
656 }
657
658 /**
659 * Flush the offset and clip variables.
660 */
661 public final void resetClipping() {
662 offsetX = 0;
663 offsetY = 0;
664 clipLeft = 0;
665 clipTop = 0;
666 clipRight = width;
667 clipBottom = height;
668 }
669
670 /**
671 * Clear the logical screen.
672 */
673 public final void clear() {
674 reset();
675 }
676
677 /**
678 * Draw a box with a border and empty background.
679 *
680 * @param left left column of box. 0 is the left-most column.
681 * @param top top row of the box. 0 is the top-most row.
682 * @param right right column of box
683 * @param bottom bottom row of the box
684 * @param border attributes to use for the border
685 * @param background attributes to use for the background
686 */
687 public final void drawBox(final int left, final int top,
688 final int right, final int bottom,
689 final CellAttributes border, final CellAttributes background) {
690
691 drawBox(left, top, right, bottom, border, background, 1, false);
692 }
693
694 /**
695 * Draw a box with a border and empty background.
696 *
697 * @param left left column of box. 0 is the left-most column.
698 * @param top top row of the box. 0 is the top-most row.
699 * @param right right column of box
700 * @param bottom bottom row of the box
701 * @param border attributes to use for the border
702 * @param background attributes to use for the background
703 * @param borderType if 1, draw a single-line border; if 2, draw a
704 * double-line border; if 3, draw double-line top/bottom edges and
705 * single-line left/right edges (like Qmodem)
706 * @param shadow if true, draw a "shadow" on the box
707 */
708 public final void drawBox(final int left, final int top,
709 final int right, final int bottom,
710 final CellAttributes border, final CellAttributes background,
711 final int borderType, final boolean shadow) {
712
713 int boxWidth = right - left;
714 int boxHeight = bottom - top;
715
716 char cTopLeft;
717 char cTopRight;
718 char cBottomLeft;
719 char cBottomRight;
720 char cHSide;
721 char cVSide;
722
723 switch (borderType) {
724 case 1:
725 cTopLeft = GraphicsChars.ULCORNER;
726 cTopRight = GraphicsChars.URCORNER;
727 cBottomLeft = GraphicsChars.LLCORNER;
728 cBottomRight = GraphicsChars.LRCORNER;
729 cHSide = GraphicsChars.SINGLE_BAR;
730 cVSide = GraphicsChars.WINDOW_SIDE;
731 break;
732
733 case 2:
734 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP_DOUBLE;
735 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP_DOUBLE;
736 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM_DOUBLE;
737 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM_DOUBLE;
738 cHSide = GraphicsChars.DOUBLE_BAR;
739 cVSide = GraphicsChars.WINDOW_SIDE_DOUBLE;
740 break;
741
742 case 3:
743 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP;
744 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP;
745 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM;
746 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM;
747 cHSide = GraphicsChars.WINDOW_TOP;
748 cVSide = GraphicsChars.WINDOW_SIDE;
749 break;
750 default:
751 throw new IllegalArgumentException("Invalid border type: "
752 + borderType);
753 }
754
755 // Place the corner characters
756 putCharXY(left, top, cTopLeft, border);
757 putCharXY(left + boxWidth - 1, top, cTopRight, border);
758 putCharXY(left, top + boxHeight - 1, cBottomLeft, border);
759 putCharXY(left + boxWidth - 1, top + boxHeight - 1, cBottomRight,
760 border);
761
762 // Draw the box lines
763 hLineXY(left + 1, top, boxWidth - 2, cHSide, border);
764 vLineXY(left, top + 1, boxHeight - 2, cVSide, border);
765 hLineXY(left + 1, top + boxHeight - 1, boxWidth - 2, cHSide, border);
766 vLineXY(left + boxWidth - 1, top + 1, boxHeight - 2, cVSide, border);
767
768 // Fill in the interior background
769 for (int i = 1; i < boxHeight - 1; i++) {
770 hLineXY(1 + left, i + top, boxWidth - 2, ' ', background);
771 }
772
773 if (shadow) {
774 // Draw a shadow
775 drawBoxShadow(left, top, right, bottom);
776 }
777 }
778
779 /**
780 * Draw a box shadow.
781 *
782 * @param left left column of box. 0 is the left-most column.
783 * @param top top row of the box. 0 is the top-most row.
784 * @param right right column of box
785 * @param bottom bottom row of the box
786 */
787 public final void drawBoxShadow(final int left, final int top,
788 final int right, final int bottom) {
789
790 int boxTop = top;
791 int boxLeft = left;
792 int boxWidth = right - left;
793 int boxHeight = bottom - top;
794 CellAttributes shadowAttr = new CellAttributes();
795
796 // Shadows do not honor clipping but they DO honor offset.
797 int oldClipRight = clipRight;
798 int oldClipBottom = clipBottom;
799 // When offsetX or offsetY go negative, we need to increase the clip
800 // bounds.
801 clipRight = width - offsetX;
802 clipBottom = height - offsetY;
803
804 for (int i = 0; i < boxHeight; i++) {
805 putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
806 putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr);
807 }
808 for (int i = 0; i < boxWidth; i++) {
809 putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr);
810 }
811 clipRight = oldClipRight;
812 clipBottom = oldClipBottom;
813 }
814
815 /**
816 * Default implementation does nothing.
817 */
818 public void flushPhysical() {}
819
820 /**
821 * Put the cursor at (x,y).
822 *
823 * @param visible if true, the cursor should be visible
824 * @param x column coordinate to put the cursor on
825 * @param y row coordinate to put the cursor on
826 */
827 public void putCursor(final boolean visible, final int x, final int y) {
828 if ((cursorY >= 0)
829 && (cursorX >= 0)
830 && (cursorY <= height - 1)
831 && (cursorX <= width - 1)
832 ) {
833 // Make the current cursor position dirty
834 physical[cursorX][cursorY].unset();
835 unsetImageRow(cursorY);
836 }
837
838 cursorVisible = visible;
839 cursorX = x;
840 cursorY = y;
841 }
842
843 /**
844 * Hide the cursor.
845 */
846 public final void hideCursor() {
847 cursorVisible = false;
848 }
849
850 /**
851 * Get the cursor visibility.
852 *
853 * @return true if the cursor is visible
854 */
855 public boolean isCursorVisible() {
856 return cursorVisible;
857 }
858
859 /**
860 * Get the cursor X position.
861 *
862 * @return the cursor x column position
863 */
864 public int getCursorX() {
865 return cursorX;
866 }
867
868 /**
869 * Get the cursor Y position.
870 *
871 * @return the cursor y row position
872 */
873 public int getCursorY() {
874 return cursorY;
875 }
876
877 /**
878 * Set the window title. Default implementation does nothing.
879 *
880 * @param title the new title
881 */
882 public void setTitle(final String title) {}
883
884 // ------------------------------------------------------------------------
885 // LogicalScreen ----------------------------------------------------------
886 // ------------------------------------------------------------------------
887
888 /**
889 * Reallocate screen buffers.
890 *
891 * @param width new width
892 * @param height new height
893 */
894 private synchronized void reallocate(final int width, final int height) {
895 if (logical != null) {
896 for (int row = 0; row < this.height; row++) {
897 for (int col = 0; col < this.width; col++) {
898 logical[col][row] = null;
899 }
900 }
901 logical = null;
902 }
903 logical = new Cell[width][height];
904 if (physical != null) {
905 for (int row = 0; row < this.height; row++) {
906 for (int col = 0; col < this.width; col++) {
907 physical[col][row] = null;
908 }
909 }
910 physical = null;
911 }
912 physical = new Cell[width][height];
913
914 for (int row = 0; row < height; row++) {
915 for (int col = 0; col < width; col++) {
916 physical[col][row] = new Cell();
917 logical[col][row] = new Cell();
918 }
919 }
920
921 this.width = width;
922 this.height = height;
923
924 clipLeft = 0;
925 clipTop = 0;
926 clipRight = width;
927 clipBottom = height;
928
929 reallyCleared = true;
930 }
931
932 /**
933 * Clear the physical screen.
934 */
935 public final void clearPhysical() {
936 for (int row = 0; row < height; row++) {
937 for (int col = 0; col < width; col++) {
938 physical[col][row].unset();
939 }
940 }
941 }
942
943 /**
944 * Unset every image cell on one row of the physical screen, forcing
945 * images on that row to be redrawn.
946 *
947 * @param y row coordinate. 0 is the top-most row.
948 */
949 public final void unsetImageRow(final int y) {
950 if ((y < 0) || (y >= height)) {
951 return;
952 }
953 for (int x = 0; x < width; x++) {
954 if (logical[x][y].isImage()) {
955 physical[x][y].unset();
956 }
957 }
958 }
959
960 /**
961 * Render one fullwidth cell.
962 *
963 * @param x column coordinate. 0 is the left-most column.
964 * @param y row coordinate. 0 is the top-most row.
965 * @param cell the cell to draw
966 */
967 public final void putFullwidthCharXY(final int x, final int y,
968 final Cell cell) {
969
970 if (lastTextHeight != getTextHeight()) {
971 glyphMaker = GlyphMaker.getInstance(getTextHeight());
972 lastTextHeight = getTextHeight();
973 }
974 BufferedImage image = glyphMaker.getImage(cell, getTextWidth() * 2,
975 getTextHeight());
976 BufferedImage leftImage = image.getSubimage(0, 0, getTextWidth(),
977 getTextHeight());
978 BufferedImage rightImage = image.getSubimage(getTextWidth(), 0,
979 getTextWidth(), getTextHeight());
980
981 Cell left = new Cell(cell);
982 left.setImage(leftImage);
983 left.setWidth(Cell.Width.LEFT);
984 // Blank out the char itself, so that shadows do not leave artifacts.
985 left.setChar(' ');
986 putCharXY(x, y, left);
987
988 Cell right = new Cell(cell);
989 right.setImage(rightImage);
990 right.setWidth(Cell.Width.RIGHT);
991 // Blank out the char itself, so that shadows do not leave artifacts.
992 right.setChar(' ');
993 putCharXY(x + 1, y, right);
994 }
995
996 /**
997 * Render one fullwidth character with attributes.
998 *
999 * @param x column coordinate. 0 is the left-most column.
1000 * @param y row coordinate. 0 is the top-most row.
1001 * @param ch character to draw
1002 * @param attr attributes to use (bold, foreColor, backColor)
1003 */
1004 public final void putFullwidthCharXY(final int x, final int y,
1005 final char ch, final CellAttributes attr) {
1006
1007 Cell cell = new Cell(ch, attr);
1008 putFullwidthCharXY(x, y, cell);
1009 }
1010
1011 /**
1012 * Render one fullwidth character with attributes.
1013 *
1014 * @param x column coordinate. 0 is the left-most column.
1015 * @param y row coordinate. 0 is the top-most row.
1016 * @param ch character to draw
1017 */
1018 public final void putFullwidthCharXY(final int x, final int y,
1019 final char ch) {
1020
1021 Cell cell = new Cell(ch);
1022 cell.setAttr(getAttrXY(x, y));
1023 putFullwidthCharXY(x, y, cell);
1024 }
1025
1026 }