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 // Do not blindly call setDimension() on every screen. Instead
390 // call it only on those screens that do not already have the
391 // requested dimension. With this very small check, we have the
392 // ability for ANY screen in the MultiBackend to resize ALL of
394 if ((screen
.getWidth() != width
)
395 || (screen
.getHeight() != height
)
397 screen
.setDimensions(width
, height
);
399 // The screen that didn't change is probably the one that
400 // prompted the resize. Force it to repaint.
401 screen
.clearPhysical();
409 * @return current screen height
411 public int getHeight() {
412 // Return the smallest height of the screens.
413 int height
= screens
.get(0).getHeight();
414 for (Screen screen
: screens
) {
415 if (screen
.getHeight() < height
) {
416 height
= screen
.getHeight();
425 * @return current screen width
427 public int getWidth() {
428 // Return the smallest width of the screens.
429 int width
= screens
.get(0).getWidth();
430 for (Screen screen
: screens
) {
431 if (screen
.getWidth() < width
) {
432 width
= screen
.getWidth();
439 * Reset screen to not-bold, white-on-black. Also flushes the offset and
442 public void reset() {
443 for (Screen screen
: screens
) {
449 * Flush the offset and clip variables.
451 public void resetClipping() {
452 for (Screen screen
: screens
) {
453 screen
.resetClipping();
458 * Clear the logical screen.
460 public void clear() {
461 for (Screen screen
: screens
) {
467 * Draw a box with a border and empty background.
469 * @param left left column of box. 0 is the left-most row.
470 * @param top top row of the box. 0 is the top-most row.
471 * @param right right column of box
472 * @param bottom bottom row of the box
473 * @param border attributes to use for the border
474 * @param background attributes to use for the background
476 public void drawBox(final int left
, final int top
,
477 final int right
, final int bottom
,
478 final CellAttributes border
, final CellAttributes background
) {
480 for (Screen screen
: screens
) {
481 screen
.drawBox(left
, top
, right
, bottom
, border
, background
);
486 * Draw a box with a border and empty background.
488 * @param left left column of box. 0 is the left-most row.
489 * @param top top row of the box. 0 is the top-most row.
490 * @param right right column of box
491 * @param bottom bottom row of the box
492 * @param border attributes to use for the border
493 * @param background attributes to use for the background
494 * @param borderType if 1, draw a single-line border; if 2, draw a
495 * double-line border; if 3, draw double-line top/bottom edges and
496 * single-line left/right edges (like Qmodem)
497 * @param shadow if true, draw a "shadow" on the box
499 public void drawBox(final int left
, final int top
,
500 final int right
, final int bottom
,
501 final CellAttributes border
, final CellAttributes background
,
502 final int borderType
, final boolean shadow
) {
504 for (Screen screen
: screens
) {
505 screen
.drawBox(left
, top
, right
, bottom
, border
, background
,
513 * @param left left column of box. 0 is the left-most row.
514 * @param top top row of the box. 0 is the top-most row.
515 * @param right right column of box
516 * @param bottom bottom row of the box
518 public void drawBoxShadow(final int left
, final int top
,
519 final int right
, final int bottom
) {
521 for (Screen screen
: screens
) {
522 screen
.drawBoxShadow(left
, top
, right
, bottom
);
527 * Clear the physical screen.
529 public void clearPhysical() {
530 for (Screen screen
: screens
) {
531 screen
.clearPhysical();
536 * Unset every image cell on one row of the physical screen, forcing
537 * images on that row to be redrawn.
539 * @param y row coordinate. 0 is the top-most row.
541 public final void unsetImageRow(final int y
) {
542 for (Screen screen
: screens
) {
543 screen
.unsetImageRow(y
);
548 * Classes must provide an implementation to push the logical screen to
549 * the physical device.
551 public void flushPhysical() {
552 for (Screen screen
: screens
) {
553 screen
.flushPhysical();
558 * Put the cursor at (x,y).
560 * @param visible if true, the cursor should be visible
561 * @param x column coordinate to put the cursor on
562 * @param y row coordinate to put the cursor on
564 public void putCursor(final boolean visible
, final int x
, final int y
) {
565 for (Screen screen
: screens
) {
566 screen
.putCursor(visible
, x
, y
);
573 public void hideCursor() {
574 for (Screen screen
: screens
) {
580 * Get the cursor visibility.
582 * @return true if the cursor is visible
584 public boolean isCursorVisible() {
585 return screens
.get(0).isCursorVisible();
589 * Get the cursor X position.
591 * @return the cursor x column position
593 public int getCursorX() {
594 return screens
.get(0).getCursorX();
598 * Get the cursor Y position.
600 * @return the cursor y row position
602 public int getCursorY() {
603 return screens
.get(0).getCursorY();
607 * Set the window title.
609 * @param title the new title
611 public void setTitle(final String title
) {
612 for (Screen screen
: screens
) {
613 screen
.setTitle(title
);
617 // ------------------------------------------------------------------------
618 // MultiScreen ------------------------------------------------------------
619 // ------------------------------------------------------------------------
622 * Add a screen to the list.
624 * @param screen the screen to add
626 public void addScreen(final Screen screen
) {
631 * Remove a screen from the list.
633 * @param screen the screen to remove
635 public void removeScreen(final Screen screen
) {
636 if (screens
.size() > 1) {
637 screens
.remove(screen
);
642 * Get the width of a character cell in pixels.
644 * @return the width in pixels of a character cell
646 public int getTextWidth() {
648 for (Screen screen
: screens
) {
649 int newTextWidth
= textWidth
;
650 if (screen
instanceof MultiScreen
) {
651 newTextWidth
= ((MultiScreen
) screen
).getTextWidth();
652 } else if (screen
instanceof ECMA48Terminal
) {
653 newTextWidth
= ((ECMA48Terminal
) screen
).getTextWidth();
654 } else if (screen
instanceof SwingTerminal
) {
655 newTextWidth
= ((SwingTerminal
) screen
).getTextWidth();
657 if (newTextWidth
< textWidth
) {
658 textWidth
= newTextWidth
;
665 * Get the height of a character cell in pixels.
667 * @return the height in pixels of a character cell
669 public int getTextHeight() {
671 for (Screen screen
: screens
) {
672 int newTextHeight
= textHeight
;
673 if (screen
instanceof MultiScreen
) {
674 newTextHeight
= ((MultiScreen
) screen
).getTextHeight();
675 } else if (screen
instanceof ECMA48Terminal
) {
676 newTextHeight
= ((ECMA48Terminal
) screen
).getTextHeight();
677 } else if (screen
instanceof SwingTerminal
) {
678 newTextHeight
= ((SwingTerminal
) screen
).getTextHeight();
680 if (newTextHeight
< textHeight
) {
681 textHeight
= newTextHeight
;