TWindow compiles
[fanfix.git] / src / jexer / io / Screen.java
1 /**
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
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.
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
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31 package jexer.io;
32
33 import jexer.bits.Cell;
34 import jexer.bits.CellAttributes;
35 import jexer.bits.GraphicsChars;
36
37 /**
38 * This class represents a text-based screen. Drawing operations write to a
39 * logical screen.
40 */
41 public abstract class Screen {
42
43 /**
44 * Width of the visible window.
45 */
46 protected int width;
47
48 /**
49 * Height of the visible window.
50 */
51 protected int height;
52
53 /**
54 * Drawing offset for x.
55 */
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 }
66
67 /**
68 * Drawing offset for y.
69 */
70 protected int offsetY;
71
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
81 /**
82 * Ignore anything drawn right of clipRight.
83 */
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 }
103
104 /**
105 * Ignore anything drawn below clipBottom.
106 */
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 }
126
127 /**
128 * Ignore anything drawn left of clipLeft.
129 */
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 }
149
150 /**
151 * Ignore anything drawn above clipTop.
152 */
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 }
172
173 /**
174 * The physical screen last sent out on flush().
175 */
176 protected Cell [][] physical;
177
178 /**
179 * The logical screen being rendered to.
180 */
181 protected Cell [][] logical;
182
183 /**
184 * When true, logical != physical.
185 */
186 protected boolean dirty;
187
188 /**
189 * Set if the user explicitly wants to redraw everything starting with a
190 * ECMATerminal.clearAll().
191 */
192 protected boolean reallyCleared;
193
194 /**
195 * If true, the cursor is visible and should be placed onscreen at
196 * (cursorX, cursorY) during a call to flushPhysical().
197 */
198 protected boolean cursorVisible;
199
200 /**
201 * Cursor X position if visible.
202 */
203 protected int cursorX;
204
205 /**
206 * Cursor Y position if visible.
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 */
217 public CellAttributes getAttrXY(final int x, final int y) {
218 CellAttributes attr = new CellAttributes();
219 attr.setTo(logical[x][y]);
220 return attr;
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)
229 */
230 public void putAttrXY(final int x, final int y, final CellAttributes attr) {
231 putAttrXY(x, y, attr, true);
232 }
233
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 */
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 }
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 */
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 }
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 */
293 public void putCharXY(final int x, final int y, final Cell ch) {
294 putCharXY(x, y, ch.getChar(), ch);
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 */
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 }
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 */
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 }
354
355 int X = x + offsetX;
356 int Y = y + offsetY;
357
358 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
359
360 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
361 dirty = true;
362 logical[X][Y].setChar(ch);
363 }
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 */
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 }
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 */
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 }
406 }
407
408 /**
409 * Draw a vertical line from (x, y) to (x, y + n).
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 */
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 }
423 }
424
425 /**
426 * Draw a horizontal line from (x, y) to (x + n, y).
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 */
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 }
440 }
441
442 /**
443 * Reallocate screen buffers.
444 *
445 * @param width new width
446 * @param height new height
447 */
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;
485 }
486
487 /**
488 * Change the width. Everything on-screen will be destroyed and must be
489 * redrawn.
490 *
491 * @param width new screen width
492 */
493 public void setWidth(final int width) {
494 reallocate(width, this.height);
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 */
503 public void setHeight(final int height) {
504 reallocate(this.width, height);
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 */
514 public void setDimensions(final int width, final int height) {
515 reallocate(width, height);
516 }
517
518 /**
519 * Get the height.
520 *
521 * @return current screen height
522 */
523 public int getHeight() {
524 return this.height;
525 }
526
527 /**
528 * Get the width.
529 *
530 * @return current screen width
531 */
532 public int getWidth() {
533 return this.width;
534 }
535
536 /**
537 * Public constructor. Sets everything to not-bold, white-on-black.
538 */
539 public Screen() {
540 offsetX = 0;
541 offsetY = 0;
542 width = 80;
543 height = 24;
544 logical = null;
545 physical = null;
546 reallocate(width, height);
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() {
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();
561 }
562
563 /**
564 * Flush the offset and clip variables.
565 */
566 public void resetClipping() {
567 offsetX = 0;
568 offsetY = 0;
569 clipLeft = 0;
570 clipTop = 0;
571 clipRight = width;
572 clipBottom = height;
573 }
574
575 /**
576 * Force the screen to be fully cleared and redrawn on the next flush().
577 */
578 public void clear() {
579 reset();
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
589 * @param border attributes to use for the border
590 * @param background attributes to use for the background
591 */
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);
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
606 * @param border attributes to use for the border
607 * @param background attributes to use for the background
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)
611 * @param shadow if true, draw a "shadow" on the box
612 */
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 }
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 */
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;
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 */
737 public void putCursor(final boolean visible, final int x, final int y) {
738 cursorVisible = visible;
739 cursorX = x;
740 cursorY = y;
741 }
742
743 /**
744 * Hide the cursor
745 */
746 public void hideCursor() {
747 cursorVisible = false;
748 }
749 }