X-Git-Url: http://git.nikiroo.be/?p=fanfix.git;a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Fjexer%2FTBrowsableWidget.java;fp=src%2Fbe%2Fnikiroo%2Fjexer%2FTBrowsableWidget.java;h=aa18d0977ac08bceb1dcebbf7a0bd6a64cffd24a;hp=0000000000000000000000000000000000000000;hb=8f34a7954f96acdd5d7be5ed6f08fea2713f7d75;hpb=4162793727db52a12e0efeaca48ac5dbdcb57bdf diff --git a/src/be/nikiroo/jexer/TBrowsableWidget.java b/src/be/nikiroo/jexer/TBrowsableWidget.java new file mode 100644 index 0000000..aa18d09 --- /dev/null +++ b/src/be/nikiroo/jexer/TBrowsableWidget.java @@ -0,0 +1,404 @@ +/* + * Jexer - Java Text User Interface + * + * The MIT License (MIT) + * + * Copyright (C) 2019 David "Niki" ROULET + * + * 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 David ROULET [niki@nikiroo.be] + * @version 1 + */ +package be.nikiroo.jexer; + +import static jexer.TKeypress.kbBackTab; +import static jexer.TKeypress.kbDown; +import static jexer.TKeypress.kbEnd; +import static jexer.TKeypress.kbEnter; +import static jexer.TKeypress.kbHome; +import static jexer.TKeypress.kbLeft; +import static jexer.TKeypress.kbPgDn; +import static jexer.TKeypress.kbPgUp; +import static jexer.TKeypress.kbRight; +import static jexer.TKeypress.kbShiftTab; +import static jexer.TKeypress.kbTab; +import static jexer.TKeypress.kbUp; +import jexer.THScroller; +import jexer.TScrollableWidget; +import jexer.TVScroller; +import jexer.TWidget; +import jexer.event.TKeypressEvent; +import jexer.event.TMouseEvent; +import jexer.event.TResizeEvent; + +/** + * This class represents a browsable {@link TWidget}, that is, a {@link TWidget} + * where you can use the keyboard or mouse to browse to one line to the next, or + * from left t right. + * + * @author niki + */ +abstract public class TBrowsableWidget extends TScrollableWidget { + private int selectedRow; + private int selectedColumn; + private int yOffset; + + /** + * The number of rows in this {@link TWidget}. + * + * @return the number of rows + */ + abstract protected int getRowCount(); + + /** + * The number of columns in this {@link TWidget}. + * + * @return the number of columns + */ + abstract protected int getColumnCount(); + + /** + * The virtual width of this {@link TWidget}, that is, the total width it + * can take to display all the data. + * + * @return the width + */ + abstract int getVirtualWidth(); + + /** + * The virtual height of this {@link TWidget}, that is, the total width it + * can take to display all the data. + * + * @return the height + */ + abstract int getVirtualHeight(); + + /** + * Basic setup of this class (called by all constructors) + */ + private void setup() { + vScroller = new TVScroller(this, 0, 0, 1); + hScroller = new THScroller(this, 0, 0, 1); + fixScrollers(); + } + + /** + * Create a new {@link TBrowsableWidget} linked to the given {@link TWidget} + * parent. + * + * @param parent + * parent widget + */ + protected TBrowsableWidget(final TWidget parent) { + super(parent); + setup(); + } + + /** + * Create a new {@link TBrowsableWidget} linked to the given {@link TWidget} + * parent. + * + * @param parent + * parent widget + * @param x + * column relative to parent + * @param y + * row relative to parent + * @param width + * width of widget + * @param height + * height of widget + */ + protected TBrowsableWidget(final TWidget parent, final int x, final int y, + final int width, final int height) { + super(parent, x, y, width, height); + setup(); + } + + /** + * Create a new {@link TBrowsableWidget} linked to the given {@link TWidget} + * parent. + * + * @param parent + * parent widget + * @param enabled + * if true assume enabled + */ + protected TBrowsableWidget(final TWidget parent, final boolean enabled) { + super(parent, enabled); + setup(); + } + + /** + * Create a new {@link TBrowsableWidget} linked to the given {@link TWidget} + * parent. + * + * @param parent + * parent widget + * @param enabled + * if true assume enabled + * @param x + * column relative to parent + * @param y + * row relative to parent + * @param width + * width of widget + * @param height + * height of widget + */ + protected TBrowsableWidget(final TWidget parent, final boolean enabled, + final int x, final int y, final int width, final int height) { + super(parent, enabled, x, y, width, height); + setup(); + } + + /** + * The currently selected row (or -1 if no row is selected). + * + * @return the selected row + */ + public int getSelectedRow() { + return selectedRow; + } + + /** + * The currently selected row (or -1 if no row is selected). + *

