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
.ArrayList
;
32 import java
.util
.List
;
34 import jexer
.bits
.Cell
;
35 import jexer
.bits
.CellAttributes
;
36 import jexer
.bits
.Clipboard
;
39 * MultiScreen mirrors its I/O to several screens.
41 public class MultiScreen
implements Screen
{
43 // ------------------------------------------------------------------------
44 // Variables --------------------------------------------------------------
45 // ------------------------------------------------------------------------
48 * The list of screens to use.
50 private List
<Screen
> screens
= new ArrayList
<Screen
>();
52 // ------------------------------------------------------------------------
53 // Constructors -----------------------------------------------------------
54 // ------------------------------------------------------------------------
57 * Public constructor requires one screen.
59 * @param screen the screen to add
61 public MultiScreen(final Screen screen
) {
65 // ------------------------------------------------------------------------
66 // Screen -----------------------------------------------------------------
67 // ------------------------------------------------------------------------
70 * Set drawing offset for x.
72 * @param offsetX new drawing offset
74 public void setOffsetX(final int offsetX
) {
75 for (Screen screen
: screens
) {
76 screen
.setOffsetX(offsetX
);
81 * Set drawing offset for y.
83 * @param offsetY new drawing offset
85 public void setOffsetY(final int offsetY
) {
86 for (Screen screen
: screens
) {
87 screen
.setOffsetY(offsetY
);
92 * Get right drawing clipping boundary.
94 * @return drawing boundary
96 public int getClipRight() {
97 if (screens
.size() > 0) {
98 return screens
.get(0).getClipRight();
104 * Set right drawing clipping boundary.
106 * @param clipRight new boundary
108 public void setClipRight(final int clipRight
) {
109 for (Screen screen
: screens
) {
110 screen
.setClipRight(clipRight
);
115 * Get bottom drawing clipping boundary.
117 * @return drawing boundary
119 public int getClipBottom() {
120 if (screens
.size() > 0) {
121 return screens
.get(0).getClipBottom();
127 * Set bottom drawing clipping boundary.
129 * @param clipBottom new boundary
131 public void setClipBottom(final int clipBottom
) {
132 for (Screen screen
: screens
) {
133 screen
.setClipBottom(clipBottom
);
138 * Get left drawing clipping boundary.
140 * @return drawing boundary
142 public int getClipLeft() {
143 if (screens
.size() > 0) {
144 return screens
.get(0).getClipLeft();
150 * Set left drawing clipping boundary.
152 * @param clipLeft new boundary
154 public void setClipLeft(final int clipLeft
) {
155 for (Screen screen
: screens
) {
156 screen
.setClipLeft(clipLeft
);
161 * Get top drawing clipping boundary.
163 * @return drawing boundary
165 public int getClipTop() {
166 if (screens
.size() > 0) {
167 return screens
.get(0).getClipTop();
173 * Set top drawing clipping boundary.
175 * @param clipTop new boundary
177 public void setClipTop(final int clipTop
) {
178 for (Screen screen
: screens
) {
179 screen
.setClipTop(clipTop
);
186 * @return if true, the logical screen is not in sync with the physical
189 public boolean isDirty() {
190 for (Screen screen
: screens
) {
191 if (screen
.isDirty()) {
199 * Get the attributes at one location.
201 * @param x column coordinate. 0 is the left-most column.
202 * @param y row coordinate. 0 is the top-most row.
203 * @return attributes at (x, y)
205 public CellAttributes
getAttrXY(final int x
, final int y
) {
206 if (screens
.size() > 0) {
207 return screens
.get(0).getAttrXY(x
, y
);
209 return new CellAttributes();
213 * Get the cell at one location.
215 * @param x column coordinate. 0 is the left-most column.
216 * @param y row coordinate. 0 is the top-most row.
217 * @return the character + attributes
219 public Cell
getCharXY(final int x
, final int y
) {
220 if (screens
.size() > 0) {
221 return screens
.get(0).getCharXY(x
, y
);
227 * Set the attributes at one location.
229 * @param x column coordinate. 0 is the left-most column.
230 * @param y row coordinate. 0 is the top-most row.
231 * @param attr attributes to use (bold, foreColor, backColor)
233 public void putAttrXY(final int x
, final int y
,
234 final CellAttributes attr
) {
236 for (Screen screen
: screens
) {
237 screen
.putAttrXY(x
, y
, attr
);
242 * Set the attributes at one location.
244 * @param x column coordinate. 0 is the left-most column.
245 * @param y row coordinate. 0 is the top-most row.
246 * @param attr attributes to use (bold, foreColor, backColor)
247 * @param clip if true, honor clipping/offset
249 public void putAttrXY(final int x
, final int y
,
250 final CellAttributes attr
, final boolean clip
) {
252 for (Screen screen
: screens
) {
253 screen
.putAttrXY(x
, y
, attr
, clip
);
258 * Fill the entire screen with one character with attributes.
260 * @param ch character to draw
261 * @param attr attributes to use (bold, foreColor, backColor)
263 public void putAll(final int ch
, final CellAttributes attr
) {
264 for (Screen screen
: screens
) {
265 screen
.putAll(ch
, attr
);
270 * Render one character with attributes.
272 * @param x column coordinate. 0 is the left-most column.
273 * @param y row coordinate. 0 is the top-most row.
274 * @param ch character + attributes to draw
276 public void putCharXY(final int x
, final int y
, final Cell ch
) {
277 for (Screen screen
: screens
) {
278 screen
.putCharXY(x
, y
, ch
);
283 * Render one character with attributes.
285 * @param x column coordinate. 0 is the left-most column.
286 * @param y row coordinate. 0 is the top-most row.
287 * @param ch character to draw
288 * @param attr attributes to use (bold, foreColor, backColor)
290 public void putCharXY(final int x
, final int y
, final int ch
,
291 final CellAttributes attr
) {
293 for (Screen screen
: screens
) {
294 screen
.putCharXY(x
, y
, ch
, attr
);
299 * Render one character without changing the underlying attributes.
301 * @param x column coordinate. 0 is the left-most column.
302 * @param y row coordinate. 0 is the top-most row.
303 * @param ch character to draw
305 public void putCharXY(final int x
, final int y
, final int ch
) {
306 for (Screen screen
: screens
) {
307 screen
.putCharXY(x
, y
, ch
);
312 * Render a string. Does not wrap if the string exceeds the line.
314 * @param x column coordinate. 0 is the left-most column.
315 * @param y row coordinate. 0 is the top-most row.
316 * @param str string to draw
317 * @param attr attributes to use (bold, foreColor, backColor)
319 public void putStringXY(final int x
, final int y
, final String str
,
320 final CellAttributes attr
) {
322 for (Screen screen
: screens
) {
323 screen
.putStringXY(x
, y
, str
, attr
);
328 * Render a string without changing the underlying attribute. Does not
329 * wrap if the string exceeds the line.
331 * @param x column coordinate. 0 is the left-most column.
332 * @param y row coordinate. 0 is the top-most row.
333 * @param str string to draw
335 public void putStringXY(final int x
, final int y
, final String str
) {
336 for (Screen screen
: screens
) {
337 screen
.putStringXY(x
, y
, str
);
342 * Draw a vertical line from (x, y) to (x, y + n).
344 * @param x column coordinate. 0 is the left-most column.
345 * @param y row coordinate. 0 is the top-most row.
346 * @param n number of characters to draw
347 * @param ch character to draw
348 * @param attr attributes to use (bold, foreColor, backColor)
350 public void vLineXY(final int x
, final int y
, final int n
,
351 final int ch
, final CellAttributes attr
) {
353 for (Screen screen
: screens
) {
354 screen
.vLineXY(x
, y
, n
, ch
, attr
);
359 * Draw a horizontal line from (x, y) to (x + n, y).
361 * @param x column coordinate. 0 is the left-most column.
362 * @param y row coordinate. 0 is the top-most row.
363 * @param n number of characters to draw
364 * @param ch character to draw
365 * @param attr attributes to use (bold, foreColor, backColor)
367 public void hLineXY(final int x
, final int y
, final int n
,
368 final int ch
, final CellAttributes attr
) {
370 for (Screen screen
: screens
) {
371 screen
.hLineXY(x
, y
, n
, ch
, attr
);
376 * Change the width. Everything on-screen will be destroyed and must be
379 * @param width new screen width
381 public void setWidth(final int width
) {
382 for (Screen screen
: screens
) {
383 screen
.setWidth(width
);
388 * Change the height. Everything on-screen will be destroyed and must be
391 * @param height new screen height
393 public void setHeight(final int height
) {
394 for (Screen screen
: screens
) {
395 screen
.setHeight(height
);
400 * Change the width and height. Everything on-screen will be destroyed
401 * and must be redrawn.
403 * @param width new screen width
404 * @param height new screen height
406 public void setDimensions(final int width
, final int height
) {
407 for (Screen screen
: screens
) {
408 // Do not blindly call setDimension() on every screen. Instead
409 // call it only on those screens that do not already have the
410 // requested dimension. With this very small check, we have the
411 // ability for ANY screen in the MultiBackend to resize ALL of
413 if ((screen
.getWidth() != width
)
414 || (screen
.getHeight() != height
)
416 screen
.setDimensions(width
, height
);
418 // The screen that didn't change is probably the one that
419 // prompted the resize. Force it to repaint.
420 screen
.clearPhysical();
428 * @return current screen height
430 public int getHeight() {
431 // Return the smallest height of the screens.
433 if (screens
.size() > 0) {
434 height
= screens
.get(0).getHeight();
436 for (Screen screen
: screens
) {
437 if (screen
.getHeight() < height
) {
438 height
= screen
.getHeight();
447 * @return current screen width
449 public int getWidth() {
450 // Return the smallest width of the screens.
452 if (screens
.size() > 0) {
453 width
= screens
.get(0).getWidth();
455 for (Screen screen
: screens
) {
456 if (screen
.getWidth() < width
) {
457 width
= screen
.getWidth();
464 * Reset screen to not-bold, white-on-black. Also flushes the offset and
467 public void reset() {
468 for (Screen screen
: screens
) {
474 * Flush the offset and clip variables.
476 public void resetClipping() {
477 for (Screen screen
: screens
) {
478 screen
.resetClipping();
483 * Clear the logical screen.
485 public void clear() {
486 for (Screen screen
: screens
) {
492 * Draw a box with a border and empty background.
494 * @param left left column of box. 0 is the left-most row.
495 * @param top top row of the box. 0 is the top-most row.
496 * @param right right column of box
497 * @param bottom bottom row of the box
498 * @param border attributes to use for the border
499 * @param background attributes to use for the background
501 public void drawBox(final int left
, final int top
,
502 final int right
, final int bottom
,
503 final CellAttributes border
, final CellAttributes background
) {
505 for (Screen screen
: screens
) {
506 screen
.drawBox(left
, top
, right
, bottom
, border
, background
);
511 * Draw a box with a border and empty 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
517 * @param border attributes to use for the border
518 * @param background attributes to use for the background
519 * @param borderType if 1, draw a single-line border; if 2, draw a
520 * double-line border; if 3, draw double-line top/bottom edges and
521 * single-line left/right edges (like Qmodem)
522 * @param shadow if true, draw a "shadow" on the box
524 public void drawBox(final int left
, final int top
,
525 final int right
, final int bottom
,
526 final CellAttributes border
, final CellAttributes background
,
527 final int borderType
, final boolean shadow
) {
529 for (Screen screen
: screens
) {
530 screen
.drawBox(left
, top
, right
, bottom
, border
, background
,
538 * @param left left column of box. 0 is the left-most row.
539 * @param top top row of the box. 0 is the top-most row.
540 * @param right right column of box
541 * @param bottom bottom row of the box
543 public void drawBoxShadow(final int left
, final int top
,
544 final int right
, final int bottom
) {
546 for (Screen screen
: screens
) {
547 screen
.drawBoxShadow(left
, top
, right
, bottom
);
552 * Clear the physical screen.
554 public void clearPhysical() {
555 for (Screen screen
: screens
) {
556 screen
.clearPhysical();
561 * Unset every image cell on one row of the physical screen, forcing
562 * images on that row to be redrawn.
564 * @param y row coordinate. 0 is the top-most row.
566 public final void unsetImageRow(final int y
) {
567 for (Screen screen
: screens
) {
568 screen
.unsetImageRow(y
);
573 * Classes must provide an implementation to push the logical screen to
574 * the physical device.
576 public void flushPhysical() {
577 for (Screen screen
: screens
) {
578 screen
.flushPhysical();
583 * Put the cursor at (x,y).
585 * @param visible if true, the cursor should be visible
586 * @param x column coordinate to put the cursor on
587 * @param y row coordinate to put the cursor on
589 public void putCursor(final boolean visible
, final int x
, final int y
) {
590 for (Screen screen
: screens
) {
591 screen
.putCursor(visible
, x
, y
);
598 public void hideCursor() {
599 for (Screen screen
: screens
) {
605 * Get the cursor visibility.
607 * @return true if the cursor is visible
609 public boolean isCursorVisible() {
610 if (screens
.size() > 0) {
611 return screens
.get(0).isCursorVisible();
617 * Get the cursor X position.
619 * @return the cursor x column position
621 public int getCursorX() {
622 if (screens
.size() > 0) {
623 return screens
.get(0).getCursorX();
629 * Get the cursor Y position.
631 * @return the cursor y row position
633 public int getCursorY() {
634 if (screens
.size() > 0) {
635 return screens
.get(0).getCursorY();
641 * Set the window title.
643 * @param title the new title
645 public void setTitle(final String title
) {
646 for (Screen screen
: screens
) {
647 screen
.setTitle(title
);
651 // ------------------------------------------------------------------------
652 // MultiScreen ------------------------------------------------------------
653 // ------------------------------------------------------------------------
656 * Add a screen to the list.
658 * @param screen the screen to add
660 public void addScreen(final Screen screen
) {
665 * Remove a screen from the list.
667 * @param screen the screen to remove
669 public void removeScreen(final Screen screen
) {
670 if (screens
.size() > 1) {
671 screens
.remove(screen
);
676 * Get the width of a character cell in pixels.
678 * @return the width in pixels of a character cell
680 public int getTextWidth() {
682 for (Screen screen
: screens
) {
683 int newTextWidth
= screen
.getTextWidth();
684 if (newTextWidth
< textWidth
) {
685 textWidth
= newTextWidth
;
692 * Get the height of a character cell in pixels.
694 * @return the height in pixels of a character cell
696 public int getTextHeight() {
698 for (Screen screen
: screens
) {
699 int newTextHeight
= screen
.getTextHeight();
700 if (newTextHeight
< textHeight
) {
701 textHeight
= newTextHeight
;
708 * Invert the cell color at a position, including both halves of a
711 * @param x column position
712 * @param y row position
714 public void invertCell(final int x
, final int y
) {
715 for (Screen screen
: screens
) {
716 screen
.invertCell(x
, y
);
721 * Invert the cell color at a position.
723 * @param x column position
724 * @param y row position
725 * @param onlyThisCell if true, only invert this cell, otherwise invert
726 * both halves of a double-width cell if necessary
728 public void invertCell(final int x
, final int y
,
729 final boolean onlyThisCell
) {
731 for (Screen screen
: screens
) {
732 screen
.invertCell(x
, y
, onlyThisCell
);
737 * Set a selection area on the screen.
739 * @param x0 the starting X position of the selection
740 * @param y0 the starting Y position of the selection
741 * @param x1 the ending X position of the selection
742 * @param y1 the ending Y position of the selection
743 * @param rectangle if true, this is a rectangle select
745 public void setSelection(final int x0
, final int y0
,
746 final int x1
, final int y1
, final boolean rectangle
) {
748 for (Screen screen
: screens
) {
749 screen
.setSelection(x0
, y0
, x1
, y1
, rectangle
);
754 * Copy the screen selection area to the clipboard.
756 * @param clipboard the clipboard to use
757 * @param x0 the starting X position of the selection
758 * @param y0 the starting Y position of the selection
759 * @param x1 the ending X position of the selection
760 * @param y1 the ending Y position of the selection
761 * @param rectangle if true, this is a rectangle select
763 public void copySelection(final Clipboard clipboard
,
764 final int x0
, final int y0
, final int x1
, final int y1
,
765 final boolean rectangle
) {
767 // Only copy from the first screen.
768 if (screens
.size() > 0) {
769 screens
.get(0).copySelection(clipboard
, x0
, y0
, x1
, y1
, rectangle
);