Merge branch 'subtree'
[fanfix.git] / src / jexer / TSplitPane.java
index 0a3443b85eeda57511fd486db0539dc663e08f6a..b308e9b79162a57aea99d55bad2a77f22e951eb3 100644 (file)
@@ -30,10 +30,8 @@ package jexer;
 
 import jexer.bits.CellAttributes;
 import jexer.bits.GraphicsChars;
-import jexer.event.TMenuEvent;
 import jexer.event.TMouseEvent;
 import jexer.event.TResizeEvent;
-import jexer.menu.TMenu;
 
 /**
  * TSplitPane contains two widgets with a draggable horizontal or vertical
@@ -81,6 +79,11 @@ public class TSplitPane extends TWidget {
      */
     private boolean inSplitMove = false;
 
+    /**
+     * The last seen mouse position.
+     */
+    private TMouseEvent mouse;
+
     // ------------------------------------------------------------------------
     // Constructors -----------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -136,14 +139,15 @@ public class TSplitPane extends TWidget {
      */
     @Override
     public void onMouseDown(final TMouseEvent mouse) {
+        this.mouse = mouse;
 
         inSplitMove = false;
 
         if (mouse.isMouse1()) {
             if (vertical) {
-                inSplitMove = (mouse.getX() == split);
+                inSplitMove = (mouse.getAbsoluteX() - getAbsoluteX() == split);
             } else {
-                inSplitMove = (mouse.getY() == split);
+                inSplitMove = (mouse.getAbsoluteY() - getAbsoluteY() == split);
             }
             if (inSplitMove) {
                 return;
@@ -161,6 +165,7 @@ public class TSplitPane extends TWidget {
      */
     @Override
     public void onMouseUp(final TMouseEvent mouse) {
+        this.mouse = mouse;
 
         if (inSplitMove && mouse.isMouse1()) {
             // Stop moving split
@@ -179,13 +184,23 @@ public class TSplitPane extends TWidget {
      */
     @Override
     public void onMouseMotion(final TMouseEvent mouse) {
+        this.mouse = mouse;
+
+        if ((mouse.getAbsoluteX() - getAbsoluteX() < 0)
+            || (mouse.getAbsoluteX() - getAbsoluteX() >= getWidth())
+            || (mouse.getAbsoluteY() - getAbsoluteY() < 0)
+            || (mouse.getAbsoluteY() - getAbsoluteY() >= getHeight())
+        ) {
+            // Mouse has travelled out of my window.
+            inSplitMove = false;
+        }
 
         if (inSplitMove) {
             if (vertical) {
-                split = mouse.getX();
+                split = mouse.getAbsoluteX() - getAbsoluteX();
                 split = Math.min(Math.max(1, split), getWidth() - 2);
             } else {
-                split = mouse.getY();
+                split = mouse.getAbsoluteY() - getAbsoluteY();
                 split = Math.min(Math.max(1, split), getHeight() - 2);
             }
             layoutChildren();
@@ -208,11 +223,95 @@ public class TSplitPane extends TWidget {
         CellAttributes attr = getTheme().getColor("tsplitpane");
         if (vertical) {
             vLineXY(split, 0, getHeight(), GraphicsChars.WINDOW_SIDE, attr);
-            // TODO: draw intersections of children
+
+            // Draw intersections of children
+            if ((left instanceof TSplitPane)
+                && (((TSplitPane) left).vertical == false)
+                && (right instanceof TSplitPane)
+                && (((TSplitPane) right).vertical == false)
+                && (((TSplitPane) left).split == ((TSplitPane) right).split)
+            ) {
+                putCharXY(split, ((TSplitPane) left).split, '\u253C', attr);
+            } else {
+                if ((left instanceof TSplitPane)
+                    && (((TSplitPane) left).vertical == false)
+                ) {
+                    putCharXY(split, ((TSplitPane) left).split, '\u2524', attr);
+                }
+                if ((right instanceof TSplitPane)
+                    && (((TSplitPane) right).vertical == false)
+                ) {
+                    putCharXY(split, ((TSplitPane) right).split, '\u251C',
+                        attr);
+                }
+            }
+
+            if ((mouse != null)
+                && (mouse.getAbsoluteX() == getAbsoluteX() + split)
+                && (mouse.getAbsoluteY() >= getAbsoluteY()) &&
+                (mouse.getAbsoluteY() < getAbsoluteY() + getHeight())
+            ) {
+                putCharXY(split, mouse.getAbsoluteY() - getAbsoluteY(),
+                    '\u2194', attr);
+            }
         } else {
             hLineXY(0, split, getWidth(), GraphicsChars.SINGLE_BAR, attr);
-            // TODO: draw intersections of children
+
+            // Draw intersections of children
+            if ((top instanceof TSplitPane)
+                && (((TSplitPane) top).vertical == true)
+                && (bottom instanceof TSplitPane)
+                && (((TSplitPane) bottom).vertical == true)
+                && (((TSplitPane) top).split == ((TSplitPane) bottom).split)
+            ) {
+                putCharXY(((TSplitPane) top).split, split, '\u253C', attr);
+            } else {
+                if ((top instanceof TSplitPane)
+                    && (((TSplitPane) top).vertical == true)
+                ) {
+                    putCharXY(((TSplitPane) top).split, split, '\u2534', attr);
+                }
+                if ((bottom instanceof TSplitPane)
+                    && (((TSplitPane) bottom).vertical == true)
+                ) {
+                    putCharXY(((TSplitPane) bottom).split, split, '\u252C',
+                        attr);
+                }
+            }
+
+            if ((mouse != null)
+                && (mouse.getAbsoluteY() == getAbsoluteY() + split)
+                && (mouse.getAbsoluteX() >= getAbsoluteX()) &&
+                (mouse.getAbsoluteX() < getAbsoluteX() + getWidth())
+            ) {
+                putCharXY(mouse.getAbsoluteX() - getAbsoluteX(), split,
+                    '\u2195', attr);
+            }
         }
+
+    }
+
+    /**
+     * Generate a human-readable string for this widget.
+     *
+     * @return a human-readable string
+     */
+    @Override
+    public String toString() {
+        return String.format("%s(%8x) %s position (%d, %d) geometry %dx%d " +
+            "split %d left %s(%8x) right %s(%8x) top %s(%8x) bottom %s(%8x) " +
+            "active %s enabled %s visible %s", getClass().getName(),
+            hashCode(), (vertical ? "VERTICAL" : "HORIZONTAL"),
+            getX(), getY(), getWidth(), getHeight(), split,
+            (left == null ? "null" : left.getClass().getName()),
+            (left == null ? 0 : left.hashCode()),
+            (right == null ? "null" : right.getClass().getName()),
+            (right == null ? 0 : right.hashCode()),
+            (top == null ? "null" : top.getClass().getName()),
+            (top == null ? 0 : top.hashCode()),
+            (bottom == null ? "null" : bottom.getClass().getName()),
+            (bottom == null ? 0 : bottom.hashCode()),
+            isActive(), isEnabled(), isVisible());
     }
 
     // ------------------------------------------------------------------------
@@ -239,7 +338,9 @@ public class TSplitPane extends TWidget {
                 "horizontal split pane");
         }
         if (left == null) {
-            remove(this.left);
+            if (this.left != null) {
+                remove(this.left);
+            }
             this.left = null;
             return;
         }
@@ -269,7 +370,9 @@ public class TSplitPane extends TWidget {
                 "horizontal split pane");
         }
         if (right == null) {
-            remove(this.right);
+            if (this.right != null) {
+                remove(this.right);
+            }
             this.right = null;
             return;
         }
@@ -299,7 +402,9 @@ public class TSplitPane extends TWidget {
                 "split pane");
         }
         if (top == null) {
-            remove(this.top);
+            if (this.top != null) {
+                remove(this.top);
+            }
             this.top = null;
             return;
         }
@@ -329,7 +434,9 @@ public class TSplitPane extends TWidget {
                 "vertical split pane");
         }
         if (bottom == null) {
-            remove(this.bottom);
+            if (this.bottom != null) {
+                remove(this.bottom);
+            }
             this.bottom = null;
             return;
         }
@@ -339,6 +446,92 @@ public class TSplitPane extends TWidget {
                 getHeight()));
     }
 
+    /**
+     * Remove a widget, regardless of what pane it is on.
+     *
+     * @param widget the widget to remove
+     */
+    public void removeWidget(final TWidget widget) {
+        if (widget == null) {
+            throw new IllegalArgumentException("cannot remove null widget");
+        }
+        if (left == widget) {
+            left = null;
+            assert(right != widget);
+            assert(top != widget);
+            assert(bottom != widget);
+            return;
+        }
+        if (right == widget) {
+            right = null;
+            assert(left != widget);
+            assert(top != widget);
+            assert(bottom != widget);
+            return;
+        }
+        if (top == widget) {
+            top = null;
+            assert(left != widget);
+            assert(right != widget);
+            assert(bottom != widget);
+            return;
+        }
+        if (bottom == widget) {
+            bottom = null;
+            assert(left != widget);
+            assert(right != widget);
+            assert(top != widget);
+            return;
+        }
+        throw new IllegalArgumentException("widget " + widget +
+            " not in this split");
+    }
+
+    /**
+     * Replace a widget, regardless of what pane it is on, with another
+     * widget.
+     *
+     * @param oldWidget the widget to remove
+     * @param newWidget the widget to replace it with
+     */
+    public void replaceWidget(final TWidget oldWidget,
+        final TWidget newWidget) {
+
+        if (oldWidget == null) {
+            throw new IllegalArgumentException("cannot remove null oldWidget");
+        }
+        if (left == oldWidget) {
+            setLeft(newWidget);
+            assert(right != newWidget);
+            assert(top != newWidget);
+            assert(bottom != newWidget);
+            return;
+        }
+        if (right == oldWidget) {
+            setRight(newWidget);
+            assert(left != newWidget);
+            assert(top != newWidget);
+            assert(bottom != newWidget);
+            return;
+        }
+        if (top == oldWidget) {
+            setTop(newWidget);
+            assert(left != newWidget);
+            assert(right != newWidget);
+            assert(bottom != newWidget);
+            return;
+        }
+        if (bottom == oldWidget) {
+            setBottom(newWidget);
+            assert(left != newWidget);
+            assert(right != newWidget);
+            assert(top != newWidget);
+            return;
+        }
+        throw new IllegalArgumentException("oldWidget " + oldWidget +
+            " not in this split");
+    }
+
     /**
      * Layout the two child widgets.
      */
@@ -382,4 +575,68 @@ public class TSplitPane extends TWidget {
         layoutChildren();
     }
 
+    /**
+     * Remove this split, removing the widget specified.
+     *
+     * @param widgetToRemove the widget to remove
+     * @param doClose if true, call the close() method before removing the
+     * child
+     * @return the pane that remains, or null if nothing is retained
+     */
+    public TWidget removeSplit(final TWidget widgetToRemove,
+        final boolean doClose) {
+
+        TWidget keep = null;
+        if (vertical) {
+            if ((widgetToRemove != left) && (widgetToRemove != right)) {
+                throw new IllegalArgumentException("widget to remove is not " +
+                    "either of the panes in this splitpane");
+            }
+            if (widgetToRemove == left) {
+                keep = right;
+            } else {
+                keep = left;
+            }
+
+        } else {
+            if ((widgetToRemove != top) && (widgetToRemove != bottom)) {
+                throw new IllegalArgumentException("widget to remove is not " +
+                    "either of the panes in this splitpane");
+            }
+            if (widgetToRemove == top) {
+                keep = bottom;
+            } else {
+                keep = top;
+            }
+        }
+
+        // Remove me from my parent widget.
+        TWidget myParent = getParent();
+        remove(false);
+
+        if (keep == null) {
+            if (myParent instanceof TSplitPane) {
+                // TSplitPane has a left/right/top/bottom link to me
+                // somewhere, remove it.
+                ((TSplitPane) myParent).removeWidget(this);
+            }
+
+            // Nothing is left of either pane.  Remove me and bail out.
+            return null;
+        }
+
+        if (myParent instanceof TSplitPane) {
+            // TSplitPane has a left/right/top/bottom link to me
+            // somewhere, replace me with keep.
+            ((TSplitPane) myParent).replaceWidget(this, keep);
+        } else {
+            keep.setParent(myParent, false);
+            keep.setDimensions(getX(), getY(), getWidth(), getHeight());
+            keep.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
+                    getHeight()));
+        }
+
+        return keep;
+    }
+
 }