#16 Refactor Swing backend, demo of multiple TApplications in one Swing frame
[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) 2017 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 jexer.bits.Cell;
32 import jexer.bits.CellAttributes;
33 import jexer.bits.GraphicsChars;
34
35 /**
36 * A logical screen composed of a 2D array of Cells.
37 */
38 public class LogicalScreen implements Screen {
39
40 /**
41 * Width of the visible window.
42 */
43 protected int width;
44
45 /**
46 * Height of the visible window.
47 */
48 protected int height;
49
50 /**
51 * Drawing offset for x.
52 */
53 private int offsetX;
54
55 /**
56 * Set drawing offset for x.
57 *
58 * @param offsetX new drawing offset
59 */
60 public final void setOffsetX(final int offsetX) {
61 this.offsetX = offsetX;
62 }
63
64 /**
65 * Drawing offset for y.
66 */
67 private int offsetY;
68
69 /**
70 * Set drawing offset for y.
71 *
72 * @param offsetY new drawing offset
73 */
74 public final void setOffsetY(final int offsetY) {
75 this.offsetY = offsetY;
76 }
77
78 /**
79 * Ignore anything drawn right of clipRight.
80 */
81 private int clipRight;
82
83 /**
84 * Get right drawing clipping boundary.
85 *
86 * @return drawing boundary
87 */
88 public final int getClipRight() {
89 return clipRight;
90 }
91
92 /**
93 * Set right drawing clipping boundary.
94 *
95 * @param clipRight new boundary
96 */
97 public final void setClipRight(final int clipRight) {
98 this.clipRight = clipRight;
99 }
100
101 /**
102 * Ignore anything drawn below clipBottom.
103 */
104 private int clipBottom;
105
106 /**
107 * Get bottom drawing clipping boundary.
108 *
109 * @return drawing boundary
110 */
111 public final int getClipBottom() {
112 return clipBottom;
113 }
114
115 /**
116 * Set bottom drawing clipping boundary.
117 *
118 * @param clipBottom new boundary
119 */
120 public final void setClipBottom(final int clipBottom) {
121 this.clipBottom = clipBottom;
122 }
123
124 /**
125 * Ignore anything drawn left of clipLeft.
126 */
127 private int clipLeft;
128
129 /**
130 * Get left drawing clipping boundary.
131 *
132 * @return drawing boundary
133 */
134 public final int getClipLeft() {
135 return clipLeft;
136 }
137
138 /**
139 * Set left drawing clipping boundary.
140 *
141 * @param clipLeft new boundary
142 */
143 public final void setClipLeft(final int clipLeft) {
144 this.clipLeft = clipLeft;
145 }
146
147 /**
148 * Ignore anything drawn above clipTop.
149 */
150 private int clipTop;
151
152 /**
153 * Get top drawing clipping boundary.
154 *
155 * @return drawing boundary
156 */
157 public final int getClipTop() {
158 return clipTop;
159 }
160
161 /**
162 * Set top drawing clipping boundary.
163 *
164 * @param clipTop new boundary
165 */
166 public final void setClipTop(final int clipTop) {
167 this.clipTop = clipTop;
168 }
169
170 /**
171 * The physical screen last sent out on flush().
172 */
173 protected Cell [][] physical;
174
175 /**
176 * The logical screen being rendered to.
177 */
178 protected Cell [][] logical;
179
180 /**
181 * When true, logical != physical.
182 */
183 protected volatile boolean dirty;
184
185 /**
186 * Get dirty flag.
187 *
188 * @return if true, the logical screen is not in sync with the physical
189 * screen
190 */
191 public final boolean isDirty() {
192 return dirty;
193 }
194
195 /**
196 * Set if the user explicitly wants to redraw everything starting with a
197 * ECMATerminal.clearAll().
198 */
199 protected boolean reallyCleared;
200
201 /**
202 * If true, the cursor is visible and should be placed onscreen at
203 * (cursorX, cursorY) during a call to flushPhysical().
204 */
205 protected boolean cursorVisible;
206
207 /**
208 * Cursor X position if visible.
209 */
210 protected int cursorX;
211
212 /**
213 * Cursor Y position if visible.
214 */
215 protected int cursorY;
216
217 /**
218 * Get the attributes at one location.
219 *
220 * @param x column coordinate. 0 is the left-most column.
221 * @param y row coordinate. 0 is the top-most row.
222 * @return attributes at (x, y)
223 */
224 public final CellAttributes getAttrXY(final int x, final int y) {
225 CellAttributes attr = new CellAttributes();
226 if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
227 attr.setTo(logical[x][y]);
228 }
229 return attr;
230 }
231
232 /**
233 * Set the attributes at one location.
234 *
235 * @param x column coordinate. 0 is the left-most column.
236 * @param y row coordinate. 0 is the top-most row.
237 * @param attr attributes to use (bold, foreColor, backColor)
238 */
239 public final void putAttrXY(final int x, final int y,
240 final CellAttributes attr) {
241
242 putAttrXY(x, y, attr, true);
243 }
244
245 /**
246 * Set the attributes at one location.
247 *
248 * @param x column coordinate. 0 is the left-most column.
249 * @param y row coordinate. 0 is the top-most row.
250 * @param attr attributes to use (bold, foreColor, backColor)
251 * @param clip if true, honor clipping/offset
252 */
253 public final void putAttrXY(final int x, final int y,
254 final CellAttributes attr, final boolean clip) {
255
256 int X = x;
257 int Y = y;
258
259 if (clip) {
260 if ((x < clipLeft)
261 || (x >= clipRight)
262 || (y < clipTop)
263 || (y >= clipBottom)
264 ) {
265 return;
266 }
267 X += offsetX;
268 Y += offsetY;
269 }
270
271 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
272 dirty = true;
273 logical[X][Y].setForeColor(attr.getForeColor());
274 logical[X][Y].setBackColor(attr.getBackColor());
275 logical[X][Y].setBold(attr.isBold());
276 logical[X][Y].setBlink(attr.isBlink());
277 logical[X][Y].setReverse(attr.isReverse());
278 logical[X][Y].setUnderline(attr.isUnderline());
279 logical[X][Y].setProtect(attr.isProtect());
280 }
281 }
282
283 /**
284 * Fill the entire screen with one character with attributes.
285 *
286 * @param ch character to draw
287 * @param attr attributes to use (bold, foreColor, backColor)
288 */
289 public final void putAll(final char ch, final CellAttributes attr) {
290
291 for (int x = 0; x < width; x++) {
292 for (int y = 0; y < height; y++) {
293 putCharXY(x, y, ch, attr);
294 }
295 }
296 }
297
298 /**
299 * Render one character with attributes.
300 *
301 * @param x column coordinate. 0 is the left-most column.
302 * @param y row coordinate. 0 is the top-most row.
303 * @param ch character + attributes to draw
304 */
305 public final void putCharXY(final int x, final int y, final Cell ch) {
306 putCharXY(x, y, ch.getChar(), ch);
307 }
308
309 /**
310 * Render one character with attributes.
311 *
312 * @param x column coordinate. 0 is the left-most column.
313 * @param y row coordinate. 0 is the top-most row.
314 * @param ch character to draw
315 * @param attr attributes to use (bold, foreColor, backColor)
316 */
317 public final void putCharXY(final int x, final int y, final char ch,
318 final CellAttributes attr) {
319
320 if ((x < clipLeft)
321 || (x >= clipRight)
322 || (y < clipTop)
323 || (y >= clipBottom)
324 ) {
325 return;
326 }
327
328 int X = x + offsetX;
329 int Y = y + offsetY;
330
331 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
332
333 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
334 dirty = true;
335
336 // Do not put control characters on the display
337 assert (ch >= 0x20);
338 assert (ch != 0x7F);
339
340 logical[X][Y].setChar(ch);
341 logical[X][Y].setForeColor(attr.getForeColor());
342 logical[X][Y].setBackColor(attr.getBackColor());
343 logical[X][Y].setBold(attr.isBold());
344 logical[X][Y].setBlink(attr.isBlink());
345 logical[X][Y].setReverse(attr.isReverse());
346 logical[X][Y].setUnderline(attr.isUnderline());
347 logical[X][Y].setProtect(attr.isProtect());
348 }
349 }
350
351 /**
352 * Render one character without changing the underlying attributes.
353 *
354 * @param x column coordinate. 0 is the left-most column.
355 * @param y row coordinate. 0 is the top-most row.
356 * @param ch character to draw
357 */
358 public final void putCharXY(final int x, final int y, final char ch) {
359
360 if ((x < clipLeft)
361 || (x >= clipRight)
362 || (y < clipTop)
363 || (y >= clipBottom)
364 ) {
365 return;
366 }
367
368 int X = x + offsetX;
369 int Y = y + offsetY;
370
371 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
372
373 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
374 dirty = true;
375 logical[X][Y].setChar(ch);
376 }
377 }
378
379 /**
380 * Render a string. Does not wrap if the string exceeds the line.
381 *
382 * @param x column coordinate. 0 is the left-most column.
383 * @param y row coordinate. 0 is the top-most row.
384 * @param str string to draw
385 * @param attr attributes to use (bold, foreColor, backColor)
386 */
387 public final void putStringXY(final int x, final int y, final String str,
388 final CellAttributes attr) {
389
390 int i = x;
391 for (int j = 0; j < str.length(); j++) {
392 char ch = str.charAt(j);
393 putCharXY(i, y, ch, attr);
394 i++;
395 if (i == width) {
396 break;
397 }
398 }
399 }
400
401 /**
402 * Render a string without changing the underlying attribute. Does not
403 * wrap if the string exceeds the line.
404 *
405 * @param x column coordinate. 0 is the left-most column.
406 * @param y row coordinate. 0 is the top-most row.
407 * @param str string to draw
408 */
409 public final void putStringXY(final int x, final int y, final String str) {
410
411 int i = x;
412 for (int j = 0; j < str.length(); j++) {
413 char ch = str.charAt(j);
414 putCharXY(i, y, ch);
415 i++;
416 if (i == width) {
417 break;
418 }
419 }
420 }
421
422 /**
423 * Draw a vertical line from (x, y) to (x, y + n).
424 *
425 * @param x column coordinate. 0 is the left-most column.
426 * @param y row coordinate. 0 is the top-most row.
427 * @param n number of characters to draw
428 * @param ch character to draw
429 * @param attr attributes to use (bold, foreColor, backColor)
430 */
431 public final void vLineXY(final int x, final int y, final int n,
432 final char ch, final CellAttributes attr) {
433
434 for (int i = y; i < y + n; i++) {
435 putCharXY(x, i, ch, attr);
436 }
437 }
438
439 /**
440 * Draw a horizontal line from (x, y) to (x + n, y).
441 *
442 * @param x column coordinate. 0 is the left-most column.
443 * @param y row coordinate. 0 is the top-most row.
444 * @param n number of characters to draw
445 * @param ch character to draw
446 * @param attr attributes to use (bold, foreColor, backColor)
447 */
448 public final void hLineXY(final int x, final int y, final int n,
449 final char ch, final CellAttributes attr) {
450
451 for (int i = x; i < x + n; i++) {
452 putCharXY(i, y, ch, attr);
453 }
454 }
455
456 /**
457 * Reallocate screen buffers.
458 *
459 * @param width new width
460 * @param height new height
461 */
462 private synchronized void reallocate(final int width, final int height) {
463 if (logical != null) {
464 for (int row = 0; row < this.height; row++) {
465 for (int col = 0; col < this.width; col++) {
466 logical[col][row] = null;
467 }
468 }
469 logical = null;
470 }
471 logical = new Cell[width][height];
472 if (physical != null) {
473 for (int row = 0; row < this.height; row++) {
474 for (int col = 0; col < this.width; col++) {
475 physical[col][row] = null;
476 }
477 }
478 physical = null;
479 }
480 physical = new Cell[width][height];
481
482 for (int row = 0; row < height; row++) {
483 for (int col = 0; col < width; col++) {
484 physical[col][row] = new Cell();
485 logical[col][row] = new Cell();
486 }
487 }
488
489 this.width = width;
490 this.height = height;
491
492 clipLeft = 0;
493 clipTop = 0;
494 clipRight = width;
495 clipBottom = height;
496
497 reallyCleared = true;
498 dirty = true;
499 }
500
501 /**
502 * Change the width. Everything on-screen will be destroyed and must be
503 * redrawn.
504 *
505 * @param width new screen width
506 */
507 public final synchronized void setWidth(final int width) {
508 reallocate(width, this.height);
509 }
510
511 /**
512 * Change the height. Everything on-screen will be destroyed and must be
513 * redrawn.
514 *
515 * @param height new screen height
516 */
517 public final synchronized void setHeight(final int height) {
518 reallocate(this.width, height);
519 }
520
521 /**
522 * Change the width and height. Everything on-screen will be destroyed
523 * and must be redrawn.
524 *
525 * @param width new screen width
526 * @param height new screen height
527 */
528 public final void setDimensions(final int width, final int height) {
529 reallocate(width, height);
530 }
531
532 /**
533 * Get the height.
534 *
535 * @return current screen height
536 */
537 public final synchronized int getHeight() {
538 return this.height;
539 }
540
541 /**
542 * Get the width.
543 *
544 * @return current screen width
545 */
546 public final synchronized int getWidth() {
547 return this.width;
548 }
549
550 /**
551 * Public constructor. Sets everything to not-bold, white-on-black.
552 */
553 protected LogicalScreen() {
554 offsetX = 0;
555 offsetY = 0;
556 width = 80;
557 height = 24;
558 logical = null;
559 physical = null;
560 reallocate(width, height);
561 }
562
563 /**
564 * Reset screen to not-bold, white-on-black. Also flushes the offset and
565 * clip variables.
566 */
567 public final synchronized void reset() {
568 dirty = true;
569 for (int row = 0; row < height; row++) {
570 for (int col = 0; col < width; col++) {
571 logical[col][row].reset();
572 }
573 }
574 resetClipping();
575 }
576
577 /**
578 * Flush the offset and clip variables.
579 */
580 public final void resetClipping() {
581 offsetX = 0;
582 offsetY = 0;
583 clipLeft = 0;
584 clipTop = 0;
585 clipRight = width;
586 clipBottom = height;
587 }
588
589 /**
590 * Clear the logical screen.
591 */
592 public final void clear() {
593 reset();
594 }
595
596 /**
597 * Clear the physical screen.
598 */
599 public final void clearPhysical() {
600 dirty = true;
601 for (int row = 0; row < height; row++) {
602 for (int col = 0; col < width; col++) {
603 physical[col][row].reset();
604 }
605 }
606 }
607
608 /**
609 * Draw a box with a border and empty background.
610 *
611 * @param left left column of box. 0 is the left-most row.
612 * @param top top row of the box. 0 is the top-most row.
613 * @param right right column of box
614 * @param bottom bottom row of the box
615 * @param border attributes to use for the border
616 * @param background attributes to use for the background
617 */
618 public final void drawBox(final int left, final int top,
619 final int right, final int bottom,
620 final CellAttributes border, final CellAttributes background) {
621
622 drawBox(left, top, right, bottom, border, background, 1, false);
623 }
624
625 /**
626 * Draw a box with a border and empty background.
627 *
628 * @param left left column of box. 0 is the left-most row.
629 * @param top top row of the box. 0 is the top-most row.
630 * @param right right column of box
631 * @param bottom bottom row of the box
632 * @param border attributes to use for the border
633 * @param background attributes to use for the background
634 * @param borderType if 1, draw a single-line border; if 2, draw a
635 * double-line border; if 3, draw double-line top/bottom edges and
636 * single-line left/right edges (like Qmodem)
637 * @param shadow if true, draw a "shadow" on the box
638 */
639 public final void drawBox(final int left, final int top,
640 final int right, final int bottom,
641 final CellAttributes border, final CellAttributes background,
642 final int borderType, final boolean shadow) {
643
644 int boxWidth = right - left;
645 int boxHeight = bottom - top;
646
647 char cTopLeft;
648 char cTopRight;
649 char cBottomLeft;
650 char cBottomRight;
651 char cHSide;
652 char cVSide;
653
654 switch (borderType) {
655 case 1:
656 cTopLeft = GraphicsChars.ULCORNER;
657 cTopRight = GraphicsChars.URCORNER;
658 cBottomLeft = GraphicsChars.LLCORNER;
659 cBottomRight = GraphicsChars.LRCORNER;
660 cHSide = GraphicsChars.SINGLE_BAR;
661 cVSide = GraphicsChars.WINDOW_SIDE;
662 break;
663
664 case 2:
665 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP_DOUBLE;
666 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP_DOUBLE;
667 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM_DOUBLE;
668 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM_DOUBLE;
669 cHSide = GraphicsChars.DOUBLE_BAR;
670 cVSide = GraphicsChars.WINDOW_SIDE_DOUBLE;
671 break;
672
673 case 3:
674 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP;
675 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP;
676 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM;
677 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM;
678 cHSide = GraphicsChars.WINDOW_TOP;
679 cVSide = GraphicsChars.WINDOW_SIDE;
680 break;
681 default:
682 throw new IllegalArgumentException("Invalid border type: "
683 + borderType);
684 }
685
686 // Place the corner characters
687 putCharXY(left, top, cTopLeft, border);
688 putCharXY(left + boxWidth - 1, top, cTopRight, border);
689 putCharXY(left, top + boxHeight - 1, cBottomLeft, border);
690 putCharXY(left + boxWidth - 1, top + boxHeight - 1, cBottomRight,
691 border);
692
693 // Draw the box lines
694 hLineXY(left + 1, top, boxWidth - 2, cHSide, border);
695 vLineXY(left, top + 1, boxHeight - 2, cVSide, border);
696 hLineXY(left + 1, top + boxHeight - 1, boxWidth - 2, cHSide, border);
697 vLineXY(left + boxWidth - 1, top + 1, boxHeight - 2, cVSide, border);
698
699 // Fill in the interior background
700 for (int i = 1; i < boxHeight - 1; i++) {
701 hLineXY(1 + left, i + top, boxWidth - 2, ' ', background);
702 }
703
704 if (shadow) {
705 // Draw a shadow
706 drawBoxShadow(left, top, right, bottom);
707 }
708 }
709
710 /**
711 * Draw a box shadow.
712 *
713 * @param left left column of box. 0 is the left-most row.
714 * @param top top row of the box. 0 is the top-most row.
715 * @param right right column of box
716 * @param bottom bottom row of the box
717 */
718 public final void drawBoxShadow(final int left, final int top,
719 final int right, final int bottom) {
720
721 int boxTop = top;
722 int boxLeft = left;
723 int boxWidth = right - left;
724 int boxHeight = bottom - top;
725 CellAttributes shadowAttr = new CellAttributes();
726
727 // Shadows do not honor clipping but they DO honor offset.
728 int oldClipRight = clipRight;
729 int oldClipBottom = clipBottom;
730 /*
731 clipRight = boxWidth + 2;
732 clipBottom = boxHeight + 1;
733 */
734 clipRight = width;
735 clipBottom = height;
736
737 for (int i = 0; i < boxHeight; i++) {
738 putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
739 putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr);
740 }
741 for (int i = 0; i < boxWidth; i++) {
742 putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr);
743 }
744 clipRight = oldClipRight;
745 clipBottom = oldClipBottom;
746 }
747
748 /**
749 * Default implementation does nothing.
750 */
751 public void flushPhysical() {}
752
753 /**
754 * Put the cursor at (x,y).
755 *
756 * @param visible if true, the cursor should be visible
757 * @param x column coordinate to put the cursor on
758 * @param y row coordinate to put the cursor on
759 */
760 public void putCursor(final boolean visible, final int x, final int y) {
761
762 cursorVisible = visible;
763 cursorX = x;
764 cursorY = y;
765 }
766
767 /**
768 * Hide the cursor.
769 */
770 public final void hideCursor() {
771 cursorVisible = false;
772 }
773
774 /**
775 * Set the window title. Default implementation does nothing.
776 *
777 * @param title the new title
778 */
779 public void setTitle(final String title) {}
780
781 }