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