/** * Jexer - Java Text User Interface * * License: LGPLv3 or later * * This module is licensed under the GNU Lesser General Public License * Version 3. Please see the file "COPYING" in this directory for more * information about the GNU Lesser General Public License Version 3. * * Copyright (C) 2015 Kevin Lamonte * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see * http://www.gnu.org/licenses/, or write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * @author Kevin Lamonte [kevin.lamonte@gmail.com] * @version 1 */ package jexer; import jexer.bits.CellAttributes; import jexer.bits.GraphicsChars; import jexer.event.TKeypressEvent; import jexer.event.TMouseEvent; import static jexer.TKeypress.*; /** * TTreeView implements a simple tree view. */ public class TTreeView extends TWidget { /** * Vertical scrollbar. */ private TVScroller vScroller; /** * Horizontal scrollbar. Note package private access. */ THScroller hScroller; /** * Root of the tree. */ private TTreeItem treeRoot; /** * Get the root of the tree. * * @return the root of the tree */ public final TTreeItem getTreeRoot() { return treeRoot; } /** * Set the root of the tree. * * @param treeRoot the new root of the tree */ public final void setTreeRoot(final TTreeItem treeRoot) { this.treeRoot = treeRoot; } /** * Maximum width of a single line. */ private int maxLineWidth; /** * Only one of my children can be selected. */ private TTreeItem selectedItem = null; /** * If true, move the window to put the selected item in view. This * normally only happens once after setting treeRoot. */ public boolean centerWindow = false; /** * The action to perform when the user selects an item. */ private TAction action = null; /** * 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) { this.treeRoot = treeRoot; this.centerWindow = centerWindow; } /** * 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 TTreeView(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 TTreeView(final TWidget parent, final int x, final int y, final int width, final int height, final TAction action) { super(parent, x, y, width, height); this.action = action; } /** * Get the tree view item that was selected. * * @return the selected item, or null if no item is selected */ public final TTreeItem getSelected() { return selectedItem; } /** * Set the new selected tree view item. Note package private access. * * @param item new item that became selected */ void setSelected(final TTreeItem item) { if (item != null) { item.setSelected(true); } if ((selectedItem != null) && (selectedItem != item)) { selectedItem.setSelected(false); } selectedItem = item; } /** * Perform user selection action. Note package private access. */ void dispatch() { if (action != null) { action.DO(); } } /** * Update (or instantiate) vScroller and hScroller. */ private void updateScrollers() { // Setup vertical scroller if (vScroller == null) { vScroller = new TVScroller(this, getWidth() - 1, 0, getHeight() - 1); vScroller.setValue(0); vScroller.setTopValue(0); } vScroller.setX(getWidth() - 1); vScroller.setHeight(getHeight() - 1); vScroller.setBigChange(getHeight() - 1); // Setup horizontal scroller if (hScroller == null) { hScroller = new THScroller(this, 0, getHeight() - 1, getWidth() - 1); hScroller.setValue(0); hScroller.setLeftValue(0); } hScroller.setY(getHeight() - 1); hScroller.setWidth(getWidth() - 1); hScroller.setBigChange(getWidth() - 1); } /** * Resize text and scrollbars for a new width/height. */ public void reflow() { int selectedRow = 0; boolean foundSelectedRow = false; updateScrollers(); if (treeRoot == null) { return; } // Make each child invisible/inactive to start, expandTree() will // reactivate the visible ones. for (TWidget widget: getChildren()) { if (widget instanceof TTreeItem) { TTreeItem item = (TTreeItem) widget; item.setInvisible(true); item.setEnabled(false); } } // Expand the tree into a linear list getChildren().clear(); getChildren().addAll(treeRoot.expandTree("", true)); for (TWidget widget: getChildren()) { TTreeItem item = (TTreeItem) widget; if (item == selectedItem) { foundSelectedRow = true; } if (foundSelectedRow == false) { selectedRow++; } int lineWidth = item.getText().length() + item.getPrefix().length() + 4; if (lineWidth > maxLineWidth) { maxLineWidth = lineWidth; } } if ((centerWindow) && (foundSelectedRow)) { if ((selectedRow < vScroller.getValue()) || (selectedRow > vScroller.getValue() + getHeight() - 2) ) { vScroller.setValue(selectedRow); centerWindow = false; } } updatePositions(); // Rescale the scroll bars vScroller.setBottomValue(getChildren().size() - getHeight() + 1); if (vScroller.getBottomValue() < 0) { vScroller.setBottomValue(0); } /* if (vScroller.getValue() > vScroller.getBottomValue()) { vScroller.setValue(vScroller.getBottomValue()); } */ hScroller.setRightValue(maxLineWidth - getWidth() + 3); if (hScroller.getRightValue() < 0) { hScroller.setRightValue(0); } /* if (hScroller.getValue() > hScroller.getRightValue()) { hScroller.setValue(hScroller.getRightValue()); } */ getChildren().add(hScroller); getChildren().add(vScroller); } /** * Update the Y positions of all the children items. */ private void updatePositions() { if (treeRoot == null) { return; } int begin = vScroller.getValue(); int topY = 0; for (int i = 0; i < getChildren().size(); i++) { if (!(getChildren().get(i) instanceof TTreeItem)) { // Skip continue; } TTreeItem item = (TTreeItem) getChildren().get(i); if (i < begin) { // Render invisible item.setEnabled(false); item.setInvisible(true); continue; } if (topY >= getHeight() - 1) { // Render invisible item.setEnabled(false); item.setInvisible(true); continue; } item.setY(topY); item.setEnabled(true); item.setInvisible(false); item.setWidth(getWidth() - 1); topY++; } } /** * Handle mouse press events. * * @param mouse mouse button press event */ @Override public void onMouseDown(final TMouseEvent mouse) { if (mouse.isMouseWheelUp()) { vScroller.decrement(); } else if (mouse.isMouseWheelDown()) { vScroller.increment(); } else { // Pass to children super.onMouseDown(mouse); } // Update the screen after the scrollbars have moved reflow(); } /** * Handle mouse release events. * * @param mouse mouse button release event */ @Override public void onMouseUp(TMouseEvent mouse) { // Pass to children super.onMouseDown(mouse); // Update the screen after any thing has expanded/contracted reflow(); } /** * Handle keystrokes. * * @param keypress keystroke event */ @Override public void onKeypress(final TKeypressEvent keypress) { if (keypress.equals(kbLeft)) { hScroller.decrement(); } else if (keypress.equals(kbRight)) { hScroller.increment(); } else if (keypress.equals(kbUp)) { vScroller.decrement(); } else if (keypress.equals(kbDown)) { vScroller.increment(); } else if (keypress.equals(kbPgUp)) { vScroller.bigDecrement(); } else if (keypress.equals(kbPgDn)) { vScroller.bigIncrement(); } else if (keypress.equals(kbHome)) { vScroller.toTop(); } else if (keypress.equals(kbEnd)) { vScroller.toBottom(); } else if (keypress.equals(kbEnter)) { if (selectedItem != null) { dispatch(); } } else { // Pass other keys (tab etc.) on super.onKeypress(keypress); } // Update the screen after any thing has expanded/contracted reflow(); } }