keyboard accelerators
[fanfix.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 */
fca67db0 56 private int offsetX;
48e27807
KL
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 */
fca67db0 70 private 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 }
fca67db0 80
df8de03f 81 /**
7b5261bc 82 * Ignore anything drawn right of clipRight.
df8de03f 83 */
fca67db0 84 private int clipRight;
48e27807
KL
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 */
fca67db0 107 private int clipBottom;
48e27807
KL
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 */
fca67db0 130 private int clipLeft;
48e27807
KL
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 */
fca67db0 153 private int clipTop;
48e27807
KL
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 */
fca67db0 217 public final CellAttributes getAttrXY(final int x, final int y) {
7b5261bc
KL
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 */
fca67db0
KL
230 public final void putAttrXY(final int x, final int y,
231 final CellAttributes attr) {
232
7b5261bc 233 putAttrXY(x, y, attr, true);
df8de03f 234 }
7b5261bc 235
df8de03f
KL
236 /**
237 * Set the attributes at one location.
238 *
239 * @param x column coordinate. 0 is the left-most column.
240 * @param y row coordinate. 0 is the top-most row.
241 * @param attr attributes to use (bold, foreColor, backColor)
242 * @param clip if true, honor clipping/offset
243 */
fca67db0
KL
244 public final void putAttrXY(final int x, final int y,
245 final CellAttributes attr, final boolean clip) {
7b5261bc
KL
246
247 int X = x;
248 int Y = y;
249
250 if (clip) {
251 if ((x < clipLeft)
252 || (x >= clipRight)
253 || (y < clipTop)
254 || (y >= clipBottom)
255 ) {
256 return;
257 }
258 X += offsetX;
259 Y += offsetY;
260 }
261
262 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
263 dirty = true;
264 logical[X][Y].setForeColor(attr.getForeColor());
265 logical[X][Y].setBackColor(attr.getBackColor());
266 logical[X][Y].setBold(attr.getBold());
267 logical[X][Y].setBlink(attr.getBlink());
268 logical[X][Y].setReverse(attr.getReverse());
269 logical[X][Y].setUnderline(attr.getUnderline());
270 logical[X][Y].setProtect(attr.getProtect());
271 }
df8de03f
KL
272 }
273
274 /**
275 * Fill the entire screen with one character with attributes.
276 *
277 * @param ch character to draw
278 * @param attr attributes to use (bold, foreColor, backColor)
279 */
fca67db0 280 public final void putAll(final char ch, final CellAttributes attr) {
7b5261bc
KL
281 for (int x = 0; x < width; x++) {
282 for (int y = 0; y < height; y++) {
283 putCharXY(x, y, ch, attr);
284 }
285 }
df8de03f
KL
286 }
287
288 /**
289 * Render one character with attributes.
290 *
291 * @param x column coordinate. 0 is the left-most column.
292 * @param y row coordinate. 0 is the top-most row.
293 * @param ch character + attributes to draw
294 */
fca67db0 295 public final void putCharXY(final int x, final int y, final Cell ch) {
7b5261bc 296 putCharXY(x, y, ch.getChar(), ch);
df8de03f
KL
297 }
298
299 /**
300 * Render one character with attributes.
301 *
302 * @param x column coordinate. 0 is the left-most column.
303 * @param y row coordinate. 0 is the top-most row.
304 * @param ch character to draw
305 * @param attr attributes to use (bold, foreColor, backColor)
306 */
fca67db0 307 public final void putCharXY(final int x, final int y, final char ch,
7b5261bc
KL
308 final CellAttributes attr) {
309
310 if ((x < clipLeft)
311 || (x >= clipRight)
312 || (y < clipTop)
313 || (y >= clipBottom)
314 ) {
315 return;
316 }
317
318 int X = x + offsetX;
319 int Y = y + offsetY;
320
321 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
322
323 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
324 dirty = true;
325
326 // Do not put control characters on the display
327 assert (ch >= 0x20);
328 assert (ch != 0x7F);
329
330 logical[X][Y].setChar(ch);
331 logical[X][Y].setForeColor(attr.getForeColor());
332 logical[X][Y].setBackColor(attr.getBackColor());
333 logical[X][Y].setBold(attr.getBold());
334 logical[X][Y].setBlink(attr.getBlink());
335 logical[X][Y].setReverse(attr.getReverse());
336 logical[X][Y].setUnderline(attr.getUnderline());
337 logical[X][Y].setProtect(attr.getProtect());
338 }
df8de03f
KL
339 }
340
341 /**
342 * Render one character without changing the underlying attributes.
343 *
344 * @param x column coordinate. 0 is the left-most column.
345 * @param y row coordinate. 0 is the top-most row.
346 * @param ch character to draw
347 */
fca67db0 348 public final void putCharXY(final int x, final int y, final char ch) {
7b5261bc
KL
349 if ((x < clipLeft)
350 || (x >= clipRight)
351 || (y < clipTop)
352 || (y >= clipBottom)
353 ) {
354 return;
355 }
df8de03f 356
7b5261bc
KL
357 int X = x + offsetX;
358 int Y = y + offsetY;
df8de03f 359
7b5261bc 360 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
df8de03f 361
7b5261bc
KL
362 if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
363 dirty = true;
364 logical[X][Y].setChar(ch);
365 }
df8de03f
KL
366 }
367
368 /**
369 * Render a string. Does not wrap if the string exceeds the line.
370 *
371 * @param x column coordinate. 0 is the left-most column.
372 * @param y row coordinate. 0 is the top-most row.
373 * @param str string to draw
374 * @param attr attributes to use (bold, foreColor, backColor)
375 */
fca67db0 376 public final void putStrXY(final int x, final int y, final String str,
7b5261bc
KL
377 final CellAttributes attr) {
378
379 int i = x;
380 for (int j = 0; j < str.length(); j++) {
381 char ch = str.charAt(j);
382 putCharXY(i, y, ch, attr);
383 i++;
384 if (i == width) {
385 break;
386 }
387 }
df8de03f
KL
388 }
389
390 /**
391 * Render a string without changing the underlying attribute. Does not
392 * wrap if the string exceeds the line.
393 *
394 * @param x column coordinate. 0 is the left-most column.
395 * @param y row coordinate. 0 is the top-most row.
396 * @param str string to draw
397 */
fca67db0 398 public final void putStrXY(final int x, final int y, final String str) {
7b5261bc
KL
399 int i = x;
400 for (int j = 0; j < str.length(); j++) {
401 char ch = str.charAt(j);
402 putCharXY(i, y, ch);
403 i++;
404 if (i == width) {
405 break;
406 }
407 }
df8de03f
KL
408 }
409
410 /**
7b5261bc 411 * Draw a vertical line from (x, y) to (x, y + n).
df8de03f
KL
412 *
413 * @param x column coordinate. 0 is the left-most column.
414 * @param y row coordinate. 0 is the top-most row.
415 * @param n number of characters to draw
416 * @param ch character to draw
417 * @param attr attributes to use (bold, foreColor, backColor)
418 */
fca67db0
KL
419 public final void vLineXY(final int x, final int y, final int n,
420 final char ch, final CellAttributes attr) {
7b5261bc
KL
421
422 for (int i = y; i < y + n; i++) {
423 putCharXY(x, i, ch, attr);
424 }
df8de03f
KL
425 }
426
427 /**
7b5261bc 428 * Draw a horizontal line from (x, y) to (x + n, y).
df8de03f
KL
429 *
430 * @param x column coordinate. 0 is the left-most column.
431 * @param y row coordinate. 0 is the top-most row.
432 * @param n number of characters to draw
433 * @param ch character to draw
434 * @param attr attributes to use (bold, foreColor, backColor)
435 */
fca67db0
KL
436 public final void hLineXY(final int x, final int y, final int n,
437 final char ch, final CellAttributes attr) {
7b5261bc
KL
438
439 for (int i = x; i < x + n; i++) {
440 putCharXY(i, y, ch, attr);
441 }
df8de03f
KL
442 }
443
444 /**
445 * Reallocate screen buffers.
446 *
447 * @param width new width
448 * @param height new height
449 */
7b5261bc
KL
450 private void reallocate(final int width, final int height) {
451 if (logical != null) {
452 for (int row = 0; row < this.height; row++) {
453 for (int col = 0; col < this.width; col++) {
454 logical[col][row] = null;
455 }
456 }
457 logical = null;
458 }
459 logical = new Cell[width][height];
460 if (physical != null) {
461 for (int row = 0; row < this.height; row++) {
462 for (int col = 0; col < this.width; col++) {
463 physical[col][row] = null;
464 }
465 }
466 physical = null;
467 }
468 physical = new Cell[width][height];
469
470 for (int row = 0; row < height; row++) {
471 for (int col = 0; col < width; col++) {
472 physical[col][row] = new Cell();
473 logical[col][row] = new Cell();
474 }
475 }
476
477 this.width = width;
478 this.height = height;
479
480 clipLeft = 0;
481 clipTop = 0;
482 clipRight = width;
483 clipBottom = height;
484
485 reallyCleared = true;
486 dirty = true;
df8de03f
KL
487 }
488
489 /**
490 * Change the width. Everything on-screen will be destroyed and must be
491 * redrawn.
7b5261bc 492 *
df8de03f
KL
493 * @param width new screen width
494 */
fca67db0 495 public final void setWidth(final int width) {
7b5261bc 496 reallocate(width, this.height);
df8de03f
KL
497 }
498
499 /**
500 * Change the height. Everything on-screen will be destroyed and must be
501 * redrawn.
502 *
503 * @param height new screen height
504 */
fca67db0 505 public final void setHeight(final int height) {
7b5261bc 506 reallocate(this.width, height);
df8de03f
KL
507 }
508
509 /**
510 * Change the width and height. Everything on-screen will be destroyed
511 * and must be redrawn.
512 *
513 * @param width new screen width
514 * @param height new screen height
515 */
fca67db0 516 public final void setDimensions(final int width, final int height) {
7b5261bc 517 reallocate(width, height);
df8de03f
KL
518 }
519
520 /**
521 * Get the height.
522 *
523 * @return current screen height
524 */
fca67db0 525 public final int getHeight() {
7b5261bc 526 return this.height;
df8de03f
KL
527 }
528
529 /**
530 * Get the width.
531 *
532 * @return current screen width
533 */
fca67db0 534 public final int getWidth() {
7b5261bc 535 return this.width;
df8de03f
KL
536 }
537
538 /**
539 * Public constructor. Sets everything to not-bold, white-on-black.
540 */
fca67db0 541 protected Screen() {
7b5261bc
KL
542 offsetX = 0;
543 offsetY = 0;
544 width = 80;
545 height = 24;
546 logical = null;
547 physical = null;
548 reallocate(width, height);
df8de03f
KL
549 }
550
551 /**
552 * Reset screen to not-bold, white-on-black. Also flushes the offset and
553 * clip variables.
554 */
fca67db0 555 public final void reset() {
7b5261bc
KL
556 dirty = true;
557 for (int row = 0; row < height; row++) {
558 for (int col = 0; col < width; col++) {
559 logical[col][row].reset();
560 }
561 }
562 resetClipping();
df8de03f
KL
563 }
564
565 /**
566 * Flush the offset and clip variables.
567 */
fca67db0 568 public final void resetClipping() {
7b5261bc
KL
569 offsetX = 0;
570 offsetY = 0;
571 clipLeft = 0;
572 clipTop = 0;
573 clipRight = width;
574 clipBottom = height;
df8de03f
KL
575 }
576
577 /**
578 * Force the screen to be fully cleared and redrawn on the next flush().
579 */
fca67db0 580 public final void clear() {
7b5261bc 581 reset();
df8de03f
KL
582 }
583
584 /**
585 * Draw a box with a border and empty background.
586 *
587 * @param left left column of box. 0 is the left-most row.
588 * @param top top row of the box. 0 is the top-most row.
589 * @param right right column of box
590 * @param bottom bottom row of the box
7b5261bc 591 * @param border attributes to use for the border
df8de03f
KL
592 * @param background attributes to use for the background
593 */
fca67db0 594 public final void drawBox(final int left, final int top,
7b5261bc
KL
595 final int right, final int bottom,
596 final CellAttributes border, final CellAttributes background) {
597
598 drawBox(left, top, right, bottom, border, background, 1, false);
df8de03f
KL
599 }
600
601 /**
602 * Draw a box with a border and empty background.
603 *
604 * @param left left column of box. 0 is the left-most row.
605 * @param top top row of the box. 0 is the top-most row.
606 * @param right right column of box
607 * @param bottom bottom row of the box
7b5261bc 608 * @param border attributes to use for the border
df8de03f 609 * @param background attributes to use for the background
7b5261bc
KL
610 * @param borderType if 1, draw a single-line border; if 2, draw a
611 * double-line border; if 3, draw double-line top/bottom edges and
612 * single-line left/right edges (like Qmodem)
df8de03f
KL
613 * @param shadow if true, draw a "shadow" on the box
614 */
fca67db0 615 public final void drawBox(final int left, final int top,
7b5261bc
KL
616 final int right, final int bottom,
617 final CellAttributes border, final CellAttributes background,
618 final int borderType, final boolean shadow) {
619
620 int boxTop = top;
621 int boxLeft = left;
622 int boxWidth = right - left;
623 int boxHeight = bottom - top;
624
625 char cTopLeft;
626 char cTopRight;
627 char cBottomLeft;
628 char cBottomRight;
629 char cHSide;
630 char cVSide;
631
632 switch (borderType) {
633 case 1:
634 cTopLeft = GraphicsChars.ULCORNER;
635 cTopRight = GraphicsChars.URCORNER;
636 cBottomLeft = GraphicsChars.LLCORNER;
637 cBottomRight = GraphicsChars.LRCORNER;
638 cHSide = GraphicsChars.SINGLE_BAR;
639 cVSide = GraphicsChars.WINDOW_SIDE;
640 break;
641
642 case 2:
643 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP_DOUBLE;
644 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP_DOUBLE;
645 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM_DOUBLE;
646 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM_DOUBLE;
647 cHSide = GraphicsChars.DOUBLE_BAR;
648 cVSide = GraphicsChars.WINDOW_SIDE_DOUBLE;
649 break;
650
651 case 3:
652 cTopLeft = GraphicsChars.WINDOW_LEFT_TOP;
653 cTopRight = GraphicsChars.WINDOW_RIGHT_TOP;
654 cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM;
655 cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM;
656 cHSide = GraphicsChars.WINDOW_TOP;
657 cVSide = GraphicsChars.WINDOW_SIDE;
658 break;
659 default:
660 throw new IllegalArgumentException("Invalid border type: "
661 + borderType);
662 }
663
664 // Place the corner characters
665 putCharXY(left, top, cTopLeft, border);
666 putCharXY(left + boxWidth - 1, top, cTopRight, border);
667 putCharXY(left, top + boxHeight - 1, cBottomLeft, border);
668 putCharXY(left + boxWidth - 1, top + boxHeight - 1, cBottomRight,
669 border);
670
671 // Draw the box lines
672 hLineXY(left + 1, top, boxWidth - 2, cHSide, border);
673 vLineXY(left, top + 1, boxHeight - 2, cVSide, border);
674 hLineXY(left + 1, top + boxHeight - 1, boxWidth - 2, cHSide, border);
675 vLineXY(left + boxWidth - 1, top + 1, boxHeight - 2, cVSide, border);
676
677 // Fill in the interior background
678 for (int i = 1; i < boxHeight - 1; i++) {
679 hLineXY(1 + left, i + top, boxWidth - 2, ' ', background);
680 }
681
682 if (shadow) {
683 // Draw a shadow
684 drawBoxShadow(left, top, right, bottom);
685 }
df8de03f
KL
686 }
687
688 /**
fca67db0 689 * Draw a box shadow.
df8de03f
KL
690 *
691 * @param left left column of box. 0 is the left-most row.
692 * @param top top row of the box. 0 is the top-most row.
693 * @param right right column of box
694 * @param bottom bottom row of the box
695 */
fca67db0 696 public final void drawBoxShadow(final int left, final int top,
7b5261bc
KL
697 final int right, final int bottom) {
698
699 int boxTop = top;
700 int boxLeft = left;
701 int boxWidth = right - left;
702 int boxHeight = bottom - top;
703 CellAttributes shadowAttr = new CellAttributes();
704
705 // Shadows do not honor clipping but they DO honor offset.
706 int oldClipRight = clipRight;
707 int oldClipBottom = clipBottom;
708 /*
709 clipRight = boxWidth + 2;
710 clipBottom = boxHeight + 1;
711 */
712 clipRight = width;
713 clipBottom = height;
714
715 for (int i = 0; i < boxHeight; i++) {
716 putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
717 putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr);
718 }
719 for (int i = 0; i < boxWidth; i++) {
720 putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr);
721 }
722 clipRight = oldClipRight;
723 clipBottom = oldClipBottom;
df8de03f
KL
724 }
725
726 /**
727 * Subclasses must provide an implementation to push the logical screen
728 * to the physical device.
729 */
730 abstract public void flushPhysical();
731
732 /**
733 * Put the cursor at (x,y).
734 *
735 * @param visible if true, the cursor should be visible
736 * @param x column coordinate to put the cursor on
737 * @param y row coordinate to put the cursor on
738 */
fca67db0
KL
739 public final void putCursor(final boolean visible,
740 final int x, final int y) {
741
7b5261bc
KL
742 cursorVisible = visible;
743 cursorX = x;
744 cursorY = y;
df8de03f
KL
745 }
746
747 /**
fca67db0 748 * Hide the cursor.
df8de03f 749 */
fca67db0 750 public final void hideCursor() {
7b5261bc 751 cursorVisible = false;
df8de03f
KL
752 }
753}