#38 notify backend of lost connection, version bump to 0.3.1
[fanfix.git] / src / jexer / 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.LinkedList;
32 import java.util.List;
33
34 import jexer.bits.Cell;
35 import jexer.bits.CellAttributes;
36
37 /**
38 * MultiScreen mirrors its I/O to several screens.
39 */
40 public class MultiScreen implements Screen {
41
42 // ------------------------------------------------------------------------
43 // Variables --------------------------------------------------------------
44 // ------------------------------------------------------------------------
45
46 /**
47 * The list of screens to use.
48 */
49 private List<Screen> screens = new LinkedList<Screen>();
50
51 // ------------------------------------------------------------------------
52 // Constructors -----------------------------------------------------------
53 // ------------------------------------------------------------------------
54
55 /**
56 * Public constructor requires one screen.
57 *
58 * @param screen the screen to add
59 */
60 public MultiScreen(final Screen screen) {
61 screens.add(screen);
62 }
63
64 // ------------------------------------------------------------------------
65 // Screen -----------------------------------------------------------------
66 // ------------------------------------------------------------------------
67
68 /**
69 * Set drawing offset for x.
70 *
71 * @param offsetX new drawing offset
72 */
73 public void setOffsetX(final int offsetX) {
74 for (Screen screen: screens) {
75 screen.setOffsetX(offsetX);
76 }
77 }
78
79 /**
80 * Set drawing offset for y.
81 *
82 * @param offsetY new drawing offset
83 */
84 public void setOffsetY(final int offsetY) {
85 for (Screen screen: screens) {
86 screen.setOffsetY(offsetY);
87 }
88 }
89
90 /**
91 * Get right drawing clipping boundary.
92 *
93 * @return drawing boundary
94 */
95 public int getClipRight() {
96 return screens.get(0).getClipRight();
97 }
98
99 /**
100 * Set right drawing clipping boundary.
101 *
102 * @param clipRight new boundary
103 */
104 public void setClipRight(final int clipRight) {
105 for (Screen screen: screens) {
106 screen.setClipRight(clipRight);
107 }
108 }
109
110 /**
111 * Get bottom drawing clipping boundary.
112 *
113 * @return drawing boundary
114 */
115 public int getClipBottom() {
116 return screens.get(0).getClipBottom();
117 }
118
119 /**
120 * Set bottom drawing clipping boundary.
121 *
122 * @param clipBottom new boundary
123 */
124 public void setClipBottom(final int clipBottom) {
125 for (Screen screen: screens) {
126 screen.setClipBottom(clipBottom);
127 }
128 }
129
130 /**
131 * Get left drawing clipping boundary.
132 *
133 * @return drawing boundary
134 */
135 public int getClipLeft() {
136 return screens.get(0).getClipLeft();
137 }
138
139 /**
140 * Set left drawing clipping boundary.
141 *
142 * @param clipLeft new boundary
143 */
144 public void setClipLeft(final int clipLeft) {
145 for (Screen screen: screens) {
146 screen.setClipLeft(clipLeft);
147 }
148 }
149
150 /**
151 * Get top drawing clipping boundary.
152 *
153 * @return drawing boundary
154 */
155 public int getClipTop() {
156 return screens.get(0).getClipTop();
157 }
158
159 /**
160 * Set top drawing clipping boundary.
161 *
162 * @param clipTop new boundary
163 */
164 public void setClipTop(final int clipTop) {
165 for (Screen screen: screens) {
166 screen.setClipTop(clipTop);
167 }
168 }
169
170 /**
171 * Get dirty flag.
172 *
173 * @return if true, the logical screen is not in sync with the physical
174 * screen
175 */
176 public boolean isDirty() {
177 for (Screen screen: screens) {
178 if (screen.isDirty()) {
179 return true;
180 }
181 }
182 return false;
183 }
184
185 /**
186 * Get the attributes at one location.
187 *
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)
191 */
192 public CellAttributes getAttrXY(final int x, final int y) {
193 return screens.get(0).getAttrXY(x, y);
194 }
195
196 /**
197 * Get the cell at one location.
198 *
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
202 */
203 public Cell getCharXY(final int x, final int y) {
204 return screens.get(0).getCharXY(x, y);
205 }
206
207 /**
208 * Set the attributes at one location.
209 *
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)
213 */
214 public void putAttrXY(final int x, final int y,
215 final CellAttributes attr) {
216
217 for (Screen screen: screens) {
218 screen.putAttrXY(x, y, attr);
219 }
220 }
221
222 /**
223 * Set the attributes at one location.
224 *
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
229 */
230 public void putAttrXY(final int x, final int y,
231 final CellAttributes attr, final boolean clip) {
232
233 for (Screen screen: screens) {
234 screen.putAttrXY(x, y, attr, clip);
235 }
236 }
237
238 /**
239 * Fill the entire screen with one character with attributes.
240 *
241 * @param ch character to draw
242 * @param attr attributes to use (bold, foreColor, backColor)
243 */
244 public void putAll(final char ch, final CellAttributes attr) {
245 for (Screen screen: screens) {
246 screen.putAll(ch, attr);
247 }
248 }
249
250 /**
251 * Render one character with attributes.
252 *
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
256 */
257 public void putCharXY(final int x, final int y, final Cell ch) {
258 for (Screen screen: screens) {
259 screen.putCharXY(x, y, ch);
260 }
261 }
262
263 /**
264 * Render one character with attributes.
265 *
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)
270 */
271 public void putCharXY(final int x, final int y, final char ch,
272 final CellAttributes attr) {
273
274 for (Screen screen: screens) {
275 screen.putCharXY(x, y, ch, attr);
276 }
277 }
278
279 /**
280 * Render one character without changing the underlying attributes.
281 *
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
285 */
286 public void putCharXY(final int x, final int y, final char ch) {
287 for (Screen screen: screens) {
288 screen.putCharXY(x, y, ch);
289 }
290 }
291
292 /**
293 * Render a string. Does not wrap if the string exceeds the line.
294 *
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)
299 */
300 public void putStringXY(final int x, final int y, final String str,
301 final CellAttributes attr) {
302
303 for (Screen screen: screens) {
304 screen.putStringXY(x, y, str, attr);
305 }
306 }
307
308 /**
309 * Render a string without changing the underlying attribute. Does not
310 * wrap if the string exceeds the line.
311 *
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
315 */
316 public void putStringXY(final int x, final int y, final String str) {
317 for (Screen screen: screens) {
318 screen.putStringXY(x, y, str);
319 }
320 }
321
322 /**
323 * Draw a vertical line from (x, y) to (x, y + n).
324 *
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)
330 */
331 public void vLineXY(final int x, final int y, final int n,
332 final char ch, final CellAttributes attr) {
333
334 for (Screen screen: screens) {
335 screen.vLineXY(x, y, n, ch, attr);
336 }
337 }
338
339 /**
340 * Draw a horizontal line from (x, y) to (x + n, y).
341 *
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)
347 */
348 public void hLineXY(final int x, final int y, final int n,
349 final char ch, final CellAttributes attr) {
350
351 for (Screen screen: screens) {
352 screen.hLineXY(x, y, n, ch, attr);
353 }
354 }
355
356 /**
357 * Change the width. Everything on-screen will be destroyed and must be
358 * redrawn.
359 *
360 * @param width new screen width
361 */
362 public void setWidth(final int width) {
363 for (Screen screen: screens) {
364 screen.setWidth(width);
365 }
366 }
367
368 /**
369 * Change the height. Everything on-screen will be destroyed and must be
370 * redrawn.
371 *
372 * @param height new screen height
373 */
374 public void setHeight(final int height) {
375 for (Screen screen: screens) {
376 screen.setHeight(height);
377 }
378 }
379
380 /**
381 * Change the width and height. Everything on-screen will be destroyed
382 * and must be redrawn.
383 *
384 * @param width new screen width
385 * @param height new screen height
386 */
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
393 // the screens.
394 if ((screen.getWidth() != width)
395 || (screen.getHeight() != height)
396 ) {
397 screen.setDimensions(width, height);
398 } else {
399 // The screen that didn't change is probably the one that
400 // prompted the resize. Force it to repaint.
401 screen.clearPhysical();
402 }
403 }
404 }
405
406 /**
407 * Get the height.
408 *
409 * @return current screen height
410 */
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();
417 }
418 }
419 return height;
420 }
421
422 /**
423 * Get the width.
424 *
425 * @return current screen width
426 */
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();
433 }
434 }
435 return width;
436 }
437
438 /**
439 * Reset screen to not-bold, white-on-black. Also flushes the offset and
440 * clip variables.
441 */
442 public void reset() {
443 for (Screen screen: screens) {
444 screen.reset();
445 }
446 }
447
448 /**
449 * Flush the offset and clip variables.
450 */
451 public void resetClipping() {
452 for (Screen screen: screens) {
453 screen.resetClipping();
454 }
455 }
456
457 /**
458 * Clear the logical screen.
459 */
460 public void clear() {
461 for (Screen screen: screens) {
462 screen.clear();
463 }
464 }
465
466 /**
467 * Draw a box with a border and empty background.
468 *
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
475 */
476 public void drawBox(final int left, final int top,
477 final int right, final int bottom,
478 final CellAttributes border, final CellAttributes background) {
479
480 for (Screen screen: screens) {
481 screen.drawBox(left, top, right, bottom, border, background);
482 }
483 }
484
485 /**
486 * Draw a box with a border and empty background.
487 *
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
498 */
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) {
503
504 for (Screen screen: screens) {
505 screen.drawBox(left, top, right, bottom, border, background,
506 borderType, shadow);
507 }
508 }
509
510 /**
511 * Draw a box shadow.
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 */
518 public void drawBoxShadow(final int left, final int top,
519 final int right, final int bottom) {
520
521 for (Screen screen: screens) {
522 screen.drawBoxShadow(left, top, right, bottom);
523 }
524 }
525
526 /**
527 * Clear the physical screen.
528 */
529 public void clearPhysical() {
530 for (Screen screen: screens) {
531 screen.clearPhysical();
532 }
533 }
534
535 /**
536 * Unset every image cell on one row of the physical screen, forcing
537 * images on that row to be redrawn.
538 *
539 * @param y row coordinate. 0 is the top-most row.
540 */
541 public final void unsetImageRow(final int y) {
542 for (Screen screen: screens) {
543 screen.unsetImageRow(y);
544 }
545 }
546
547 /**
548 * Classes must provide an implementation to push the logical screen to
549 * the physical device.
550 */
551 public void flushPhysical() {
552 for (Screen screen: screens) {
553 screen.flushPhysical();
554 }
555 }
556
557 /**
558 * Put the cursor at (x,y).
559 *
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
563 */
564 public void putCursor(final boolean visible, final int x, final int y) {
565 for (Screen screen: screens) {
566 screen.putCursor(visible, x, y);
567 }
568 }
569
570 /**
571 * Hide the cursor.
572 */
573 public void hideCursor() {
574 for (Screen screen: screens) {
575 screen.hideCursor();
576 }
577 }
578
579 /**
580 * Get the cursor visibility.
581 *
582 * @return true if the cursor is visible
583 */
584 public boolean isCursorVisible() {
585 return screens.get(0).isCursorVisible();
586 }
587
588 /**
589 * Get the cursor X position.
590 *
591 * @return the cursor x column position
592 */
593 public int getCursorX() {
594 return screens.get(0).getCursorX();
595 }
596
597 /**
598 * Get the cursor Y position.
599 *
600 * @return the cursor y row position
601 */
602 public int getCursorY() {
603 return screens.get(0).getCursorY();
604 }
605
606 /**
607 * Set the window title.
608 *
609 * @param title the new title
610 */
611 public void setTitle(final String title) {
612 for (Screen screen: screens) {
613 screen.setTitle(title);
614 }
615 }
616
617 // ------------------------------------------------------------------------
618 // MultiScreen ------------------------------------------------------------
619 // ------------------------------------------------------------------------
620
621 /**
622 * Add a screen to the list.
623 *
624 * @param screen the screen to add
625 */
626 public void addScreen(final Screen screen) {
627 screens.add(screen);
628 }
629
630 /**
631 * Remove a screen from the list.
632 *
633 * @param screen the screen to remove
634 */
635 public void removeScreen(final Screen screen) {
636 if (screens.size() > 1) {
637 screens.remove(screen);
638 }
639 }
640
641 /**
642 * Get the width of a character cell in pixels.
643 *
644 * @return the width in pixels of a character cell
645 */
646 public int getTextWidth() {
647 int textWidth = 16;
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();
656 }
657 if (newTextWidth < textWidth) {
658 textWidth = newTextWidth;
659 }
660 }
661 return textWidth;
662 }
663
664 /**
665 * Get the height of a character cell in pixels.
666 *
667 * @return the height in pixels of a character cell
668 */
669 public int getTextHeight() {
670 int textHeight = 20;
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();
679 }
680 if (newTextHeight < textHeight) {
681 textHeight = newTextHeight;
682 }
683 }
684 return textHeight;
685 }
686
687 }