2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
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:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
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.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 package jexer
.backend
;
31 import java
.util
.LinkedList
;
32 import java
.util
.List
;
34 import jexer
.bits
.Cell
;
35 import jexer
.bits
.CellAttributes
;
38 * MultiScreen mirrors its I/O to several screens.
40 public class MultiScreen
implements Screen
{
42 // ------------------------------------------------------------------------
43 // Variables --------------------------------------------------------------
44 // ------------------------------------------------------------------------
47 * The list of screens to use.
49 private List
<Screen
> screens
= new LinkedList
<Screen
>();
51 // ------------------------------------------------------------------------
52 // Constructors -----------------------------------------------------------
53 // ------------------------------------------------------------------------
56 * Public constructor requires one screen.
58 * @param screen the screen to add
60 public MultiScreen(final Screen screen
) {
64 // ------------------------------------------------------------------------
65 // Screen -----------------------------------------------------------------
66 // ------------------------------------------------------------------------
69 * Set drawing offset for x.
71 * @param offsetX new drawing offset
73 public void setOffsetX(final int offsetX
) {
74 for (Screen screen
: screens
) {
75 screen
.setOffsetX(offsetX
);
80 * Set drawing offset for y.
82 * @param offsetY new drawing offset
84 public void setOffsetY(final int offsetY
) {
85 for (Screen screen
: screens
) {
86 screen
.setOffsetY(offsetY
);
91 * Get right drawing clipping boundary.
93 * @return drawing boundary
95 public int getClipRight() {
96 return screens
.get(0).getClipRight();
100 * Set right drawing clipping boundary.
102 * @param clipRight new boundary
104 public void setClipRight(final int clipRight
) {
105 for (Screen screen
: screens
) {
106 screen
.setClipRight(clipRight
);
111 * Get bottom drawing clipping boundary.
113 * @return drawing boundary
115 public int getClipBottom() {
116 return screens
.get(0).getClipBottom();
120 * Set bottom drawing clipping boundary.
122 * @param clipBottom new boundary
124 public void setClipBottom(final int clipBottom
) {
125 for (Screen screen
: screens
) {
126 screen
.setClipBottom(clipBottom
);
131 * Get left drawing clipping boundary.
133 * @return drawing boundary
135 public int getClipLeft() {
136 return screens
.get(0).getClipLeft();
140 * Set left drawing clipping boundary.
142 * @param clipLeft new boundary
144 public void setClipLeft(final int clipLeft
) {
145 for (Screen screen
: screens
) {
146 screen
.setClipLeft(clipLeft
);
151 * Get top drawing clipping boundary.
153 * @return drawing boundary
155 public int getClipTop() {
156 return screens
.get(0).getClipTop();
160 * Set top drawing clipping boundary.
162 * @param clipTop new boundary
164 public void setClipTop(final int clipTop
) {
165 for (Screen screen
: screens
) {
166 screen
.setClipTop(clipTop
);
173 * @return if true, the logical screen is not in sync with the physical
176 public boolean isDirty() {
177 for (Screen screen
: screens
) {
178 if (screen
.isDirty()) {
186 * Get the attributes at one location.
188 * @param x column coordinate. 0 is the left-most column.
189 * @param y row coordinate. 0 is the top-most row.
190 * @return attributes at (x, y)
192 public CellAttributes
getAttrXY(final int x
, final int y
) {
193 return screens
.get(0).getAttrXY(x
, y
);
197 * Get the cell at one location.
199 * @param x column coordinate. 0 is the left-most column.
200 * @param y row coordinate. 0 is the top-most row.
201 * @return the character + attributes
203 public Cell
getCharXY(final int x
, final int y
) {
204 return screens
.get(0).getCharXY(x
, y
);
208 * Set the attributes at one location.
210 * @param x column coordinate. 0 is the left-most column.
211 * @param y row coordinate. 0 is the top-most row.
212 * @param attr attributes to use (bold, foreColor, backColor)
214 public void putAttrXY(final int x
, final int y
,
215 final CellAttributes attr
) {
217 for (Screen screen
: screens
) {
218 screen
.putAttrXY(x
, y
, attr
);
223 * Set the attributes at one location.
225 * @param x column coordinate. 0 is the left-most column.
226 * @param y row coordinate. 0 is the top-most row.
227 * @param attr attributes to use (bold, foreColor, backColor)
228 * @param clip if true, honor clipping/offset
230 public void putAttrXY(final int x
, final int y
,
231 final CellAttributes attr
, final boolean clip
) {
233 for (Screen screen
: screens
) {
234 screen
.putAttrXY(x
, y
, attr
, clip
);
239 * Fill the entire screen with one character with attributes.
241 * @param ch character to draw
242 * @param attr attributes to use (bold, foreColor, backColor)
244 public void putAll(final char ch
, final CellAttributes attr
) {
245 for (Screen screen
: screens
) {
246 screen
.putAll(ch
, attr
);
251 * Render one character with attributes.
253 * @param x column coordinate. 0 is the left-most column.
254 * @param y row coordinate. 0 is the top-most row.
255 * @param ch character + attributes to draw
257 public void putCharXY(final int x
, final int y
, final Cell ch
) {
258 for (Screen screen
: screens
) {
259 screen
.putCharXY(x
, y
, ch
);
264 * Render one character with attributes.
266 * @param x column coordinate. 0 is the left-most column.
267 * @param y row coordinate. 0 is the top-most row.
268 * @param ch character to draw
269 * @param attr attributes to use (bold, foreColor, backColor)
271 public void putCharXY(final int x
, final int y
, final char ch
,
272 final CellAttributes attr
) {
274 for (Screen screen
: screens
) {
275 screen
.putCharXY(x
, y
, ch
, attr
);
280 * Render one character without changing the underlying attributes.
282 * @param x column coordinate. 0 is the left-most column.
283 * @param y row coordinate. 0 is the top-most row.
284 * @param ch character to draw
286 public void putCharXY(final int x
, final int y
, final char ch
) {
287 for (Screen screen
: screens
) {
288 screen
.putCharXY(x
, y
, ch
);
293 * Render a string. Does not wrap if the string exceeds the line.
295 * @param x column coordinate. 0 is the left-most column.
296 * @param y row coordinate. 0 is the top-most row.
297 * @param str string to draw
298 * @param attr attributes to use (bold, foreColor, backColor)
300 public void putStringXY(final int x
, final int y
, final String str
,
301 final CellAttributes attr
) {
303 for (Screen screen
: screens
) {
304 screen
.putStringXY(x
, y
, str
, attr
);
309 * Render a string without changing the underlying attribute. Does not
310 * wrap if the string exceeds the line.
312 * @param x column coordinate. 0 is the left-most column.
313 * @param y row coordinate. 0 is the top-most row.
314 * @param str string to draw
316 public void putStringXY(final int x
, final int y
, final String str
) {
317 for (Screen screen
: screens
) {
318 screen
.putStringXY(x
, y
, str
);
323 * Draw a vertical line from (x, y) to (x, y + n).
325 * @param x column coordinate. 0 is the left-most column.
326 * @param y row coordinate. 0 is the top-most row.
327 * @param n number of characters to draw
328 * @param ch character to draw
329 * @param attr attributes to use (bold, foreColor, backColor)
331 public void vLineXY(final int x
, final int y
, final int n
,
332 final char ch
, final CellAttributes attr
) {
334 for (Screen screen
: screens
) {
335 screen
.vLineXY(x
, y
, n
, ch
, attr
);
340 * Draw a horizontal line from (x, y) to (x + n, y).
342 * @param x column coordinate. 0 is the left-most column.
343 * @param y row coordinate. 0 is the top-most row.
344 * @param n number of characters to draw
345 * @param ch character to draw
346 * @param attr attributes to use (bold, foreColor, backColor)
348 public void hLineXY(final int x
, final int y
, final int n
,
349 final char ch
, final CellAttributes attr
) {
351 for (Screen screen
: screens
) {
352 screen
.hLineXY(x
, y
, n
, ch
, attr
);
357 * Change the width. Everything on-screen will be destroyed and must be
360 * @param width new screen width
362 public void setWidth(final int width
) {
363 for (Screen screen
: screens
) {
364 screen
.setWidth(width
);
369 * Change the height. Everything on-screen will be destroyed and must be
372 * @param height new screen height
374 public void setHeight(final int height
) {
375 for (Screen screen
: screens
) {
376 screen
.setHeight(height
);
381 * Change the width and height. Everything on-screen will be destroyed
382 * and must be redrawn.
384 * @param width new screen width
385 * @param height new screen height
387 public void setDimensions(final int width
, final int height
) {
388 for (Screen screen
: screens
) {
389 screen
.setDimensions(width
, height
);
396 * @return current screen height
398 public int getHeight() {
399 // Return the smallest height of the screens.
400 int height
= screens
.get(0).getHeight();
401 for (Screen screen
: screens
) {
402 if (screen
.getHeight() < height
) {
403 height
= screen
.getHeight();
412 * @return current screen width
414 public int getWidth() {
415 // Return the smallest width of the screens.
416 int width
= screens
.get(0).getWidth();
417 for (Screen screen
: screens
) {
418 if (screen
.getWidth() < width
) {
419 width
= screen
.getWidth();
426 * Reset screen to not-bold, white-on-black. Also flushes the offset and
429 public void reset() {
430 for (Screen screen
: screens
) {
436 * Flush the offset and clip variables.
438 public void resetClipping() {
439 for (Screen screen
: screens
) {
440 screen
.resetClipping();
445 * Clear the logical screen.
447 public void clear() {
448 for (Screen screen
: screens
) {
454 * Draw a box with a border and empty background.
456 * @param left left column of box. 0 is the left-most row.
457 * @param top top row of the box. 0 is the top-most row.
458 * @param right right column of box
459 * @param bottom bottom row of the box
460 * @param border attributes to use for the border
461 * @param background attributes to use for the background
463 public void drawBox(final int left
, final int top
,
464 final int right
, final int bottom
,
465 final CellAttributes border
, final CellAttributes background
) {
467 for (Screen screen
: screens
) {
468 screen
.drawBox(left
, top
, right
, bottom
, border
, background
);
473 * Draw a box with a border and empty background.
475 * @param left left column of box. 0 is the left-most row.
476 * @param top top row of the box. 0 is the top-most row.
477 * @param right right column of box
478 * @param bottom bottom row of the box
479 * @param border attributes to use for the border
480 * @param background attributes to use for the background
481 * @param borderType if 1, draw a single-line border; if 2, draw a
482 * double-line border; if 3, draw double-line top/bottom edges and
483 * single-line left/right edges (like Qmodem)
484 * @param shadow if true, draw a "shadow" on the box
486 public void drawBox(final int left
, final int top
,
487 final int right
, final int bottom
,
488 final CellAttributes border
, final CellAttributes background
,
489 final int borderType
, final boolean shadow
) {
491 for (Screen screen
: screens
) {
492 screen
.drawBox(left
, top
, right
, bottom
, border
, background
,
500 * @param left left column of box. 0 is the left-most row.
501 * @param top top row of the box. 0 is the top-most row.
502 * @param right right column of box
503 * @param bottom bottom row of the box
505 public void drawBoxShadow(final int left
, final int top
,
506 final int right
, final int bottom
) {
508 for (Screen screen
: screens
) {
509 screen
.drawBoxShadow(left
, top
, right
, bottom
);
514 * Clear the physical screen.
516 public void clearPhysical() {
517 for (Screen screen
: screens
) {
518 screen
.clearPhysical();
523 * Unset every image cell on one row of the physical screen, forcing
524 * images on that row to be redrawn.
526 * @param y row coordinate. 0 is the top-most row.
528 public final void unsetImageRow(final int y
) {
529 for (Screen screen
: screens
) {
530 screen
.unsetImageRow(y
);
535 * Classes must provide an implementation to push the logical screen to
536 * the physical device.
538 public void flushPhysical() {
539 for (Screen screen
: screens
) {
540 screen
.flushPhysical();
545 * Put the cursor at (x,y).
547 * @param visible if true, the cursor should be visible
548 * @param x column coordinate to put the cursor on
549 * @param y row coordinate to put the cursor on
551 public void putCursor(final boolean visible
, final int x
, final int y
) {
552 for (Screen screen
: screens
) {
553 screen
.putCursor(visible
, x
, y
);
560 public void hideCursor() {
561 for (Screen screen
: screens
) {
567 * Get the cursor visibility.
569 * @return true if the cursor is visible
571 public boolean isCursorVisible() {
572 return screens
.get(0).isCursorVisible();
576 * Get the cursor X position.
578 * @return the cursor x column position
580 public int getCursorX() {
581 return screens
.get(0).getCursorX();
585 * Get the cursor Y position.
587 * @return the cursor y row position
589 public int getCursorY() {
590 return screens
.get(0).getCursorY();
594 * Set the window title.
596 * @param title the new title
598 public void setTitle(final String title
) {
599 for (Screen screen
: screens
) {
600 screen
.setTitle(title
);
604 // ------------------------------------------------------------------------
605 // MultiScreen ------------------------------------------------------------
606 // ------------------------------------------------------------------------
609 * Add a screen to the list.
611 * @param screen the screen to add
613 public void addScreen(final Screen screen
) {
618 * Remove a screen from the list.
620 * @param screen the screen to remove
622 public void removeScreen(final Screen screen
) {
623 if (screens
.size() > 1) {
624 screens
.remove(screen
);
629 * Get the width of a character cell in pixels.
631 * @return the width in pixels of a character cell
633 public int getTextWidth() {
635 for (Screen screen
: screens
) {
636 int newTextWidth
= textWidth
;
637 if (screen
instanceof MultiScreen
) {
638 newTextWidth
= ((MultiScreen
) screen
).getTextWidth();
639 } else if (screen
instanceof ECMA48Terminal
) {
640 newTextWidth
= ((ECMA48Terminal
) screen
).getTextWidth();
641 } else if (screen
instanceof SwingTerminal
) {
642 newTextWidth
= ((SwingTerminal
) screen
).getTextWidth();
644 if (newTextWidth
< textWidth
) {
645 textWidth
= newTextWidth
;
652 * Get the height of a character cell in pixels.
654 * @return the height in pixels of a character cell
656 public int getTextHeight() {
658 for (Screen screen
: screens
) {
659 int newTextHeight
= textHeight
;
660 if (screen
instanceof MultiScreen
) {
661 newTextHeight
= ((MultiScreen
) screen
).getTextHeight();
662 } else if (screen
instanceof ECMA48Terminal
) {
663 newTextHeight
= ((ECMA48Terminal
) screen
).getTextHeight();
664 } else if (screen
instanceof SwingTerminal
) {
665 newTextHeight
= ((SwingTerminal
) screen
).getTextHeight();
667 if (newTextHeight
< textHeight
) {
668 textHeight
= newTextHeight
;