Merge branch 'upstream' into subtree
[nikiroo-utils.git] / backend / MultiScreen.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.backend;
30
31 import java.util.ArrayList;
32 import java.util.List;
33
34 import jexer.bits.Cell;
35 import jexer.bits.CellAttributes;
36 import jexer.bits.Clipboard;
37
38 /**
39 * MultiScreen mirrors its I/O to several screens.
40 */
41 public class MultiScreen implements Screen {
42
43 // ------------------------------------------------------------------------
44 // Variables --------------------------------------------------------------
45 // ------------------------------------------------------------------------
46
47 /**
48 * The list of screens to use.
49 */
50 private List<Screen> screens = new ArrayList<Screen>();
51
52 // ------------------------------------------------------------------------
53 // Constructors -----------------------------------------------------------
54 // ------------------------------------------------------------------------
55
56 /**
57 * Public constructor requires one screen.
58 *
59 * @param screen the screen to add
60 */
61 public MultiScreen(final Screen screen) {
62 screens.add(screen);
63 }
64
65 // ------------------------------------------------------------------------
66 // Screen -----------------------------------------------------------------
67 // ------------------------------------------------------------------------
68
69 /**
70 * Set drawing offset for x.
71 *
72 * @param offsetX new drawing offset
73 */
74 public void setOffsetX(final int offsetX) {
75 for (Screen screen: screens) {
76 screen.setOffsetX(offsetX);
77 }
78 }
79
80 /**
81 * Set drawing offset for y.
82 *
83 * @param offsetY new drawing offset
84 */
85 public void setOffsetY(final int offsetY) {
86 for (Screen screen: screens) {
87 screen.setOffsetY(offsetY);
88 }
89 }
90
91 /**
92 * Get right drawing clipping boundary.
93 *
94 * @return drawing boundary
95 */
96 public int getClipRight() {
97 if (screens.size() > 0) {
98 return screens.get(0).getClipRight();
99 }
100 return 0;
101 }
102
103 /**
104 * Set right drawing clipping boundary.
105 *
106 * @param clipRight new boundary
107 */
108 public void setClipRight(final int clipRight) {
109 for (Screen screen: screens) {
110 screen.setClipRight(clipRight);
111 }
112 }
113
114 /**
115 * Get bottom drawing clipping boundary.
116 *
117 * @return drawing boundary
118 */
119 public int getClipBottom() {
120 if (screens.size() > 0) {
121 return screens.get(0).getClipBottom();
122 }
123 return 0;
124 }
125
126 /**
127 * Set bottom drawing clipping boundary.
128 *
129 * @param clipBottom new boundary
130 */
131 public void setClipBottom(final int clipBottom) {
132 for (Screen screen: screens) {
133 screen.setClipBottom(clipBottom);
134 }
135 }
136
137 /**
138 * Get left drawing clipping boundary.
139 *
140 * @return drawing boundary
141 */
142 public int getClipLeft() {
143 if (screens.size() > 0) {
144 return screens.get(0).getClipLeft();
145 }
146 return 0;
147 }
148
149 /**
150 * Set left drawing clipping boundary.
151 *
152 * @param clipLeft new boundary
153 */
154 public void setClipLeft(final int clipLeft) {
155 for (Screen screen: screens) {
156 screen.setClipLeft(clipLeft);
157 }
158 }
159
160 /**
161 * Get top drawing clipping boundary.
162 *
163 * @return drawing boundary
164 */
165 public int getClipTop() {
166 if (screens.size() > 0) {
167 return screens.get(0).getClipTop();
168 }
169 return 0;
170 }
171
172 /**
173 * Set top drawing clipping boundary.
174 *
175 * @param clipTop new boundary
176 */
177 public void setClipTop(final int clipTop) {
178 for (Screen screen: screens) {
179 screen.setClipTop(clipTop);
180 }
181 }
182
183 /**
184 * Get dirty flag.
185 *
186 * @return if true, the logical screen is not in sync with the physical
187 * screen
188 */
189 public boolean isDirty() {
190 for (Screen screen: screens) {
191 if (screen.isDirty()) {
192 return true;
193 }
194 }
195 return false;
196 }
197
198 /**
199 * Get the attributes at one location.
200 *
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)
204 */
205 public CellAttributes getAttrXY(final int x, final int y) {
206 if (screens.size() > 0) {
207 return screens.get(0).getAttrXY(x, y);
208 }
209 return new CellAttributes();
210 }
211
212 /**
213 * Get the cell at one location.
214 *
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
218 */
219 public Cell getCharXY(final int x, final int y) {
220 if (screens.size() > 0) {
221 return screens.get(0).getCharXY(x, y);
222 }
223 return new Cell();
224 }
225
226 /**
227 * Set the attributes at one location.
228 *
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)
232 */
233 public void putAttrXY(final int x, final int y,
234 final CellAttributes attr) {
235
236 for (Screen screen: screens) {
237 screen.putAttrXY(x, y, attr);
238 }
239 }
240
241 /**
242 * Set the attributes at one location.
243 *
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
248 */
249 public void putAttrXY(final int x, final int y,
250 final CellAttributes attr, final boolean clip) {
251
252 for (Screen screen: screens) {
253 screen.putAttrXY(x, y, attr, clip);
254 }
255 }
256
257 /**
258 * Fill the entire screen with one character with attributes.
259 *
260 * @param ch character to draw
261 * @param attr attributes to use (bold, foreColor, backColor)
262 */
263 public void putAll(final int ch, final CellAttributes attr) {
264 for (Screen screen: screens) {
265 screen.putAll(ch, attr);
266 }
267 }
268
269 /**
270 * Render one character with attributes.
271 *
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
275 */
276 public void putCharXY(final int x, final int y, final Cell ch) {
277 for (Screen screen: screens) {
278 screen.putCharXY(x, y, ch);
279 }
280 }
281
282 /**
283 * Render one character with attributes.
284 *
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)
289 */
290 public void putCharXY(final int x, final int y, final int ch,
291 final CellAttributes attr) {
292
293 for (Screen screen: screens) {
294 screen.putCharXY(x, y, ch, attr);
295 }
296 }
297
298 /**
299 * Render one character without changing the underlying attributes.
300 *
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
304 */
305 public void putCharXY(final int x, final int y, final int ch) {
306 for (Screen screen: screens) {
307 screen.putCharXY(x, y, ch);
308 }
309 }
310
311 /**
312 * Render a string. Does not wrap if the string exceeds the line.
313 *
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)
318 */
319 public void putStringXY(final int x, final int y, final String str,
320 final CellAttributes attr) {
321
322 for (Screen screen: screens) {
323 screen.putStringXY(x, y, str, attr);
324 }
325 }
326
327 /**
328 * Render a string without changing the underlying attribute. Does not
329 * wrap if the string exceeds the line.
330 *
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
334 */
335 public void putStringXY(final int x, final int y, final String str) {
336 for (Screen screen: screens) {
337 screen.putStringXY(x, y, str);
338 }
339 }
340
341 /**
342 * Draw a vertical line from (x, y) to (x, y + n).
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 n number of characters to draw
347 * @param ch character to draw
348 * @param attr attributes to use (bold, foreColor, backColor)
349 */
350 public void vLineXY(final int x, final int y, final int n,
351 final int ch, final CellAttributes attr) {
352
353 for (Screen screen: screens) {
354 screen.vLineXY(x, y, n, ch, attr);
355 }
356 }
357
358 /**
359 * Draw a horizontal line from (x, y) to (x + n, y).
360 *
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)
366 */
367 public void hLineXY(final int x, final int y, final int n,
368 final int ch, final CellAttributes attr) {
369
370 for (Screen screen: screens) {
371 screen.hLineXY(x, y, n, ch, attr);
372 }
373 }
374
375 /**
376 * Change the width. Everything on-screen will be destroyed and must be
377 * redrawn.
378 *
379 * @param width new screen width
380 */
381 public void setWidth(final int width) {
382 for (Screen screen: screens) {
383 screen.setWidth(width);
384 }
385 }
386
387 /**
388 * Change the height. Everything on-screen will be destroyed and must be
389 * redrawn.
390 *
391 * @param height new screen height
392 */
393 public void setHeight(final int height) {
394 for (Screen screen: screens) {
395 screen.setHeight(height);
396 }
397 }
398
399 /**
400 * Change the width and height. Everything on-screen will be destroyed
401 * and must be redrawn.
402 *
403 * @param width new screen width
404 * @param height new screen height
405 */
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
412 // the screens.
413 if ((screen.getWidth() != width)
414 || (screen.getHeight() != height)
415 ) {
416 screen.setDimensions(width, height);
417 } else {
418 // The screen that didn't change is probably the one that
419 // prompted the resize. Force it to repaint.
420 screen.clearPhysical();
421 }
422 }
423 }
424
425 /**
426 * Get the height.
427 *
428 * @return current screen height
429 */
430 public int getHeight() {
431 // Return the smallest height of the screens.
432 int height = 25;
433 if (screens.size() > 0) {
434 height = screens.get(0).getHeight();
435 }
436 for (Screen screen: screens) {
437 if (screen.getHeight() < height) {
438 height = screen.getHeight();
439 }
440 }
441 return height;
442 }
443
444 /**
445 * Get the width.
446 *
447 * @return current screen width
448 */
449 public int getWidth() {
450 // Return the smallest width of the screens.
451 int width = 80;
452 if (screens.size() > 0) {
453 width = screens.get(0).getWidth();
454 }
455 for (Screen screen: screens) {
456 if (screen.getWidth() < width) {
457 width = screen.getWidth();
458 }
459 }
460 return width;
461 }
462
463 /**
464 * Reset screen to not-bold, white-on-black. Also flushes the offset and
465 * clip variables.
466 */
467 public void reset() {
468 for (Screen screen: screens) {
469 screen.reset();
470 }
471 }
472
473 /**
474 * Flush the offset and clip variables.
475 */
476 public void resetClipping() {
477 for (Screen screen: screens) {
478 screen.resetClipping();
479 }
480 }
481
482 /**
483 * Clear the logical screen.
484 */
485 public void clear() {
486 for (Screen screen: screens) {
487 screen.clear();
488 }
489 }
490
491 /**
492 * Draw a box with a border and empty background.
493 *
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
500 */
501 public void drawBox(final int left, final int top,
502 final int right, final int bottom,
503 final CellAttributes border, final CellAttributes background) {
504
505 for (Screen screen: screens) {
506 screen.drawBox(left, top, right, bottom, border, background);
507 }
508 }
509
510 /**
511 * Draw a box with a border and empty background.
512 *
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
523 */
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) {
528
529 for (Screen screen: screens) {
530 screen.drawBox(left, top, right, bottom, border, background,
531 borderType, shadow);
532 }
533 }
534
535 /**
536 * Draw a box shadow.
537 *
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
542 */
543 public void drawBoxShadow(final int left, final int top,
544 final int right, final int bottom) {
545
546 for (Screen screen: screens) {
547 screen.drawBoxShadow(left, top, right, bottom);
548 }
549 }
550
551 /**
552 * Clear the physical screen.
553 */
554 public void clearPhysical() {
555 for (Screen screen: screens) {
556 screen.clearPhysical();
557 }
558 }
559
560 /**
561 * Unset every image cell on one row of the physical screen, forcing
562 * images on that row to be redrawn.
563 *
564 * @param y row coordinate. 0 is the top-most row.
565 */
566 public final void unsetImageRow(final int y) {
567 for (Screen screen: screens) {
568 screen.unsetImageRow(y);
569 }
570 }
571
572 /**
573 * Classes must provide an implementation to push the logical screen to
574 * the physical device.
575 */
576 public void flushPhysical() {
577 for (Screen screen: screens) {
578 screen.flushPhysical();
579 }
580 }
581
582 /**
583 * Put the cursor at (x,y).
584 *
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
588 */
589 public void putCursor(final boolean visible, final int x, final int y) {
590 for (Screen screen: screens) {
591 screen.putCursor(visible, x, y);
592 }
593 }
594
595 /**
596 * Hide the cursor.
597 */
598 public void hideCursor() {
599 for (Screen screen: screens) {
600 screen.hideCursor();
601 }
602 }
603
604 /**
605 * Get the cursor visibility.
606 *
607 * @return true if the cursor is visible
608 */
609 public boolean isCursorVisible() {
610 if (screens.size() > 0) {
611 return screens.get(0).isCursorVisible();
612 }
613 return true;
614 }
615
616 /**
617 * Get the cursor X position.
618 *
619 * @return the cursor x column position
620 */
621 public int getCursorX() {
622 if (screens.size() > 0) {
623 return screens.get(0).getCursorX();
624 }
625 return 0;
626 }
627
628 /**
629 * Get the cursor Y position.
630 *
631 * @return the cursor y row position
632 */
633 public int getCursorY() {
634 if (screens.size() > 0) {
635 return screens.get(0).getCursorY();
636 }
637 return 0;
638 }
639
640 /**
641 * Set the window title.
642 *
643 * @param title the new title
644 */
645 public void setTitle(final String title) {
646 for (Screen screen: screens) {
647 screen.setTitle(title);
648 }
649 }
650
651 // ------------------------------------------------------------------------
652 // MultiScreen ------------------------------------------------------------
653 // ------------------------------------------------------------------------
654
655 /**
656 * Add a screen to the list.
657 *
658 * @param screen the screen to add
659 */
660 public void addScreen(final Screen screen) {
661 screens.add(screen);
662 }
663
664 /**
665 * Remove a screen from the list.
666 *
667 * @param screen the screen to remove
668 */
669 public void removeScreen(final Screen screen) {
670 if (screens.size() > 1) {
671 screens.remove(screen);
672 }
673 }
674
675 /**
676 * Get the width of a character cell in pixels.
677 *
678 * @return the width in pixels of a character cell
679 */
680 public int getTextWidth() {
681 int textWidth = 16;
682 for (Screen screen: screens) {
683 int newTextWidth = screen.getTextWidth();
684 if (newTextWidth < textWidth) {
685 textWidth = newTextWidth;
686 }
687 }
688 return textWidth;
689 }
690
691 /**
692 * Get the height of a character cell in pixels.
693 *
694 * @return the height in pixels of a character cell
695 */
696 public int getTextHeight() {
697 int textHeight = 20;
698 for (Screen screen: screens) {
699 int newTextHeight = screen.getTextHeight();
700 if (newTextHeight < textHeight) {
701 textHeight = newTextHeight;
702 }
703 }
704 return textHeight;
705 }
706
707 /**
708 * Invert the cell color at a position, including both halves of a
709 * double-width cell.
710 *
711 * @param x column position
712 * @param y row position
713 */
714 public void invertCell(final int x, final int y) {
715 for (Screen screen: screens) {
716 screen.invertCell(x, y);
717 }
718 }
719
720 /**
721 * Invert the cell color at a position.
722 *
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
727 */
728 public void invertCell(final int x, final int y,
729 final boolean onlyThisCell) {
730
731 for (Screen screen: screens) {
732 screen.invertCell(x, y, onlyThisCell);
733 }
734 }
735
736 /**
737 * Set a selection area on the screen.
738 *
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
744 */
745 public void setSelection(final int x0, final int y0,
746 final int x1, final int y1, final boolean rectangle) {
747
748 for (Screen screen: screens) {
749 screen.setSelection(x0, y0, x1, y1, rectangle);
750 }
751 }
752
753 /**
754 * Copy the screen selection area to the clipboard.
755 *
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
762 */
763 public void copySelection(final Clipboard clipboard,
764 final int x0, final int y0, final int x1, final int y1,
765 final boolean rectangle) {
766
767 // Only copy from the first screen.
768 if (screens.size() > 0) {
769 screens.get(0).copySelection(clipboard, x0, y0, x1, y1, rectangle);
770 }
771 }
772
773 }