Add 'src/jexer/' from commit 'cf01c92f5809a0732409e280fb0f32f27393618d'
[fanfix.git] / src / jexer / ttree / TTreeViewWidget.java
diff --git a/src/jexer/ttree/TTreeViewWidget.java b/src/jexer/ttree/TTreeViewWidget.java
new file mode 100644 (file)
index 0000000..080a200
--- /dev/null
@@ -0,0 +1,406 @@
+/*
+ * 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.ttree;
+
+import jexer.TAction;
+import jexer.THScroller;
+import jexer.TKeypress;
+import jexer.TScrollableWidget;
+import jexer.TVScroller;
+import jexer.TWidget;
+import jexer.bits.StringUtils;
+import jexer.event.TKeypressEvent;
+import jexer.event.TMouseEvent;
+import jexer.event.TResizeEvent;
+import static jexer.TKeypress.*;
+
+/**
+ * TTreeViewWidget wraps a tree view with horizontal and vertical scrollbars.
+ */
+public class TTreeViewWidget extends TScrollableWidget {
+
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * The TTreeView
+     */
+    private TTreeView treeView;
+
+    /**
+     * If true, move the window to put the selected item in view.  This
+     * normally only happens once after setting treeRoot.
+     */
+    private boolean centerWindow = false;
+
+    /**
+     * Maximum width of a single line.
+     */
+    private int maxLineWidth;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.
+     *
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width width of tree view
+     * @param height height of tree view
+     */
+    public TTreeViewWidget(final TWidget parent, final int x, final int y,
+        final int width, final int height) {
+
+        this(parent, x, y, width, height, null);
+    }
+
+    /**
+     * Public constructor.
+     *
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width width of tree view
+     * @param height height of tree view
+     * @param action action to perform when an item is selected
+     */
+    public TTreeViewWidget(final TWidget parent, final int x, final int y,
+        final int width, final int height, final TAction action) {
+
+        super(parent, x, y, width, height);
+
+        treeView = new TTreeView(this, 0, 0, getWidth() - 1, getHeight() - 1,
+            action);
+
+        vScroller = new TVScroller(this, getWidth() - 1, 0, getHeight() - 1);
+        hScroller = new THScroller(this, 0, getHeight() - 1, getWidth() - 1);
+
+    }
+
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Handle window/screen resize events.
+     *
+     * @param event resize event
+     */
+    @Override
+    public void onResize(final TResizeEvent event) {
+        super.onResize(event);
+
+        if (event.getType() == TResizeEvent.Type.WIDGET) {
+            treeView.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
+                    getWidth() - 1, getHeight() - 1));
+            return;
+        } else {
+            super.onResize(event);
+        }
+    }
+
+    /**
+     * Handle mouse press events.
+     *
+     * @param mouse mouse button press event
+     */
+    @Override
+    public void onMouseDown(final TMouseEvent mouse) {
+        if (mouse.isMouseWheelUp()) {
+            verticalDecrement();
+        } else if (mouse.isMouseWheelDown()) {
+            verticalIncrement();
+        } else {
+            // Pass to the TreeView or scrollbars
+            super.onMouseDown(mouse);
+        }
+
+        // Update the view to reflect the new scrollbar positions
+        treeView.setTopLine(getVerticalValue());
+        treeView.setLeftColumn(getHorizontalValue());
+        reflowData();
+    }
+
+    /**
+     * Handle mouse release events.
+     *
+     * @param mouse mouse button release event
+     */
+    @Override
+    public void onMouseUp(final TMouseEvent mouse) {
+        // Pass to the TreeView or scrollbars
+        super.onMouseUp(mouse);
+
+        // Update the view to reflect the new scrollbar positions
+        treeView.setTopLine(getVerticalValue());
+        treeView.setLeftColumn(getHorizontalValue());
+        reflowData();
+    }
+
+    /**
+     * Handle mouse motion events.
+     *
+     * @param mouse mouse motion event
+     */
+    @Override
+    public void onMouseMotion(final TMouseEvent mouse) {
+        // Pass to the TreeView or scrollbars
+        super.onMouseMotion(mouse);
+
+        // Update the view to reflect the new scrollbar positions
+        treeView.setTopLine(getVerticalValue());
+        treeView.setLeftColumn(getHorizontalValue());
+        reflowData();
+    }
+
+    /**
+     * Handle keystrokes.
+     *
+     * @param keypress keystroke event
+     */
+    @Override
+    public void onKeypress(final TKeypressEvent keypress) {
+        if (keypress.equals(kbShiftLeft)
+            || keypress.equals(kbCtrlLeft)
+            || keypress.equals(kbAltLeft)
+        ) {
+            horizontalDecrement();
+        } else if (keypress.equals(kbShiftRight)
+            || keypress.equals(kbCtrlRight)
+            || keypress.equals(kbAltRight)
+        ) {
+            horizontalIncrement();
+        } else if (keypress.equals(kbShiftUp)
+            || keypress.equals(kbCtrlUp)
+            || keypress.equals(kbAltUp)
+        ) {
+            verticalDecrement();
+        } else if (keypress.equals(kbShiftDown)
+            || keypress.equals(kbCtrlDown)
+            || keypress.equals(kbAltDown)
+        ) {
+            verticalIncrement();
+        } else if (keypress.equals(kbShiftPgUp)
+            || keypress.equals(kbCtrlPgUp)
+            || keypress.equals(kbAltPgUp)
+        ) {
+            bigVerticalDecrement();
+        } else if (keypress.equals(kbShiftPgDn)
+            || keypress.equals(kbCtrlPgDn)
+            || keypress.equals(kbAltPgDn)
+        ) {
+            bigVerticalIncrement();
+        } else if (keypress.equals(kbPgDn)) {
+            for (int i = 0; i < getHeight() - 2; i++) {
+                treeView.onKeypress(new TKeypressEvent(TKeypress.kbDown));
+            }
+            reflowData();
+            return;
+        } else if (keypress.equals(kbPgUp)) {
+            for (int i = 0; i < getHeight() - 2; i++) {
+                treeView.onKeypress(new TKeypressEvent(TKeypress.kbUp));
+            }
+            reflowData();
+            return;
+        } else if (keypress.equals(kbHome)) {
+            treeView.setSelected((TTreeItem) treeView.getChildren().get(0),
+                false);
+            treeView.setTopLine(0);
+            reflowData();
+            return;
+        } else if (keypress.equals(kbEnd)) {
+            treeView.setSelected((TTreeItem)  treeView.getChildren().get(
+                treeView.getChildren().size() - 1), true);
+            reflowData();
+            return;
+        } else if (keypress.equals(kbTab)) {
+            getParent().switchWidget(true);
+            return;
+        } else if (keypress.equals(kbShiftTab)
+                || keypress.equals(kbBackTab)) {
+            getParent().switchWidget(false);
+            return;
+        } else {
+            treeView.onKeypress(keypress);
+
+            // Update the scrollbars to reflect the new data position
+            reflowData();
+            return;
+        }
+
+        // Update the view to reflect the new scrollbar position
+        treeView.setTopLine(getVerticalValue());
+        treeView.setLeftColumn(getHorizontalValue());
+        reflowData();
+    }
+
+    // ------------------------------------------------------------------------
+    // TScrollableWidget ------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Resize text and scrollbars for a new width/height.
+     */
+    @Override
+    public void reflowData() {
+        int selectedRow = 0;
+        boolean foundSelectedRow = false;
+
+        // Reset the keyboard list, expandTree() will recreate it.
+        for (TWidget widget: treeView.getChildren()) {
+            TTreeItem item = (TTreeItem) widget;
+            item.keyboardPrevious = null;
+            item.keyboardNext = null;
+        }
+
+        // Expand the tree into a linear list
+        treeView.getChildren().clear();
+        treeView.getChildren().addAll(treeView.getTreeRoot().expandTree("",
+                true));
+
+        // Locate the selected row and maximum line width
+        for (TWidget widget: treeView.getChildren()) {
+            TTreeItem item = (TTreeItem) widget;
+
+            if (item == treeView.getSelected()) {
+                foundSelectedRow = true;
+            }
+            if (!foundSelectedRow) {
+                selectedRow++;
+            }
+
+            int lineWidth = StringUtils.width(item.getText())
+                + item.getPrefix().length() + 4;
+            if (lineWidth > maxLineWidth) {
+                maxLineWidth = lineWidth;
+            }
+        }
+
+        if ((centerWindow) && (foundSelectedRow)) {
+            if ((selectedRow < getVerticalValue())
+                || (selectedRow > getVerticalValue() + getHeight() - 2)
+            ) {
+                treeView.setTopLine(selectedRow);
+                centerWindow = false;
+            }
+        }
+        treeView.alignTree();
+
+        // Rescale the scroll bars
+        setVerticalValue(treeView.getTopLine());
+        setBottomValue(treeView.getTotalLineCount() - (getHeight() - 1));
+        if (getBottomValue() < getTopValue()) {
+            setBottomValue(getTopValue());
+        }
+        if (getVerticalValue() > getBottomValue()) {
+            setVerticalValue(getBottomValue());
+        }
+        setRightValue(maxLineWidth - 2);
+        if (getHorizontalValue() > getRightValue()) {
+            setHorizontalValue(getRightValue());
+        }
+
+    }
+
+    // ------------------------------------------------------------------------
+    // TTreeView --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get the underlying TTreeView.
+     *
+     * @return the TTreeView
+     */
+    public TTreeView getTreeView() {
+        return treeView;
+    }
+
+    /**
+     * Get the root of the tree.
+     *
+     * @return the root of the tree
+     */
+    public final TTreeItem getTreeRoot() {
+        return treeView.getTreeRoot();
+    }
+
+    /**
+     * Set the root of the tree.
+     *
+     * @param treeRoot the new root of the tree
+     */
+    public final void setTreeRoot(final TTreeItem treeRoot) {
+        treeView.setTreeRoot(treeRoot);
+    }
+
+    /**
+     * Set treeRoot.
+     *
+     * @param treeRoot ultimate root of tree
+     * @param centerWindow if true, move the window to put the root in view
+     */
+    public void setTreeRoot(final TTreeItem treeRoot,
+        final boolean centerWindow) {
+
+        treeView.setTreeRoot(treeRoot);
+        this.centerWindow = centerWindow;
+    }
+
+    /**
+     * Get the tree view item that was selected.
+     *
+     * @return the selected item, or null if no item is selected
+     */
+    public final TTreeItem getSelected() {
+        return treeView.getSelected();
+    }
+
+    /**
+     * Set the new selected tree view item.
+     *
+     * @param item new item that became selected
+     * @param centerWindow if true, move the window to put the selected into
+     * view
+     */
+    public void setSelected(final TTreeItem item, final boolean centerWindow) {
+        treeView.setSelected(item, centerWindow);
+    }
+
+    /**
+     * Perform user selection action.
+     */
+    public void dispatch() {
+        treeView.dispatch();
+    }
+
+}