Merge commit '77d3a60869e7a780c6ae069e51530e1eacece5e2'
[fanfix.git] / src / jexer / backend / MultiScreen.java
index f7b61ddfc276b12befba88eca7c4aa88454c24bf..45741c05f15853eb336738267508cd1fd62e32ee 100644 (file)
@@ -3,7 +3,7 @@
  *
  * The MIT License (MIT)
  *
- * Copyright (C) 2017 Kevin Lamonte
+ * 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"),
  */
 package jexer.backend;
 
-import java.util.LinkedList;
+import java.util.ArrayList;
 import java.util.List;
 
 import jexer.bits.Cell;
 import jexer.bits.CellAttributes;
+import jexer.bits.Clipboard;
 
 /**
  * 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 LinkedList<Screen>();
+    private List<Screen> screens = new ArrayList<Screen>();
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Public constructor requires one screen.
@@ -53,25 +62,9 @@ public class MultiScreen implements Screen {
         screens.add(screen);
     }
 
-    /**
-     * 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);
-        }
-    }
+    // ------------------------------------------------------------------------
+    // Screen -----------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Set drawing offset for x.
@@ -101,7 +94,10 @@ public class MultiScreen implements Screen {
      * @return drawing boundary
      */
     public int getClipRight() {
-        return screens.get(0).getClipRight();
+        if (screens.size() > 0) {
+            return screens.get(0).getClipRight();
+        }
+        return 0;
     }
 
     /**
@@ -121,7 +117,10 @@ public class MultiScreen implements Screen {
      * @return drawing boundary
      */
     public int getClipBottom() {
-        return screens.get(0).getClipBottom();
+        if (screens.size() > 0) {
+            return screens.get(0).getClipBottom();
+        }
+        return 0;
     }
 
     /**
@@ -141,7 +140,10 @@ public class MultiScreen implements Screen {
      * @return drawing boundary
      */
     public int getClipLeft() {
-        return screens.get(0).getClipLeft();
+        if (screens.size() > 0) {
+            return screens.get(0).getClipLeft();
+        }
+        return 0;
     }
 
     /**
@@ -161,7 +163,10 @@ public class MultiScreen implements Screen {
      * @return drawing boundary
      */
     public int getClipTop() {
-        return screens.get(0).getClipTop();
+        if (screens.size() > 0) {
+            return screens.get(0).getClipTop();
+        }
+        return 0;
     }
 
     /**
@@ -182,7 +187,12 @@ public class MultiScreen implements Screen {
      * screen
      */
     public boolean isDirty() {
-        return screens.get(0).isDirty();
+        for (Screen screen: screens) {
+            if (screen.isDirty()) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -193,7 +203,24 @@ public class MultiScreen implements Screen {
      * @return attributes at (x, y)
      */
     public CellAttributes getAttrXY(final int x, final int y) {
-        return screens.get(0).getAttrXY(x, y);
+        if (screens.size() > 0) {
+            return screens.get(0).getAttrXY(x, y);
+        }
+        return new CellAttributes();
+    }
+
+    /**
+     * 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) {
+        if (screens.size() > 0) {
+            return screens.get(0).getCharXY(x, y);
+        }
+        return new Cell();
     }
 
     /**
@@ -233,7 +260,7 @@ public class MultiScreen implements Screen {
      * @param ch character to draw
      * @param attr attributes to use (bold, foreColor, backColor)
      */
-    public void putAll(final char ch, final CellAttributes attr) {
+    public void putAll(final int ch, final CellAttributes attr) {
         for (Screen screen: screens) {
             screen.putAll(ch, attr);
         }
@@ -260,7 +287,7 @@ public class MultiScreen implements Screen {
      * @param ch character to draw
      * @param attr attributes to use (bold, foreColor, backColor)
      */
-    public void putCharXY(final int x, final int y, final char ch,
+    public void putCharXY(final int x, final int y, final int ch,
         final CellAttributes attr) {
 
         for (Screen screen: screens) {
@@ -275,7 +302,7 @@ public class MultiScreen implements Screen {
      * @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 char ch) {
+    public void putCharXY(final int x, final int y, final int ch) {
         for (Screen screen: screens) {
             screen.putCharXY(x, y, ch);
         }
@@ -321,7 +348,7 @@ public class MultiScreen implements Screen {
      * @param attr attributes to use (bold, foreColor, backColor)
      */
     public void vLineXY(final int x, final int y, final int n,
-        final char ch, final CellAttributes attr) {
+        final int ch, final CellAttributes attr) {
 
         for (Screen screen: screens) {
             screen.vLineXY(x, y, n, ch, attr);
@@ -338,7 +365,7 @@ public class MultiScreen implements Screen {
      * @param attr attributes to use (bold, foreColor, backColor)
      */
     public void hLineXY(final int x, final int y, final int n,
-        final char ch, final CellAttributes attr) {
+        final int ch, final CellAttributes attr) {
 
         for (Screen screen: screens) {
             screen.hLineXY(x, y, n, ch, attr);
@@ -378,7 +405,20 @@ public class MultiScreen implements Screen {
      */
     public void setDimensions(final int width, final int height) {
         for (Screen screen: screens) {
-            screen.setDimensions(width, height);
+            // 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();
+            }
         }
     }
 
@@ -388,7 +428,17 @@ public class MultiScreen implements Screen {
      * @return current screen height
      */
     public int getHeight() {
-        return screens.get(0).getHeight();
+        // Return the smallest height of the screens.
+        int height = 25;
+        if (screens.size() > 0) {
+            height = screens.get(0).getHeight();
+        }
+        for (Screen screen: screens) {
+            if (screen.getHeight() < height) {
+                height = screen.getHeight();
+            }
+        }
+        return height;
     }
 
     /**
@@ -397,7 +447,17 @@ public class MultiScreen implements Screen {
      * @return current screen width
      */
     public int getWidth() {
-        return screens.get(0).getWidth();
+        // Return the smallest width of the screens.
+        int width = 80;
+        if (screens.size() > 0) {
+            width = screens.get(0).getWidth();
+        }
+        for (Screen screen: screens) {
+            if (screen.getWidth() < width) {
+                width = screen.getWidth();
+            }
+        }
+        return width;
     }
 
     /**
@@ -488,6 +548,27 @@ public class MultiScreen implements Screen {
         }
     }
 
+    /**
+     * 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.
@@ -520,6 +601,42 @@ public class MultiScreen implements Screen {
         }
     }
 
+    /**
+     * Get the cursor visibility.
+     *
+     * @return true if the cursor is visible
+     */
+    public boolean isCursorVisible() {
+        if (screens.size() > 0) {
+            return screens.get(0).isCursorVisible();
+        }
+        return true;
+    }
+
+    /**
+     * Get the cursor X position.
+     *
+     * @return the cursor x column position
+     */
+    public int getCursorX() {
+        if (screens.size() > 0) {
+            return screens.get(0).getCursorX();
+        }
+        return 0;
+    }
+
+    /**
+     * Get the cursor Y position.
+     *
+     * @return the cursor y row position
+     */
+    public int getCursorY() {
+        if (screens.size() > 0) {
+            return screens.get(0).getCursorY();
+        }
+        return 0;
+    }
+
     /**
      * Set the window title.
      *
@@ -531,4 +648,126 @@ public class MultiScreen implements Screen {
         }
     }
 
+    // ------------------------------------------------------------------------
+    // 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;
+    }
+
+    /**
+     * Invert the cell color at a position, including both halves of a
+     * double-width cell.
+     *
+     * @param x column position
+     * @param y row position
+     */
+    public void invertCell(final int x, final int y) {
+        for (Screen screen: screens) {
+            screen.invertCell(x, y);
+        }
+    }
+
+    /**
+     * Invert the cell color at a position.
+     *
+     * @param x column position
+     * @param y row position
+     * @param onlyThisCell if true, only invert this cell, otherwise invert
+     * both halves of a double-width cell if necessary
+     */
+    public void invertCell(final int x, final int y,
+        final boolean onlyThisCell) {
+
+        for (Screen screen: screens) {
+            screen.invertCell(x, y, onlyThisCell);
+        }
+    }
+
+    /**
+     * Set a selection area on the screen.
+     *
+     * @param x0 the starting X position of the selection
+     * @param y0 the starting Y position of the selection
+     * @param x1 the ending X position of the selection
+     * @param y1 the ending Y position of the selection
+     * @param rectangle if true, this is a rectangle select
+     */
+    public void setSelection(final int x0, final int y0,
+        final int x1, final int y1, final boolean rectangle) {
+
+        for (Screen screen: screens) {
+            screen.setSelection(x0, y0, x1, y1, rectangle);
+        }
+    }
+
+    /**
+     * Copy the screen selection area to the clipboard.
+     *
+     * @param clipboard the clipboard to use
+     * @param x0 the starting X position of the selection
+     * @param y0 the starting Y position of the selection
+     * @param x1 the ending X position of the selection
+     * @param y1 the ending Y position of the selection
+     * @param rectangle if true, this is a rectangle select
+     */
+    public void copySelection(final Clipboard clipboard,
+        final int x0, final int y0, final int x1, final int y1,
+        final boolean rectangle) {
+
+        // Only copy from the first screen.
+        if (screens.size() > 0) {
+            screens.get(0).copySelection(clipboard, x0, y0, x1, y1, rectangle);
+        }
+    }
+
 }