Update main screenshot
[nikiroo-utils.git] / src / jexer / backend / LogicalScreen.java
CommitLineData
daa4106c 1/*
df8de03f
KL
2 * Jexer - Java Text User Interface
3 *
e16dda65 4 * The MIT License (MIT)
df8de03f 5 *
a69ed767 6 * Copyright (C) 2019 Kevin Lamonte
df8de03f 7 *
e16dda65
KL
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:
df8de03f 14 *
e16dda65
KL
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
df8de03f 17 *
e16dda65
KL
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.
7b5261bc
KL
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
df8de03f 28 */
42873e30 29package jexer.backend;
df8de03f 30
3af53a35
KL
31import java.awt.image.BufferedImage;
32
33import jexer.backend.GlyphMaker;
df8de03f
KL
34import jexer.bits.Cell;
35import jexer.bits.CellAttributes;
36import jexer.bits.GraphicsChars;
3af53a35 37import jexer.bits.StringUtils;
df8de03f
KL
38
39/**
42873e30 40 * A logical screen composed of a 2D array of Cells.
df8de03f 41 */
42873e30 42public class LogicalScreen implements Screen {
df8de03f 43
d36057df
KL
44 // ------------------------------------------------------------------------
45 // Variables --------------------------------------------------------------
46 // ------------------------------------------------------------------------
47
df8de03f 48 /**
7b5261bc 49 * Width of the visible window.
df8de03f
KL
50 */
51 protected int width;
52
53 /**
7b5261bc 54 * Height of the visible window.
df8de03f
KL
55 */
56 protected int height;
57
58 /**
7b5261bc 59 * Drawing offset for x.
df8de03f 60 */
fca67db0 61 private int offsetX;
48e27807 62
d36057df
KL
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
3af53a35
KL
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
d36057df
KL
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
03ae544a
KL
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
48e27807
KL
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 }
df8de03f 180
48e27807
KL
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 }
fca67db0 189
48e27807
KL
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 }
df8de03f 207
48e27807
KL
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 }
df8de03f 225
48e27807
KL
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 }
df8de03f 243
48e27807
KL
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 }
df8de03f 261
92554d64
KL
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() {
be72cb5c
KL
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;
92554d64
KL
283 }
284
df8de03f
KL
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 */
fca67db0 292 public final CellAttributes getAttrXY(final int x, final int y) {
7b5261bc 293 CellAttributes attr = new CellAttributes();
30bd4abd
KL
294 if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
295 attr.setTo(logical[x][y]);
296 }
7b5261bc 297 return attr;
df8de03f
KL
298 }
299
3e074355
KL
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
df8de03f
KL
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)
df8de03f 321 */
fca67db0
KL
322 public final void putAttrXY(final int x, final int y,
323 final CellAttributes attr) {
324
7b5261bc 325 putAttrXY(x, y, attr, true);
df8de03f 326 }
7b5261bc 327
df8de03f
KL
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 */
a1879051
KL
336 public final void putAttrXY(final int x, final int y,
337 final CellAttributes attr, final boolean clip) {
7b5261bc
KL
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)) {
be72cb5c 355 logical[X][Y].setTo(attr);
24489803
KL
356
357 // If this happens to be the cursor position, make the position
358 // dirty.
359 if ((cursorX == X) && (cursorY == Y)) {
a69ed767
KL
360 physical[cursorX][cursorY].unset();
361 unsetImageRow(cursorY);
24489803 362 }
7b5261bc 363 }
df8de03f
KL
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 */
218d18db 372 public final void putAll(final int ch, final CellAttributes attr) {
87a17f3c 373
7b5261bc
KL
374 for (int x = 0; x < width; x++) {
375 for (int y = 0; y < height; y++) {
376 putCharXY(x, y, ch, attr);
377 }
378 }
df8de03f
KL
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 */
fca67db0 388 public final void putCharXY(final int x, final int y, final Cell ch) {
a69ed767
KL
389 if ((x < clipLeft)
390 || (x >= clipRight)
391 || (y < clipTop)
392 || (y >= clipBottom)
393 ) {
394 return;
395 }
396
3af53a35
KL
397 if ((StringUtils.width(ch.getChar()) == 2) && (!ch.isImage())) {
398 putFullwidthCharXY(x, y, ch);
399 return;
400 }
401
a69ed767
KL
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 }
df8de03f
KL
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 */
218d18db 433 public final void putCharXY(final int x, final int y, final int ch,
7b5261bc
KL
434 final CellAttributes attr) {
435
436 if ((x < clipLeft)
437 || (x >= clipRight)
438 || (y < clipTop)
439 || (y >= clipBottom)
440 ) {
441 return;
442 }
443
3af53a35
KL
444 if (StringUtils.width(ch) == 2) {
445 putFullwidthCharXY(x, y, ch, attr);
446 return;
447 }
448
7b5261bc
KL
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)) {
7b5261bc
KL
455
456 // Do not put control characters on the display
457 assert (ch >= 0x20);
458 assert (ch != 0x7F);
459
be72cb5c 460 logical[X][Y].setTo(attr);
7b5261bc 461 logical[X][Y].setChar(ch);
24489803
KL
462
463 // If this happens to be the cursor position, make the position
464 // dirty.
465 if ((cursorX == X) && (cursorY == Y)) {
a69ed767
KL
466 physical[cursorX][cursorY].unset();
467 unsetImageRow(cursorY);
24489803 468 }
7b5261bc 469 }
df8de03f
KL
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 */
218d18db 479 public final void putCharXY(final int x, final int y, final int ch) {
7b5261bc
KL
480 if ((x < clipLeft)
481 || (x >= clipRight)
482 || (y < clipTop)
483 || (y >= clipBottom)
484 ) {
485 return;
486 }
df8de03f 487
3af53a35
KL
488 if (StringUtils.width(ch) == 2) {
489 putFullwidthCharXY(x, y, ch);
490 return;
491 }
492
7b5261bc
KL
493 int X = x + offsetX;
494 int Y = y + offsetY;
df8de03f 495
7b5261bc 496 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
df8de03f 497
7b5261bc 498 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
7b5261bc 499 logical[X][Y].setChar(ch);
24489803
KL
500
501 // If this happens to be the cursor position, make the position
502 // dirty.
503 if ((cursorX == X) && (cursorY == Y)) {
a69ed767
KL
504 physical[cursorX][cursorY].unset();
505 unsetImageRow(cursorY);
24489803 506 }
7b5261bc 507 }
df8de03f
KL
508 }
509
510 /**
511 * Render a string. Does not wrap if the string exceeds the line.
512 *
513 * @param x column coordinate. 0 is the left-most column.
514 * @param y row coordinate. 0 is the top-most row.
515 * @param str string to draw
516 * @param attr attributes to use (bold, foreColor, backColor)
517 */
0d47c546 518 public final void putStringXY(final int x, final int y, final String str,
7b5261bc
KL
519 final CellAttributes attr) {
520
521 int i = x;
218d18db
KL
522 for (int j = 0; j < str.length();) {
523 int ch = str.codePointAt(j);
524 j += Character.charCount(ch);
7b5261bc 525 putCharXY(i, y, ch, attr);
97bc3f29 526 i += StringUtils.width(ch);
7b5261bc
KL
527 if (i == width) {
528 break;
529 }
530 }
df8de03f
KL
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 */
0d47c546 541 public final void putStringXY(final int x, final int y, final String str) {
87a17f3c 542
7b5261bc 543 int i = x;
218d18db
KL
544 for (int j = 0; j < str.length();) {
545 int ch = str.codePointAt(j);
546 j += Character.charCount(ch);
7b5261bc 547 putCharXY(i, y, ch);
97bc3f29 548 i += StringUtils.width(ch);
7b5261bc
KL
549 if (i == width) {
550 break;
551 }
552 }
df8de03f
KL
553 }
554
555 /**
7b5261bc 556 * Draw a vertical line from (x, y) to (x, y + n).
df8de03f
KL
557 *
558 * @param x column coordinate. 0 is the left-most column.
559 * @param y row coordinate. 0 is the top-most row.
560 * @param n number of characters to draw
561 * @param ch character to draw
562 * @param attr attributes to use (bold, foreColor, backColor)
563 */
fca67db0 564 public final void vLineXY(final int x, final int y, final int n,
218d18db 565 final int ch, final CellAttributes attr) {
7b5261bc
KL
566
567 for (int i = y; i < y + n; i++) {
568 putCharXY(x, i, ch, attr);
569 }
df8de03f
KL
570 }
571
572 /**
7b5261bc 573 * Draw a horizontal line from (x, y) to (x + n, y).
df8de03f
KL
574 *
575 * @param x column coordinate. 0 is the left-most column.
576 * @param y row coordinate. 0 is the top-most row.
577 * @param n number of characters to draw
578 * @param ch character to draw
579 * @param attr attributes to use (bold, foreColor, backColor)
580 */
fca67db0 581 public final void hLineXY(final int x, final int y, final int n,
218d18db 582 final int ch, final CellAttributes attr) {
7b5261bc
KL
583
584 for (int i = x; i < x + n; i++) {
585 putCharXY(i, y, ch, attr);
586 }
df8de03f
KL
587 }
588
df8de03f
KL
589 /**
590 * Change the width. Everything on-screen will be destroyed and must be
591 * redrawn.
7b5261bc 592 *
df8de03f
KL
593 * @param width new screen width
594 */
87a17f3c 595 public final synchronized void setWidth(final int width) {
7b5261bc 596 reallocate(width, this.height);
df8de03f
KL
597 }
598
599 /**
600 * Change the height. Everything on-screen will be destroyed and must be
601 * redrawn.
602 *
603 * @param height new screen height
604 */
87a17f3c 605 public final synchronized void setHeight(final int height) {
7b5261bc 606 reallocate(this.width, height);
df8de03f
KL
607 }
608
609 /**
610 * Change the width and height. Everything on-screen will be destroyed
611 * and must be redrawn.
612 *
613 * @param width new screen width
614 * @param height new screen height
615 */
fca67db0 616 public final void setDimensions(final int width, final int height) {
7b5261bc 617 reallocate(width, height);
9696a8f6
KL
618 resizeToScreen();
619 }
620
621 /**
622 * Resize the physical screen to match the logical screen dimensions.
623 */
624 public void resizeToScreen() {
625 // Subclasses are expected to override this.
df8de03f
KL
626 }
627
628 /**
629 * Get the height.
630 *
631 * @return current screen height
632 */
87a17f3c 633 public final synchronized int getHeight() {
7b5261bc 634 return this.height;
df8de03f
KL
635 }
636
637 /**
638 * Get the width.
639 *
640 * @return current screen width
641 */
87a17f3c 642 public final synchronized int getWidth() {
7b5261bc 643 return this.width;
df8de03f
KL
644 }
645
df8de03f
KL
646 /**
647 * Reset screen to not-bold, white-on-black. Also flushes the offset and
648 * clip variables.
649 */
87a17f3c 650 public final synchronized void reset() {
7b5261bc
KL
651 for (int row = 0; row < height; row++) {
652 for (int col = 0; col < width; col++) {
653 logical[col][row].reset();
654 }
655 }
656 resetClipping();
df8de03f
KL
657 }
658
659 /**
660 * Flush the offset and clip variables.
661 */
fca67db0 662 public final void resetClipping() {
7b5261bc
KL
663 offsetX = 0;
664 offsetY = 0;
665 clipLeft = 0;
666 clipTop = 0;
667 clipRight = width;
668 clipBottom = height;
df8de03f
KL
669 }
670
671 /**
bd8d51fa 672 * Clear the logical screen.
df8de03f 673 */
fca67db0 674 public final void clear() {
7b5261bc 675 reset();
df8de03f
KL
676 }
677
678 /**
679 * Draw a box with a border and empty background.
680 *
a69ed767 681 * @param left left column of box. 0 is the left-most column.
df8de03f
KL
682 * @param top top row of the box. 0 is the top-most row.
683 * @param right right column of box
684 * @param bottom bottom row of the box
7b5261bc 685 * @param border attributes to use for the border
df8de03f
KL
686 * @param background attributes to use for the background
687 */
fca67db0 688 public final void drawBox(final int left, final int top,
7b5261bc
KL
689 final int right, final int bottom,
690 final CellAttributes border, final CellAttributes background) {
691
692 drawBox(left, top, right, bottom, border, background, 1, false);
df8de03f
KL
693 }
694
695 /**
696 * Draw a box with a border and empty background.
697 *
a69ed767 698 * @param left left column of box. 0 is the left-most column.
df8de03f
KL
699 * @param top top row of the box. 0 is the top-most row.
700 * @param right right column of box
701 * @param bottom bottom row of the box
7b5261bc 702 * @param border attributes to use for the border
df8de03f 703 * @param background attributes to use for the background
7b5261bc
KL
704 * @param borderType if 1, draw a single-line border; if 2, draw a
705 * double-line border; if 3, draw double-line top/bottom edges and
706 * single-line left/right edges (like Qmodem)
df8de03f
KL
707 * @param shadow if true, draw a "shadow" on the box
708 */
fca67db0 709 public final void drawBox(final int left, final int top,
7b5261bc
KL
710 final int right, final int bottom,
711 final CellAttributes border, final CellAttributes background,
712 final int borderType, final boolean shadow) {
713
7b5261bc
KL
714 int boxWidth = right - left;
715 int boxHeight = bottom - top;
716
717 char cTopLeft;
718 char cTopRight;
719 char cBottomLeft;
720 char cBottomRight;
721 char cHSide;
722 char cVSide;
723
724 switch (borderType) {
725 case 1:
726 cTopLeft = GraphicsChars.ULCORNER;
727 cTopRight = GraphicsChars.URCORNER;
728 cBottomLeft = GraphicsChars.LLCORNER;
729 cBottomRight = GraphicsChars.LRCORNER;
730 cHSide = GraphicsChars.SINGLE_BAR;
731 cVSide = GraphicsChars.WINDOW_SIDE;
732 break;
733
734 case 2:
735 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP_DOUBLE;
736 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP_DOUBLE;
737 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM_DOUBLE;
738 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM_DOUBLE;
739 cHSide = GraphicsChars.DOUBLE_BAR;
740 cVSide = GraphicsChars.WINDOW_SIDE_DOUBLE;
741 break;
742
743 case 3:
744 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP;
745 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP;
746 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM;
747 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM;
748 cHSide = GraphicsChars.WINDOW_TOP;
749 cVSide = GraphicsChars.WINDOW_SIDE;
750 break;
751 default:
752 throw new IllegalArgumentException("Invalid border type: "
753 + borderType);
754 }
755
756 // Place the corner characters
757 putCharXY(left, top, cTopLeft, border);
758 putCharXY(left + boxWidth - 1, top, cTopRight, border);
759 putCharXY(left, top + boxHeight - 1, cBottomLeft, border);
760 putCharXY(left + boxWidth - 1, top + boxHeight - 1, cBottomRight,
761 border);
762
763 // Draw the box lines
764 hLineXY(left + 1, top, boxWidth - 2, cHSide, border);
765 vLineXY(left, top + 1, boxHeight - 2, cVSide, border);
766 hLineXY(left + 1, top + boxHeight - 1, boxWidth - 2, cHSide, border);
767 vLineXY(left + boxWidth - 1, top + 1, boxHeight - 2, cVSide, border);
768
769 // Fill in the interior background
770 for (int i = 1; i < boxHeight - 1; i++) {
771 hLineXY(1 + left, i + top, boxWidth - 2, ' ', background);
772 }
773
774 if (shadow) {
775 // Draw a shadow
776 drawBoxShadow(left, top, right, bottom);
777 }
df8de03f
KL
778 }
779
780 /**
fca67db0 781 * Draw a box shadow.
df8de03f 782 *
a69ed767 783 * @param left left column of box. 0 is the left-most column.
df8de03f
KL
784 * @param top top row of the box. 0 is the top-most row.
785 * @param right right column of box
786 * @param bottom bottom row of the box
787 */
fca67db0 788 public final void drawBoxShadow(final int left, final int top,
7b5261bc
KL
789 final int right, final int bottom) {
790
791 int boxTop = top;
792 int boxLeft = left;
793 int boxWidth = right - left;
794 int boxHeight = bottom - top;
795 CellAttributes shadowAttr = new CellAttributes();
796
797 // Shadows do not honor clipping but they DO honor offset.
798 int oldClipRight = clipRight;
799 int oldClipBottom = clipBottom;
a69ed767
KL
800 // When offsetX or offsetY go negative, we need to increase the clip
801 // bounds.
802 clipRight = width - offsetX;
803 clipBottom = height - offsetY;
7b5261bc
KL
804
805 for (int i = 0; i < boxHeight; i++) {
3fe82fa7
KL
806 Cell cell = getCharXY(offsetX + boxLeft + boxWidth,
807 offsetY + boxTop + 1 + i);
808 if (cell.getWidth() == Cell.Width.SINGLE) {
809 putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
810 } else {
811 putCharXY(boxLeft + boxWidth, boxTop + 1 + i, ' ', shadowAttr);
812 }
813 cell = getCharXY(offsetX + boxLeft + boxWidth + 1,
814 offsetY + boxTop + 1 + i);
815 if (cell.getWidth() == Cell.Width.SINGLE) {
816 putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr);
817 } else {
818 putCharXY(boxLeft + boxWidth + 1, boxTop + 1 + i, ' ',
819 shadowAttr);
820 }
7b5261bc
KL
821 }
822 for (int i = 0; i < boxWidth; i++) {
3fe82fa7
KL
823 Cell cell = getCharXY(offsetX + boxLeft + 2 + i,
824 offsetY + boxTop + boxHeight);
825 if (cell.getWidth() == Cell.Width.SINGLE) {
826 putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr);
827 } else {
828 putCharXY(boxLeft + 2 + i, boxTop + boxHeight, ' ', shadowAttr);
829 }
7b5261bc
KL
830 }
831 clipRight = oldClipRight;
832 clipBottom = oldClipBottom;
df8de03f
KL
833 }
834
835 /**
42873e30 836 * Default implementation does nothing.
df8de03f 837 */
42873e30 838 public void flushPhysical() {}
df8de03f
KL
839
840 /**
841 * Put the cursor at (x,y).
842 *
843 * @param visible if true, the cursor should be visible
844 * @param x column coordinate to put the cursor on
845 * @param y row coordinate to put the cursor on
846 */
30bd4abd 847 public void putCursor(final boolean visible, final int x, final int y) {
be72cb5c
KL
848 if ((cursorY >= 0)
849 && (cursorX >= 0)
850 && (cursorY <= height - 1)
851 && (cursorX <= width - 1)
852 ) {
853 // Make the current cursor position dirty
a69ed767
KL
854 physical[cursorX][cursorY].unset();
855 unsetImageRow(cursorY);
be72cb5c 856 }
fca67db0 857
7b5261bc
KL
858 cursorVisible = visible;
859 cursorX = x;
860 cursorY = y;
df8de03f
KL
861 }
862
863 /**
fca67db0 864 * Hide the cursor.
df8de03f 865 */
fca67db0 866 public final void hideCursor() {
7b5261bc 867 cursorVisible = false;
df8de03f 868 }
42873e30 869
3e074355
KL
870 /**
871 * Get the cursor visibility.
872 *
873 * @return true if the cursor is visible
874 */
875 public boolean isCursorVisible() {
876 return cursorVisible;
877 }
878
879 /**
880 * Get the cursor X position.
881 *
882 * @return the cursor x column position
883 */
884 public int getCursorX() {
885 return cursorX;
886 }
887
888 /**
889 * Get the cursor Y position.
890 *
891 * @return the cursor y row position
892 */
893 public int getCursorY() {
894 return cursorY;
895 }
896
42873e30
KL
897 /**
898 * Set the window title. Default implementation does nothing.
899 *
900 * @param title the new title
901 */
902 public void setTitle(final String title) {}
903
d36057df
KL
904 // ------------------------------------------------------------------------
905 // LogicalScreen ----------------------------------------------------------
906 // ------------------------------------------------------------------------
907
908 /**
909 * Reallocate screen buffers.
910 *
911 * @param width new width
912 * @param height new height
913 */
914 private synchronized void reallocate(final int width, final int height) {
915 if (logical != null) {
916 for (int row = 0; row < this.height; row++) {
917 for (int col = 0; col < this.width; col++) {
918 logical[col][row] = null;
919 }
920 }
921 logical = null;
922 }
923 logical = new Cell[width][height];
924 if (physical != null) {
925 for (int row = 0; row < this.height; row++) {
926 for (int col = 0; col < this.width; col++) {
927 physical[col][row] = null;
928 }
929 }
930 physical = null;
931 }
932 physical = new Cell[width][height];
933
934 for (int row = 0; row < height; row++) {
935 for (int col = 0; col < width; col++) {
936 physical[col][row] = new Cell();
937 logical[col][row] = new Cell();
938 }
939 }
940
941 this.width = width;
942 this.height = height;
943
944 clipLeft = 0;
945 clipTop = 0;
946 clipRight = width;
947 clipBottom = height;
948
949 reallyCleared = true;
950 }
951
952 /**
953 * Clear the physical screen.
954 */
955 public final void clearPhysical() {
956 for (int row = 0; row < height; row++) {
957 for (int col = 0; col < width; col++) {
a69ed767
KL
958 physical[col][row].unset();
959 }
960 }
961 }
962
963 /**
964 * Unset every image cell on one row of the physical screen, forcing
965 * images on that row to be redrawn.
966 *
967 * @param y row coordinate. 0 is the top-most row.
968 */
969 public final void unsetImageRow(final int y) {
9696a8f6
KL
970 if ((y < 0) || (y >= height)) {
971 return;
972 }
a69ed767
KL
973 for (int x = 0; x < width; x++) {
974 if (logical[x][y].isImage()) {
975 physical[x][y].unset();
d36057df
KL
976 }
977 }
978 }
979
3af53a35
KL
980 /**
981 * Render one fullwidth cell.
982 *
983 * @param x column coordinate. 0 is the left-most column.
984 * @param y row coordinate. 0 is the top-most row.
985 * @param cell the cell to draw
986 */
987 public final void putFullwidthCharXY(final int x, final int y,
988 final Cell cell) {
989
bfa37f3b
KL
990 int cellWidth = getTextWidth();
991 int cellHeight = getTextHeight();
992
993 if (lastTextHeight != cellHeight) {
994 glyphMaker = GlyphMaker.getInstance(cellHeight);
995 lastTextHeight = cellHeight;
996 }
997 BufferedImage image = glyphMaker.getImage(cell, cellWidth * 2,
998 cellHeight);
999 BufferedImage leftImage = image.getSubimage(0, 0, cellWidth,
1000 cellHeight);
1001 BufferedImage rightImage = image.getSubimage(cellWidth, 0, cellWidth,
1002 cellHeight);
3af53a35 1003
027de5ae 1004 Cell left = new Cell(cell);
3af53a35
KL
1005 left.setImage(leftImage);
1006 left.setWidth(Cell.Width.LEFT);
3af53a35
KL
1007 putCharXY(x, y, left);
1008
027de5ae 1009 Cell right = new Cell(cell);
3af53a35
KL
1010 right.setImage(rightImage);
1011 right.setWidth(Cell.Width.RIGHT);
3af53a35
KL
1012 putCharXY(x + 1, y, right);
1013 }
1014
1015 /**
1016 * Render one fullwidth character with attributes.
1017 *
1018 * @param x column coordinate. 0 is the left-most column.
1019 * @param y row coordinate. 0 is the top-most row.
1020 * @param ch character to draw
1021 * @param attr attributes to use (bold, foreColor, backColor)
1022 */
1023 public final void putFullwidthCharXY(final int x, final int y,
218d18db 1024 final int ch, final CellAttributes attr) {
3af53a35 1025
027de5ae 1026 Cell cell = new Cell(ch, attr);
3af53a35
KL
1027 putFullwidthCharXY(x, y, cell);
1028 }
1029
1030 /**
1031 * Render one fullwidth character with attributes.
1032 *
1033 * @param x column coordinate. 0 is the left-most column.
1034 * @param y row coordinate. 0 is the top-most row.
1035 * @param ch character to draw
1036 */
1037 public final void putFullwidthCharXY(final int x, final int y,
218d18db 1038 final int ch) {
3af53a35
KL
1039
1040 Cell cell = new Cell(ch);
1041 cell.setAttr(getAttrXY(x, y));
1042 putFullwidthCharXY(x, y, cell);
1043 }
1044
df8de03f 1045}