Add 'src/jexer/' from commit 'cf01c92f5809a0732409e280fb0f32f27393618d'
[fanfix.git] / src / jexer / backend / MultiScreen.java
diff --git a/src/jexer/backend/MultiScreen.java b/src/jexer/backend/MultiScreen.java
new file mode 100644 (file)
index 0000000..9d66b69
--- /dev/null
@@ -0,0 +1,673 @@
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2019 Kevin Lamonte
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * @author Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+package jexer.backend;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import jexer.bits.Cell;
+import jexer.bits.CellAttributes;
+
+/**
+ * MultiScreen mirrors its I/O to several screens.
+ */
+public class MultiScreen implements Screen {
+
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * The list of screens to use.
+     */
+    private List<Screen> screens = new ArrayList<Screen>();
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor requires one screen.
+     *
+     * @param screen the screen to add
+     */
+    public MultiScreen(final Screen screen) {
+        screens.add(screen);
+    }
+
+    // ------------------------------------------------------------------------
+    // Screen -----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Set drawing offset for x.
+     *
+     * @param offsetX new drawing offset
+     */
+    public void setOffsetX(final int offsetX) {
+        for (Screen screen: screens) {
+            screen.setOffsetX(offsetX);
+        }
+    }
+
+    /**
+     * Set drawing offset for y.
+     *
+     * @param offsetY new drawing offset
+     */
+    public void setOffsetY(final int offsetY) {
+        for (Screen screen: screens) {
+            screen.setOffsetY(offsetY);
+        }
+    }
+
+    /**
+     * Get right drawing clipping boundary.
+     *
+     * @return drawing boundary
+     */
+    public int getClipRight() {
+        return screens.get(0).getClipRight();
+    }
+
+    /**
+     * Set right drawing clipping boundary.
+     *
+     * @param clipRight new boundary
+     */
+    public void setClipRight(final int clipRight) {
+        for (Screen screen: screens) {
+            screen.setClipRight(clipRight);
+        }
+    }
+
+    /**
+     * Get bottom drawing clipping boundary.
+     *
+     * @return drawing boundary
+     */
+    public int getClipBottom() {
+        return screens.get(0).getClipBottom();
+    }
+
+    /**
+     * Set bottom drawing clipping boundary.
+     *
+     * @param clipBottom new boundary
+     */
+    public void setClipBottom(final int clipBottom) {
+        for (Screen screen: screens) {
+            screen.setClipBottom(clipBottom);
+        }
+    }
+
+    /**
+     * Get left drawing clipping boundary.
+     *
+     * @return drawing boundary
+     */
+    public int getClipLeft() {
+        return screens.get(0).getClipLeft();
+    }
+
+    /**
+     * Set left drawing clipping boundary.
+     *
+     * @param clipLeft new boundary
+     */
+    public void setClipLeft(final int clipLeft) {
+        for (Screen screen: screens) {
+            screen.setClipLeft(clipLeft);
+        }
+    }
+
+    /**
+     * Get top drawing clipping boundary.
+     *
+     * @return drawing boundary
+     */
+    public int getClipTop() {
+        return screens.get(0).getClipTop();
+    }
+
+    /**
+     * Set top drawing clipping boundary.
+     *
+     * @param clipTop new boundary
+     */
+    public void setClipTop(final int clipTop) {
+        for (Screen screen: screens) {
+            screen.setClipTop(clipTop);
+        }
+    }
+
+    /**
+     * Get dirty flag.
+     *
+     * @return if true, the logical screen is not in sync with the physical
+     * screen
+     */
+    public boolean isDirty() {
+        for (Screen screen: screens) {
+            if (screen.isDirty()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get the attributes at one location.
+     *
+     * @param x column coordinate.  0 is the left-most column.
+     * @param y row coordinate.  0 is the top-most row.
+     * @return attributes at (x, y)
+     */
+    public CellAttributes getAttrXY(final int x, final int y) {
+        return screens.get(0).getAttrXY(x, y);
+    }
+
+    /**
+     * Get the cell at one location.
+     *
+     * @param x column coordinate.  0 is the left-most column.
+     * @param y row coordinate.  0 is the top-most row.
+     * @return the character + attributes
+     */
+    public Cell getCharXY(final int x, final int y) {
+        return screens.get(0).getCharXY(x, y);
+    }
+
+    /**
+     * Set the attributes at one location.
+     *
+     * @param x column coordinate.  0 is the left-most column.
+     * @param y row coordinate.  0 is the top-most row.
+     * @param attr attributes to use (bold, foreColor, backColor)
+     */
+    public void putAttrXY(final int x, final int y,
+        final CellAttributes attr) {
+
+        for (Screen screen: screens) {
+            screen.putAttrXY(x, y, attr);
+        }
+    }
+
+    /**
+     * Set the attributes at one location.
+     *
+     * @param x column coordinate.  0 is the left-most column.
+     * @param y row coordinate.  0 is the top-most row.
+     * @param attr attributes to use (bold, foreColor, backColor)
+     * @param clip if true, honor clipping/offset
+     */
+    public void putAttrXY(final int x, final int y,
+        final CellAttributes attr, final boolean clip) {
+
+        for (Screen screen: screens) {
+            screen.putAttrXY(x, y, attr, clip);
+        }
+    }
+
+    /**
+     * Fill the entire screen with one character with attributes.
+     *
+     * @param ch character to draw
+     * @param attr attributes to use (bold, foreColor, backColor)
+     */
+    public void putAll(final int ch, final CellAttributes attr) {
+        for (Screen screen: screens) {
+            screen.putAll(ch, attr);
+        }
+    }
+
+    /**
+     * Render one character with attributes.
+     *
+     * @param x column coordinate.  0 is the left-most column.
+     * @param y row coordinate.  0 is the top-most row.
+     * @param ch character + attributes to draw
+     */
+    public void putCharXY(final int x, final int y, final Cell ch) {
+        for (Screen screen: screens) {
+            screen.putCharXY(x, y, ch);
+        }
+    }
+
+    /**
+     * Render one character with attributes.
+     *
+     * @param x column coordinate.  0 is the left-most column.
+     * @param y row coordinate.  0 is the top-most row.
+     * @param ch character to draw
+     * @param attr attributes to use (bold, foreColor, backColor)
+     */
+    public void putCharXY(final int x, final int y, final int ch,
+        final CellAttributes attr) {
+
+        for (Screen screen: screens) {
+            screen.putCharXY(x, y, ch, attr);
+        }
+    }
+
+    /**
+     * Render one character without changing the underlying attributes.
+     *
+     * @param x column coordinate.  0 is the left-most column.
+     * @param y row coordinate.  0 is the top-most row.
+     * @param ch character to draw
+     */
+    public void putCharXY(final int x, final int y, final int ch) {
+        for (Screen screen: screens) {
+            screen.putCharXY(x, y, ch);
+        }
+    }
+
+    /**
+     * Render a string.  Does not wrap if the string exceeds the line.
+     *
+     * @param x column coordinate.  0 is the left-most column.
+     * @param y row coordinate.  0 is the top-most row.
+     * @param str string to draw
+     * @param attr attributes to use (bold, foreColor, backColor)
+     */
+    public void putStringXY(final int x, final int y, final String str,
+        final CellAttributes attr) {
+
+        for (Screen screen: screens) {
+            screen.putStringXY(x, y, str, attr);
+        }
+    }
+
+    /**
+     * Render a string without changing the underlying attribute.  Does not
+     * wrap if the string exceeds the line.
+     *
+     * @param x column coordinate.  0 is the left-most column.
+     * @param y row coordinate.  0 is the top-most row.
+     * @param str string to draw
+     */
+    public void putStringXY(final int x, final int y, final String str) {
+        for (Screen screen: screens) {
+            screen.putStringXY(x, y, str);
+        }
+    }
+
+    /**
+     * Draw a vertical line from (x, y) to (x, y + n).
+     *
+     * @param x column coordinate.  0 is the left-most column.
+     * @param y row coordinate.  0 is the top-most row.
+     * @param n number of characters to draw
+     * @param ch character to draw
+     * @param attr attributes to use (bold, foreColor, backColor)
+     */
+    public void vLineXY(final int x, final int y, final int n,
+        final int ch, final CellAttributes attr) {
+
+        for (Screen screen: screens) {
+            screen.vLineXY(x, y, n, ch, attr);
+        }
+    }
+
+    /**
+     * Draw a horizontal line from (x, y) to (x + n, y).
+     *
+     * @param x column coordinate.  0 is the left-most column.
+     * @param y row coordinate.  0 is the top-most row.
+     * @param n number of characters to draw
+     * @param ch character to draw
+     * @param attr attributes to use (bold, foreColor, backColor)
+     */
+    public void hLineXY(final int x, final int y, final int n,
+        final int ch, final CellAttributes attr) {
+
+        for (Screen screen: screens) {
+            screen.hLineXY(x, y, n, ch, attr);
+        }
+    }
+
+    /**
+     * Change the width.  Everything on-screen will be destroyed and must be
+     * redrawn.
+     *
+     * @param width new screen width
+     */
+    public void setWidth(final int width) {
+        for (Screen screen: screens) {
+            screen.setWidth(width);
+        }
+    }
+
+    /**
+     * Change the height.  Everything on-screen will be destroyed and must be
+     * redrawn.
+     *
+     * @param height new screen height
+     */
+    public void setHeight(final int height) {
+        for (Screen screen: screens) {
+            screen.setHeight(height);
+        }
+    }
+
+    /**
+     * Change the width and height.  Everything on-screen will be destroyed
+     * and must be redrawn.
+     *
+     * @param width new screen width
+     * @param height new screen height
+     */
+    public void setDimensions(final int width, final int height) {
+        for (Screen screen: screens) {
+            // Do not blindly call setDimension() on every screen.  Instead
+            // call it only on those screens that do not already have the
+            // requested dimension.  With this very small check, we have the
+            // ability for ANY screen in the MultiBackend to resize ALL of
+            // the screens.
+            if ((screen.getWidth() != width)
+                || (screen.getHeight() != height)
+            ) {
+                screen.setDimensions(width, height);
+            } else {
+                // The screen that didn't change is probably the one that
+                // prompted the resize.  Force it to repaint.
+                screen.clearPhysical();
+            }
+        }
+    }
+
+    /**
+     * Get the height.
+     *
+     * @return current screen height
+     */
+    public int getHeight() {
+        // Return the smallest height of the screens.
+        int height = screens.get(0).getHeight();
+        for (Screen screen: screens) {
+            if (screen.getHeight() < height) {
+                height = screen.getHeight();
+            }
+        }
+        return height;
+    }
+
+    /**
+     * Get the width.
+     *
+     * @return current screen width
+     */
+    public int getWidth() {
+        // Return the smallest width of the screens.
+        int width = screens.get(0).getWidth();
+        for (Screen screen: screens) {
+            if (screen.getWidth() < width) {
+                width = screen.getWidth();
+            }
+        }
+        return width;
+    }
+
+    /**
+     * Reset screen to not-bold, white-on-black.  Also flushes the offset and
+     * clip variables.
+     */
+    public void reset() {
+        for (Screen screen: screens) {
+            screen.reset();
+        }
+    }
+
+    /**
+     * Flush the offset and clip variables.
+     */
+    public void resetClipping() {
+        for (Screen screen: screens) {
+            screen.resetClipping();
+        }
+    }
+
+    /**
+     * Clear the logical screen.
+     */
+    public void clear() {
+        for (Screen screen: screens) {
+            screen.clear();
+        }
+    }
+
+    /**
+     * Draw a box with a border and empty background.
+     *
+     * @param left left column of box.  0 is the left-most row.
+     * @param top top row of the box.  0 is the top-most row.
+     * @param right right column of box
+     * @param bottom bottom row of the box
+     * @param border attributes to use for the border
+     * @param background attributes to use for the background
+     */
+    public void drawBox(final int left, final int top,
+        final int right, final int bottom,
+        final CellAttributes border, final CellAttributes background) {
+
+        for (Screen screen: screens) {
+            screen.drawBox(left, top, right, bottom, border, background);
+        }
+    }
+
+    /**
+     * Draw a box with a border and empty background.
+     *
+     * @param left left column of box.  0 is the left-most row.
+     * @param top top row of the box.  0 is the top-most row.
+     * @param right right column of box
+     * @param bottom bottom row of the box
+     * @param border attributes to use for the border
+     * @param background attributes to use for the background
+     * @param borderType if 1, draw a single-line border; if 2, draw a
+     * double-line border; if 3, draw double-line top/bottom edges and
+     * single-line left/right edges (like Qmodem)
+     * @param shadow if true, draw a "shadow" on the box
+     */
+    public void drawBox(final int left, final int top,
+        final int right, final int bottom,
+        final CellAttributes border, final CellAttributes background,
+        final int borderType, final boolean shadow) {
+
+        for (Screen screen: screens) {
+            screen.drawBox(left, top, right, bottom, border, background,
+                borderType, shadow);
+        }
+    }
+
+    /**
+     * Draw a box shadow.
+     *
+     * @param left left column of box.  0 is the left-most row.
+     * @param top top row of the box.  0 is the top-most row.
+     * @param right right column of box
+     * @param bottom bottom row of the box
+     */
+    public void drawBoxShadow(final int left, final int top,
+        final int right, final int bottom) {
+
+        for (Screen screen: screens) {
+            screen.drawBoxShadow(left, top, right, bottom);
+        }
+    }
+
+    /**
+     * Clear the physical screen.
+     */
+    public void clearPhysical() {
+        for (Screen screen: screens) {
+            screen.clearPhysical();
+        }
+    }
+
+    /**
+     * Unset every image cell on one row of the physical screen, forcing
+     * images on that row to be redrawn.
+     *
+     * @param y row coordinate.  0 is the top-most row.
+     */
+    public final void unsetImageRow(final int y) {
+        for (Screen screen: screens) {
+            screen.unsetImageRow(y);
+        }
+    }
+
+    /**
+     * Classes must provide an implementation to push the logical screen to
+     * the physical device.
+     */
+    public void flushPhysical() {
+        for (Screen screen: screens) {
+            screen.flushPhysical();
+        }
+    }
+
+    /**
+     * Put the cursor at (x,y).
+     *
+     * @param visible if true, the cursor should be visible
+     * @param x column coordinate to put the cursor on
+     * @param y row coordinate to put the cursor on
+     */
+    public void putCursor(final boolean visible, final int x, final int y) {
+        for (Screen screen: screens) {
+            screen.putCursor(visible, x, y);
+        }
+    }
+
+    /**
+     * Hide the cursor.
+     */
+    public void hideCursor() {
+        for (Screen screen: screens) {
+            screen.hideCursor();
+        }
+    }
+
+    /**
+     * Get the cursor visibility.
+     *
+     * @return true if the cursor is visible
+     */
+    public boolean isCursorVisible() {
+        return screens.get(0).isCursorVisible();
+    }
+
+    /**
+     * Get the cursor X position.
+     *
+     * @return the cursor x column position
+     */
+    public int getCursorX() {
+        return screens.get(0).getCursorX();
+    }
+
+    /**
+     * Get the cursor Y position.
+     *
+     * @return the cursor y row position
+     */
+    public int getCursorY() {
+        return screens.get(0).getCursorY();
+    }
+
+    /**
+     * Set the window title.
+     *
+     * @param title the new title
+     */
+    public void setTitle(final String title) {
+        for (Screen screen: screens) {
+            screen.setTitle(title);
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // MultiScreen ------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Add a screen to the list.
+     *
+     * @param screen the screen to add
+     */
+    public void addScreen(final Screen screen) {
+        screens.add(screen);
+    }
+
+    /**
+     * Remove a screen from the list.
+     *
+     * @param screen the screen to remove
+     */
+    public void removeScreen(final Screen screen) {
+        if (screens.size() > 1) {
+            screens.remove(screen);
+        }
+    }
+
+    /**
+     * Get the width of a character cell in pixels.
+     *
+     * @return the width in pixels of a character cell
+     */
+    public int getTextWidth() {
+        int textWidth = 16;
+        for (Screen screen: screens) {
+            int newTextWidth = screen.getTextWidth();
+            if (newTextWidth < textWidth) {
+                textWidth = newTextWidth;
+            }
+        }
+        return textWidth;
+    }
+
+    /**
+     * Get the height of a character cell in pixels.
+     *
+     * @return the height in pixels of a character cell
+     */
+    public int getTextHeight() {
+        int textHeight = 20;
+        for (Screen screen: screens) {
+            int newTextHeight = screen.getTextHeight();
+            if (newTextHeight < textHeight) {
+                textHeight = newTextHeight;
+            }
+        }
+        return textHeight;
+    }
+
+}