+ * You may want to call {@link TBrowsableWidget#reflowData()} when done to + * see the changes. + * + * @param selectedRow + * the new selected row + * + * @throws IndexOutOfBoundsException + * when the index is out of bounds + */ + public void setSelectedRow(int selectedRow) { + if (selectedRow < -1 || selectedRow >= getRowCount()) { + throw new IndexOutOfBoundsException(String.format( + "Cannot set row %d on a table with %d rows", selectedRow, + getRowCount())); + } + + this.selectedRow = selectedRow; + } + + /** + * The currently selected column (or -1 if no column is selected). + * + * @return the new selected column + */ + public int getSelectedColumn() { + return selectedColumn; + } + + /** + * The currently selected column (or -1 if no column is selected). + *

+ * You may want to call {@link TBrowsableWidget#reflowData()} when done to + * see the changes. + * + * @param selectedColumn + * the new selected column + * + * @throws IndexOutOfBoundsException + * when the index is out of bounds + */ + public void setSelectedColumn(int selectedColumn) { + if (selectedColumn < -1 || selectedColumn >= getColumnCount()) { + throw new IndexOutOfBoundsException(String.format( + "Cannot set column %d on a table with %d columns", + selectedColumn, getColumnCount())); + } + + this.selectedColumn = selectedColumn; + } + + /** + * An offset on the Y position of the table, i.e., the number of rows to + * skip so the control can draw that many rows always on top. + * + * @return the offset + */ + public int getYOffset() { + return yOffset; + } + + /** + * An offset on the Y position of the table, i.e., the number of rows that + * should always stay on top. + * + * @param yOffset + * the new offset + */ + public void setYOffset(int yOffset) { + this.yOffset = yOffset; + } + + @SuppressWarnings("unused") + public void dispatchMove(int fromRow, int toRow) { + reflowData(); + } + + @SuppressWarnings("unused") + public void dispatchEnter(int selectedRow) { + reflowData(); + } + + @Override + public void onMouseDown(final TMouseEvent mouse) { + if (mouse.isMouseWheelUp()) { + vScroller.decrement(); + return; + } + if (mouse.isMouseWheelDown()) { + vScroller.increment(); + return; + } + + if ((mouse.getX() < getWidth() - 1) && (mouse.getY() < getHeight() - 1)) { + if (vScroller.getValue() + mouse.getY() < getRowCount()) { + selectedRow = vScroller.getValue() + mouse.getY() + - getYOffset(); + } + dispatchEnter(selectedRow); + return; + } + + // Pass to children + super.onMouseDown(mouse); + } + + @Override + public void onKeypress(final TKeypressEvent keypress) { + int maxX = getRowCount(); + int prevSelectedRow = selectedRow; + + int firstLineIndex = vScroller.getValue() - getYOffset() + 2; + int lastLineIndex = firstLineIndex - hScroller.getHeight() + + getHeight() - 2 - 2; + + if (keypress.equals(kbLeft)) { + hScroller.decrement(); + } else if (keypress.equals(kbRight)) { + hScroller.increment(); + } else if (keypress.equals(kbUp)) { + if (maxX > 0 && selectedRow < maxX) { + if (selectedRow > 0) { + if (selectedRow <= firstLineIndex) { + vScroller.decrement(); + } + selectedRow--; + } else { + selectedRow = 0; + } + + dispatchMove(prevSelectedRow, selectedRow); + } + } else if (keypress.equals(kbDown)) { + if (maxX > 0) { + if (selectedRow >= 0) { + if (selectedRow < maxX - 1) { + selectedRow++; + if (selectedRow >= lastLineIndex) { + vScroller.increment(); + } + } + } else { + selectedRow = 0; + } + + dispatchMove(prevSelectedRow, selectedRow); + } + } else if (keypress.equals(kbPgUp)) { + if (selectedRow >= 0) { + vScroller.bigDecrement(); + selectedRow -= getHeight() - 1; + if (selectedRow < 0) { + selectedRow = 0; + } + + dispatchMove(prevSelectedRow, selectedRow); + } + } else if (keypress.equals(kbPgDn)) { + if (selectedRow >= 0) { + vScroller.bigIncrement(); + selectedRow += getHeight() - 1; + if (selectedRow > getRowCount() - 1) { + selectedRow = getRowCount() - 1; + } + + dispatchMove(prevSelectedRow, selectedRow); + } + } else if (keypress.equals(kbHome)) { + if (getRowCount() > 0) { + vScroller.toTop(); + selectedRow = 0; + dispatchMove(prevSelectedRow, selectedRow); + } + } else if (keypress.equals(kbEnd)) { + if (getRowCount() > 0) { + vScroller.toBottom(); + selectedRow = getRowCount() - 1; + dispatchMove(prevSelectedRow, selectedRow); + } + } else if (keypress.equals(kbTab)) { + getParent().switchWidget(true); + } else if (keypress.equals(kbShiftTab) || keypress.equals(kbBackTab)) { + getParent().switchWidget(false); + } else if (keypress.equals(kbEnter)) { + if (selectedRow >= 0) { + dispatchEnter(selectedRow); + } + } else { + // Pass other keys (tab etc.) on + super.onKeypress(keypress); + } + } + + @Override + public void onResize(TResizeEvent event) { + super.onResize(event); + reflowData(); + } + + @Override + public void reflowData() { + super.reflowData(); + fixScrollers(); + } + + private void fixScrollers() { + vScroller.setX(Math.max(0, getWidth() - 3)); + vScroller.setHeight(Math.max(1, getHeight() - 2)); + hScroller.setY(Math.max(0, getHeight() - 3)); + hScroller.setWidth(Math.max(1, getWidth() - 3)); + + // virtual_size + // - the other scroll bar size + // + 2 (for the border of the window) + vScroller.setTopValue(0); + vScroller.setBottomValue(Math.max(0, getVirtualHeight() - getHeight() + + hScroller.getHeight() + 2)); + hScroller.setLeftValue(0); + hScroller.setRightValue(Math.max(0, getVirtualWidth() - getWidth() + + vScroller.getWidth() + 2)); + } +}