};
selectBox = addComboBox(0, 0, 10, selects, 0, -1, onSelect);
- selectBox.setReadOnly(true);
selectTargetBox = addComboBox(0, 0, 0, selectTargets, 0, -1,
new TAction() {
}
}
});
- selectTargetBox.setReadOnly(true);
// Set defaults
onSelect.DO();
import jexer.TAction;
import jexer.TButton;
import jexer.TLabel;
-import jexer.TTable;
import jexer.TText;
import jexer.TWindow;
import jexer.event.TCommandEvent;
import be.nikiroo.fanfix.data.Paragraph;
import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.jexer.TTable;
import be.nikiroo.utils.StringUtils;
/**
// last = use window background
titleField = new TLabel(this, " Title", 0, 1, "tlabel", false);
textField = new TText(this, "", 0, 0, 1, 1);
- table = new TTable(this, 0, 0, 1, 1, null, null, Arrays.asList("Key",
- "Value"), true);
+ table = new TTable(this, 0, 0, 1, 1, null, null, Arrays.asList("Key", "Value"), true);
titleField.setEnabled(false);
--- /dev/null
+/*
+ * 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).
+ * <p>
+ * 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).
+ * <p>
+ * 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));
+ }
+}
--- /dev/null
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.table.TableModel;
+
+import be.nikiroo.jexer.TTableCellRenderer.CellRendererMode;
+import jexer.TAction;
+import jexer.TWidget;
+import jexer.bits.CellAttributes;
+
+/**
+ * A table widget to display and browse through tabular data.
+ * <p>
+ * Currently, you can only select a line (a row) at a time, but the data you
+ * present is still tabular. You also access the data in a tabular way (by
+ * <tt>(raw,column)</tt>).
+ *
+ * @author niki
+ */
+public class TTable extends TBrowsableWidget {
+ // Default renderers use text mode
+ static private TTableCellRenderer defaultSeparatorRenderer = new TTableCellRendererText(
+ CellRendererMode.SEPARATOR);
+ static private TTableCellRenderer defaultHeaderRenderer = new TTableCellRendererText(
+ CellRendererMode.HEADER);
+ static private TTableCellRenderer defaultHeaderSeparatorRenderer = new TTableCellRendererText(
+ CellRendererMode.HEADER_SEPARATOR);
+
+ private boolean showHeader;
+
+ private List<TTableColumn> columns = new ArrayList<TTableColumn>();
+ private TableModel model;
+
+ private int selectedColumn;
+
+ private TTableCellRenderer separatorRenderer;
+ private TTableCellRenderer headerRenderer;
+ private TTableCellRenderer headerSeparatorRenderer;
+
+ /**
+ * The action to perform when the user selects an item (clicks or enter).
+ */
+ private TAction enterAction = null;
+
+ /**
+ * The action to perform when the user navigates with keyboard.
+ */
+ private TAction moveAction = null;
+
+ /**
+ * Create a new {@link TTable}.
+ *
+ * @param parent
+ * the parent widget
+ * @param x
+ * the X position
+ * @param y
+ * the Y position
+ * @param width
+ * the width of the {@link TTable}
+ * @param height
+ * the height of the {@link TTable}
+ * @param enterAction
+ * an action to call when a cell is selected
+ * @param moveAction
+ * an action to call when the currently active cell is changed
+ */
+ public TTable(TWidget parent, int x, int y, int width, int height,
+ final TAction enterAction, final TAction moveAction) {
+ this(parent, x, y, width, height, enterAction, moveAction, null, false);
+ }
+
+ /**
+ * Create a new {@link TTable}.
+ *
+ * @param parent
+ * the parent widget
+ * @param x
+ * the X position
+ * @param y
+ * the Y position
+ * @param width
+ * the width of the {@link TTable}
+ * @param height
+ * the height of the {@link TTable}
+ * @param enterAction
+ * an action to call when a cell is selected
+ * @param moveAction
+ * an action to call when the currently active cell is changed
+ * @param headers
+ * the headers of the {@link TTable}
+ * @param showHeaders
+ * TRUE to show the headers on screen
+ */
+ public TTable(TWidget parent, int x, int y, int width, int height,
+ final TAction enterAction, final TAction moveAction,
+ List<? extends Object> headers, boolean showHeaders) {
+ super(parent, x, y, width, height);
+
+ this.model = new TTableModel(new Object[][] {});
+ setSelectedRow(-1);
+ this.selectedColumn = -1;
+
+ setHeaders(headers, showHeaders);
+
+ this.enterAction = enterAction;
+ this.moveAction = moveAction;
+
+ reflowData();
+ }
+
+ /**
+ * The data model (containing the actual data) used by this {@link TTable},
+ * as with the usual Swing tables.
+ *
+ * @return the model
+ */
+ public TableModel getModel() {
+ return model;
+ }
+
+ /**
+ * The data model (containing the actual data) used by this {@link TTable},
+ * as with the usual Swing tables.
+ * <p>
+ * Will reset all the rendering cells.
+ *
+ * @param model
+ * the new model
+ */
+ public void setModel(TableModel model) {
+ this.model = model;
+ reflowData();
+ }
+
+ /**
+ * The columns used by this {@link TTable} (you need to access them if you
+ * want to change the way they are rendered, for instance, or their size).
+ *
+ * @return the columns
+ */
+ public List<TTableColumn> getColumns() {
+ return columns;
+ }
+
+ /**
+ * The {@link TTableCellRenderer} used by the separators (one separator
+ * between two data columns).
+ *
+ * @return the renderer, or the default one if none is set (never NULL)
+ */
+ public TTableCellRenderer getSeparatorRenderer() {
+ return separatorRenderer != null ? separatorRenderer
+ : defaultSeparatorRenderer;
+ }
+
+ /**
+ * The {@link TTableCellRenderer} used by the separators (one separator
+ * between two data columns).
+ *
+ * @param separatorRenderer
+ * the new renderer, or NULL to use the default renderer
+ */
+ public void setSeparatorRenderer(TTableCellRenderer separatorRenderer) {
+ this.separatorRenderer = separatorRenderer;
+ }
+
+ /**
+ * The {@link TTableCellRenderer} used by the headers (if
+ * {@link TTable#isShowHeader()} is enabled, the first line represents the
+ * headers with the column names).
+ *
+ * @return the renderer, or the default one if none is set (never NULL)
+ */
+ public TTableCellRenderer getHeaderRenderer() {
+ return headerRenderer != null ? headerRenderer : defaultHeaderRenderer;
+ }
+
+ /**
+ * The {@link TTableCellRenderer} used by the headers (if
+ * {@link TTable#isShowHeader()} is enabled, the first line represents the
+ * headers with the column names).
+ *
+ * @param headerRenderer
+ * the new renderer, or NULL to use the default renderer
+ */
+ public void setHeaderRenderer(TTableCellRenderer headerRenderer) {
+ this.headerRenderer = headerRenderer;
+ }
+
+ /**
+ * The {@link TTableCellRenderer} to use on separators in header lines (see
+ * the related methods to understand what each of them is).
+ *
+ * @return the renderer, or the default one if none is set (never NULL)
+ */
+ public TTableCellRenderer getHeaderSeparatorRenderer() {
+ return headerSeparatorRenderer != null ? headerSeparatorRenderer
+ : defaultHeaderSeparatorRenderer;
+ }
+
+ /**
+ * The {@link TTableCellRenderer} to use on separators in header lines (see
+ * the related methods to understand what each of them is).
+ *
+ * @param headerSeparatorRenderer
+ * the new renderer, or NULL to use the default renderer
+ */
+ public void setHeaderSeparatorRenderer(
+ TTableCellRenderer headerSeparatorRenderer) {
+ this.headerSeparatorRenderer = headerSeparatorRenderer;
+ }
+
+ /**
+ * Show the header row on this {@link TTable}.
+ *
+ * @return TRUE if we show them
+ */
+ public boolean isShowHeader() {
+ return showHeader;
+ }
+
+ /**
+ * Show the header row on this {@link TTable}.
+ *
+ * @param showHeader
+ * TRUE to show them
+ */
+ public void setShowHeader(boolean showHeader) {
+ this.showHeader = showHeader;
+ setYOffset(showHeader ? 2 : 0);
+ reflowData();
+ }
+
+ /**
+ * Change the headers of the table.
+ * <p>
+ * Note that this method is a convenience method that will create columns of
+ * the corresponding names and set them. As such, the previous columns if
+ * any will be replaced.
+ *
+ * @param headers
+ * the new headers
+ */
+ public void setHeaders(List<? extends Object> headers) {
+ setHeaders(headers, showHeader);
+ }
+
+ /**
+ * Change the headers of the table.
+ * <p>
+ * Note that this method is a convenience method that will create columns of
+ * the corresponding names and set them in the same order. As such, the
+ * previous columns if any will be replaced.
+ *
+ * @param headers
+ * the new headers
+ * @param showHeader
+ * TRUE to show them on screen
+ */
+ public void setHeaders(List<? extends Object> headers, boolean showHeader) {
+ if (headers == null) {
+ headers = new ArrayList<Object>();
+ }
+
+ int i = 0;
+ this.columns = new ArrayList<TTableColumn>();
+ for (Object header : headers) {
+ this.columns.add(new TTableColumn(i++, header, getModel()));
+ }
+
+ setShowHeader(showHeader);
+ }
+
+ /**
+ * Set the data and create a new {@link TTableModel} for them.
+ *
+ * @param data
+ * the data to set into this table, as an array of rows, that is,
+ * an array of arrays of values
+ */
+
+ public void setRowData(Object[][] data) {
+ setRowData(TTableModel.convert(data));
+ }
+
+ /**
+ * Set the data and create a new {@link TTableModel} for them.
+ *
+ * @param data
+ * the data to set into this table, as a collection of rows, that
+ * is, a collection of collections of values
+ */
+ public void setRowData(
+ final Collection<? extends Collection<? extends Object>> data) {
+ setModel(new TTableModel(data));
+ }
+
+ /**
+ * The currently selected cell.
+ *
+ * @return the cell
+ */
+ public Object getSelectedCell() {
+ int selectedRow = getSelectedRow();
+ if (selectedRow >= 0 && selectedColumn >= 0) {
+ return model.getValueAt(selectedRow, selectedColumn);
+ }
+
+ return null;
+ }
+
+ @Override
+ public int getRowCount() {
+ if (model == null) {
+ return 0;
+ }
+ return model.getRowCount();
+ }
+
+ @Override
+ public int getColumnCount() {
+ if (model == null) {
+ return 0;
+ }
+ return model.getColumnCount();
+ }
+
+ @Override
+ public void dispatchEnter(int selectedRow) {
+ super.dispatchEnter(selectedRow);
+ if (enterAction != null) {
+ enterAction.DO();
+ }
+ }
+
+ @Override
+ public void dispatchMove(int fromRow, int toRow) {
+ super.dispatchMove(fromRow, toRow);
+ if (moveAction != null) {
+ moveAction.DO();
+ }
+ }
+
+ /**
+ * Clear the content of the {@link TTable}.
+ * <p>
+ * It will not affect the headers.
+ * <p>
+ * You may want to call {@link TTable#reflowData()} when done to see the
+ * changes.
+ */
+ public void clear() {
+ setSelectedRow(-1);
+ selectedColumn = -1;
+ setModel(new TTableModel(new Object[][] {}));
+ }
+
+ @Override
+ public void reflowData() {
+ super.reflowData();
+
+ int lastAutoColumn = -1;
+ int rowWidth = 0;
+
+ int i = 0;
+ for (TTableColumn tcol : columns) {
+ tcol.reflowData();
+
+ if (!tcol.isForcedWidth()) {
+ lastAutoColumn = i;
+ }
+
+ rowWidth += tcol.getWidth();
+
+ i++;
+ }
+
+ if (!columns.isEmpty()) {
+ rowWidth += (i - 1) * getSeparatorRenderer().getWidthOf(null);
+
+ int extraWidth = getWidth() - rowWidth;
+ if (extraWidth > 0) {
+ if (lastAutoColumn < 0) {
+ lastAutoColumn = columns.size() - 1;
+ }
+ TTableColumn tcol = columns.get(lastAutoColumn);
+ tcol.expandWidthTo(tcol.getWidth() + extraWidth);
+ rowWidth += extraWidth;
+ }
+ }
+ }
+
+ @Override
+ public void draw() {
+ int begin = vScroller.getValue();
+ int y = this.showHeader ? 2 : 0;
+
+ if (showHeader) {
+ CellAttributes colorHeaders = getHeaderRenderer()
+ .getCellAttributes(getTheme(), false, isAbsoluteActive());
+ drawRow(-1, 0);
+ String formatString = "%-" + Integer.toString(getWidth()) + "s";
+ String data = String.format(formatString, "");
+ getScreen().putStringXY(0, 1, data, colorHeaders);
+ }
+
+ // draw the actual rows until no more,
+ // then pad the rest with blank rows
+ for (int i = begin; i < getRowCount(); i++) {
+ drawRow(i, y);
+ y++;
+
+ // -2: window borders
+ if (y >= getHeight() - 2 - getHorizontalScroller().getHeight()) {
+ break;
+ }
+ }
+
+ CellAttributes emptyRowColor = getSeparatorRenderer()
+ .getCellAttributes(getTheme(), false, isAbsoluteActive());
+ for (int i = getRowCount(); i < getHeight(); i++) {
+ getScreen().hLineXY(0, y, getWidth() - 1, ' ', emptyRowColor);
+ y++;
+ }
+ }
+
+ @Override
+ protected int getVirtualWidth() {
+ int width = 0;
+
+ if (getColumns() != null) {
+ for (TTableColumn tcol : getColumns()) {
+ width += tcol.getWidth();
+ }
+
+ if (getColumnCount() > 0) {
+ width += (getColumnCount() - 1)
+ * getSeparatorRenderer().getWidthOf(null);
+ }
+ }
+
+ return width;
+ }
+
+ @Override
+ protected int getVirtualHeight() {
+ // TODO: allow changing the height of one row
+ return (showHeader ? 2 : 0) + (getRowCount() * 1);
+ }
+
+ /**
+ * Draw the given row (it <b>MUST</b> exist) at the specified index and
+ * offset.
+ *
+ * @param rowIndex
+ * the index of the row to draw or -1 for the headers
+ * @param y
+ * the Y position
+ */
+ private void drawRow(int rowIndex, int y) {
+ for (int i = 0; i < getColumnCount(); i++) {
+ TTableColumn tcol = columns.get(i);
+ Object value;
+ if (rowIndex < 0) {
+ value = tcol.getHeaderValue();
+ } else {
+ value = model.getValueAt(rowIndex, tcol.getModelIndex());
+ }
+
+ if (i > 0) {
+ TTableCellRenderer sep = rowIndex < 0 ? getHeaderSeparatorRenderer()
+ : getSeparatorRenderer();
+ sep.renderTableCell(this, null, rowIndex, i - 1, y);
+ }
+
+ if (rowIndex < 0) {
+ getHeaderRenderer()
+ .renderTableCell(this, value, rowIndex, i, y);
+ } else {
+ tcol.getRenderer().renderTableCell(this, value, rowIndex, i, y);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * 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 jexer.bits.CellAttributes;
+import jexer.bits.ColorTheme;
+
+/**
+ * A {@link TTable} cell renderer allows you to customize the way a single cell
+ * will be displayed on screen.
+ * <p>
+ * It can be used in a {@link TTable} for the haeders or the separators or in a
+ * {@link TTableColumn} for the data.
+ *
+ * @author niki
+ */
+abstract public class TTableCellRenderer {
+ private CellRendererMode mode;
+
+ /**
+ * The simple renderer mode.
+ *
+ * @author niki
+ */
+ public enum CellRendererMode {
+ /** Normal text mode */
+ NORMAL,
+ /** Only display a separator */
+ SEPARATOR,
+ /** Header text mode */
+ HEADER,
+ /** Both HEADER and SEPARATOR at once */
+ HEADER_SEPARATOR;
+
+ /**
+ * This mode represents a separator.
+ *
+ * @return TRUE for separators
+ */
+ public boolean isSeparator() {
+ return this == SEPARATOR || this == HEADER_SEPARATOR;
+ }
+
+ /**
+ * This mode represents a header.
+ *
+ * @return TRUE for headers
+ */
+ public boolean isHeader() {
+ return this == HEADER || this == HEADER_SEPARATOR;
+ }
+ }
+
+ /**
+ * Create a new renderer of the given mode.
+ *
+ * @param mode
+ * the renderer mode, cannot be NULL
+ */
+ public TTableCellRenderer(CellRendererMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException(
+ "Cannot create a renderer of type NULL");
+ }
+
+ this.mode = mode;
+ }
+
+ /**
+ * Render the given value.
+ *
+ * @param table
+ * the table to write on
+ * @param value
+ * the value to write
+ * @param rowIndex
+ * the row index in the table
+ * @param colIndex
+ * the column index in the table
+ * @param y
+ * the Y position at which to draw this row
+ */
+ abstract public void renderTableCell(TTable table, Object value,
+ int rowIndex, int colIndex, int y);
+
+ /**
+ * The mode of this {@link TTableCellRenderer}.
+ *
+ * @return the mode
+ */
+ public CellRendererMode getMode() {
+ return mode;
+ }
+
+ /**
+ * The cell attributes to use for the given state.
+ *
+ * @param theme
+ * the color theme to use
+ * @param isSelected
+ * TRUE if the cell is selected
+ * @param hasFocus
+ * TRUE if the cell has focus
+ *
+ * @return the attributes
+ */
+ public CellAttributes getCellAttributes(ColorTheme theme,
+ boolean isSelected, boolean hasFocus) {
+ return theme.getColor(getColorKey(isSelected, hasFocus));
+ }
+
+ /**
+ * Measure the width of the value.
+ *
+ * @param value
+ * the value to measure
+ *
+ * @return its width
+ */
+ public int getWidthOf(Object value) {
+ if (getMode().isSeparator()) {
+ return asText(null, 0, false).length();
+ }
+ return ("" + value).length();
+ }
+
+ /**
+ * The colour to use for the given state, specified as a Jexer colour key.
+ *
+ * @param isSelected
+ * TRUE if the cell is selected
+ * @param hasFocus
+ * TRUE if the cell has focus
+ *
+ * @return the colour key
+ */
+ protected String getColorKey(boolean isSelected, boolean hasFocus) {
+ if (mode.isHeader()) {
+ return "tlabel";
+ }
+
+ String colorKey = "tlist";
+ if (isSelected) {
+ colorKey += ".selected";
+ } else if (!hasFocus) {
+ colorKey += ".inactive";
+ }
+
+ return colorKey;
+ }
+
+ /**
+ * Return the X offset to use to draw a column at the given index.
+ *
+ * @param table
+ * the table to draw into
+ * @param colIndex
+ * the column index
+ *
+ * @return the offset
+ */
+ protected int getXOffset(TTable table, int colIndex) {
+ int xOffset = -table.getHorizontalValue();
+ for (int i = 0; i <= colIndex; i++) {
+ TTableColumn tcol = table.getColumns().get(i);
+ xOffset += tcol.getWidth();
+ if (i > 0) {
+ xOffset += table.getSeparatorRenderer().getWidthOf(null);
+ }
+ }
+
+ TTableColumn tcol = table.getColumns().get(colIndex);
+ if (!getMode().isSeparator()) {
+ xOffset -= tcol.getWidth();
+ }
+
+ return xOffset;
+ }
+
+ /**
+ * Return the text to use (usually the converted-to-text value, except for
+ * the special separator mode).
+ *
+ * @param value
+ * the value to get the text of
+ * @param width
+ * the width we should tale
+ * @param align
+ * the text to the right
+ *
+ * @return the {@link String} to display
+ */
+ protected String asText(Object value, int width, boolean rightAlign) {
+ if (getMode().isSeparator()) {
+ // some nice characters for the separator: ┃ │ |
+ return " │ ";
+ }
+
+ if (width <= 0) {
+ return "";
+ }
+
+ String format;
+ if (!rightAlign) {
+ // Left align
+ format = "%-" + width + "s";
+ } else {
+ // right align
+ format = "%" + width + "s";
+ }
+
+ return String.format(format, value);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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 jexer.bits.CellAttributes;
+
+/**
+ * A simple {@link TTableCellRenderer} that display the values within a
+ * {@link TLabel}.
+ * <p>
+ * It supports a few different modes, see
+ * {@link TTableOldSimpleTextCellRenderer.CellRendererMode}.
+ *
+ * @author niki
+ */
+public class TTableCellRendererText extends TTableCellRenderer {
+ private boolean rightAlign;
+
+ /**
+ * Create a new renderer for normal text mode.
+ */
+ public TTableCellRendererText() {
+ this(CellRendererMode.NORMAL);
+ }
+
+ /**
+ * Create a new renderer of the given mode.
+ *
+ * @param mode
+ * the renderer mode
+ */
+ public TTableCellRendererText(CellRendererMode mode) {
+ this(mode, false);
+ }
+
+ /**
+ * Create a new renderer of the given mode.
+ *
+ * @param mode
+ * the renderer mode, cannot be NULL
+ */
+ public TTableCellRendererText(CellRendererMode mode,
+ boolean rightAlign) {
+ super(mode);
+
+ this.rightAlign = rightAlign;
+ }
+
+ @Override
+ public void renderTableCell(TTable table, Object value, int rowIndex,
+ int colIndex, int y) {
+
+ int xOffset = getXOffset(table, colIndex);
+ TTableColumn tcol = table.getColumns().get(colIndex);
+ String data = asText(value, tcol.getWidth(), rightAlign);
+
+ if (!data.isEmpty()) {
+ boolean isSelected = table.getSelectedRow() == rowIndex;
+ boolean hasFocus = table.isAbsoluteActive();
+ CellAttributes color = getCellAttributes(table.getWindow()
+ .getApplication().getTheme(), isSelected, hasFocus);
+ table.getScreen().putStringXY(xOffset, y, data, color);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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 java.util.HashMap;
+import java.util.Map;
+
+import jexer.TLabel;
+import jexer.TWidget;
+
+/**
+ * A simple {@link TTableCellRenderer} that display the values within a
+ * {@link TLabel}.
+ * <p>
+ * It supports a few different modes, see
+ * {@link TTableSimpleTextCellRenderer.CellRendererMode}.
+ *
+ * @author niki
+ */
+public class TTableCellRendererWidget extends TTableCellRenderer {
+ private boolean rightAlign;
+ private Map<String, TWidget> widgets = new HashMap<String, TWidget>();
+
+ /**
+ * Create a new renderer for normal text mode.
+ */
+ public TTableCellRendererWidget() {
+ this(CellRendererMode.NORMAL);
+ }
+
+ /**
+ * Create a new renderer of the given mode.
+ *
+ * @param mode
+ * the renderer mode
+ */
+ public TTableCellRendererWidget(CellRendererMode mode) {
+ this(mode, false);
+ }
+
+ /**
+ * Create a new renderer of the given mode.
+ *
+ * @param mode
+ * the renderer mode, cannot be NULL
+ */
+ public TTableCellRendererWidget(CellRendererMode mode, boolean rightAlign) {
+ super(mode);
+
+ this.rightAlign = rightAlign;
+ }
+
+ @Override
+ public void renderTableCell(TTable table, Object value, int rowIndex,
+ int colIndex, int y) {
+
+ String wkey = "[Row " + y + " " + getMode() + "]";
+ TWidget widget = widgets.get(wkey);
+
+ TTableColumn tcol = table.getColumns().get(colIndex);
+ boolean isSelected = table.getSelectedRow() == rowIndex;
+ boolean hasFocus = table.isAbsoluteActive();
+ int width = tcol.getWidth();
+
+ int xOffset = getXOffset(table, colIndex);
+
+ if (widget != null
+ && !updateTableCellRendererComponent(widget, value, isSelected,
+ hasFocus, y, xOffset, width)) {
+ table.removeChild(widget);
+ widget = null;
+ }
+
+ if (widget == null) {
+ widget = getTableCellRendererComponent(table, value, isSelected,
+ hasFocus, y, xOffset, width);
+ }
+
+ widgets.put(wkey, widget);
+ }
+
+ /**
+ * Create a new {@link TWidget} to represent the given value.
+ *
+ * @param table
+ * the parent {@link TTable}
+ * @param value
+ * the value to represent
+ * @param isSelected
+ * TRUE if selected
+ * @param hasFocus
+ * TRUE if focused
+ * @param row
+ * the row to draw it at
+ * @param column
+ * the column to draw it at
+ * @param width
+ * the width of the control
+ *
+ * @return the widget
+ */
+ protected TWidget getTableCellRendererComponent(TTable table, Object value,
+ boolean isSelected, boolean hasFocus, int row, int column, int width) {
+ return new TLabel(table, asText(value, width, rightAlign), column, row,
+ getColorKey(isSelected, hasFocus), false);
+ }
+
+ /**
+ * Update the content of the widget if at all possible.
+ *
+ * @param component
+ * the component to update
+ * @param value
+ * the value to represent
+ * @param isSelected
+ * TRUE if selected
+ * @param hasFocus
+ * TRUE if focused
+ * @param row
+ * the row to draw it at
+ * @param column
+ * the column to draw it at
+ * @param width
+ * the width of the control
+ *
+ * @return TRUE if the operation was possible, FALSE if it failed
+ */
+ protected boolean updateTableCellRendererComponent(TWidget component,
+ Object value, boolean isSelected, boolean hasFocus, int row,
+ int column, int width) {
+
+ if (component instanceof TLabel) {
+ TLabel widget = (TLabel) component;
+ widget.setLabel(asText(value, width, rightAlign));
+ widget.setColorKey(getColorKey(isSelected, hasFocus));
+ widget.setWidth(width);
+ widget.setX(column);
+ widget.setY(row);
+ return true;
+ }
+
+ return false;
+ }
+}
--- /dev/null
+package be.nikiroo.jexer;
+
+import javax.swing.table.TableModel;
+
+import be.nikiroo.jexer.TTableCellRenderer.CellRendererMode;
+
+public class TTableColumn {
+ static private TTableCellRenderer defaultrenderer = new TTableCellRendererText(
+ CellRendererMode.NORMAL);
+
+ private TableModel model;
+ private int modelIndex;
+ private int width;
+ private boolean forcedWidth;
+
+ private TTableCellRenderer renderer;
+
+ /** The auto-computed width of the column (the width of the largest value) */
+ private int autoWidth;
+
+ private Object headerValue;
+
+ public TTableColumn(int modelIndex) {
+ this(modelIndex, null);
+ }
+
+ public TTableColumn(int modelIndex, String colName) {
+ this(modelIndex, colName, null);
+ }
+
+ // set the width and preferred with the the max data size
+ public TTableColumn(int modelIndex, Object colValue, TableModel model) {
+ this.model = model;
+ this.modelIndex = modelIndex;
+
+ reflowData();
+
+ if (colValue != null) {
+ setHeaderValue(colValue);
+ }
+ }
+
+ // never null
+ public TTableCellRenderer getRenderer() {
+ return renderer != null ? renderer : defaultrenderer;
+ }
+
+ public void setCellRenderer(TTableCellRenderer renderer) {
+ this.renderer = renderer;
+ }
+
+ /**
+ * Recompute whatever data is displayed by this widget.
+ * <p>
+ * Will just update the sizes in this case.
+ */
+ public void reflowData() {
+ if (model != null) {
+ int maxDataSize = 0;
+ for (int i = 0; i < model.getRowCount(); i++) {
+ maxDataSize = Math.max(
+ maxDataSize,
+ getRenderer().getWidthOf(
+ model.getValueAt(i, modelIndex)));
+ }
+
+ autoWidth = maxDataSize;
+ if (!forcedWidth) {
+ setWidth(maxDataSize);
+ }
+ } else {
+ autoWidth = 0;
+ forcedWidth = false;
+ width = 0;
+ }
+ }
+
+ public int getModelIndex() {
+ return modelIndex;
+ }
+
+ /**
+ * The actual size of the column. This can be auto-computed in some cases.
+ *
+ * @return the width (never < 0)
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * Set the actual size of the column or -1 for auto size.
+ *
+ * @param width
+ * the width (or -1 for auto)
+ */
+ public void setWidth(int width) {
+ forcedWidth = width >= 0;
+
+ if (forcedWidth) {
+ this.width = width;
+ } else {
+ this.width = autoWidth;
+ }
+ }
+
+ /**
+ * The width was forced by the user (using
+ * {@link TTableColumn#setWidth(int)} with a positive value).
+ *
+ * @return TRUE if it was
+ */
+ public boolean isForcedWidth() {
+ return forcedWidth;
+ }
+
+ // not an actual forced width, but does change the width return
+ void expandWidthTo(int width) {
+ this.width = width;
+ }
+
+ public Object getHeaderValue() {
+ return headerValue;
+ }
+
+ public void setHeaderValue(Object headerValue) {
+ this.headerValue = headerValue;
+ }
+}
--- /dev/null
+package be.nikiroo.jexer;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+public class TTableLine implements List<String> {
+ //TODO: in TTable: default to header of size 1
+ private List<String> list;
+
+ public TTableLine(List<String> list) {
+ this.list = list;
+ }
+
+ // TODO: override this and the rest shall follow
+ protected List<String> getList() {
+ return list;
+ }
+
+ @Override
+ public int size() {
+ return getList().size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return getList().isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return getList().contains(o);
+ }
+
+ @Override
+ public Iterator<String> iterator() {
+ return getList().iterator();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return getList().toArray();
+ }
+
+ @Override
+ public <T> T[] toArray(T[] a) {
+ return getList().toArray(a);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return getList().containsAll(c);
+ }
+
+ @Override
+ public String get(int index) {
+ return getList().get(index);
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return getList().indexOf(o);
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return getList().lastIndexOf(o);
+ }
+
+ @Override
+ public List<String> subList(int fromIndex, int toIndex) {
+ return getList().subList(fromIndex, toIndex);
+ }
+
+ @Override
+ public ListIterator<String> listIterator() {
+ return getList().listIterator();
+ }
+
+ @Override
+ public ListIterator<String> listIterator(int index) {
+ return getList().listIterator(index);
+ }
+
+ @Override
+ public boolean add(String e) {
+ throw new UnsupportedOperationException("Read-only collection");
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ throw new UnsupportedOperationException("Read-only collection");
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends String> c) {
+ throw new UnsupportedOperationException("Read-only collection");
+ }
+
+ @Override
+ public boolean addAll(int index, Collection<? extends String> c) {
+ throw new UnsupportedOperationException("Read-only collection");
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c) {
+ throw new UnsupportedOperationException("Read-only collection");
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c) {
+ throw new UnsupportedOperationException("Read-only collection");
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException("Read-only collection");
+ }
+
+ @Override
+ public String set(int index, String element) {
+ throw new UnsupportedOperationException("Read-only collection");
+ }
+
+ @Override
+ public void add(int index, String element) {
+ throw new UnsupportedOperationException("Read-only collection");
+ }
+
+ @Override
+ public String remove(int index) {
+ throw new UnsupportedOperationException("Read-only collection");
+ }
+}
--- /dev/null
+/*
+ * 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import javax.swing.event.TableModelListener;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableModel;
+
+/**
+ * The model of a {@link TTable}. It contains the data of the table and allows
+ * you access to it.
+ * <p>
+ * Note that you don't need to send it the representation of the data, but the
+ * data itself; {@link TTableCellRenderer} is the class responsible of
+ * representing that data (you can change the headers renderer on a
+ * {@link TTable} and the cells renderer on each of its {@link TTableColumn}).
+ * <p>
+ * It works in a similar way to the Java Swing version of it.
+ *
+ * @author niki
+ */
+public class TTableModel implements TableModel {
+ private TableModel model;
+
+ /**
+ * Create a new {@link TTableModel} with the given data inside.
+ *
+ * @param data
+ * the data
+ */
+ public TTableModel(Object[][] data) {
+ this(convert(data));
+ }
+
+ /**
+ * Create a new {@link TTableModel} with the given data inside.
+ *
+ * @param data
+ * the data
+ */
+ public TTableModel(
+ final Collection<? extends Collection<? extends Object>> data) {
+
+ int maxItemsPerRow = 0;
+ for (Collection<? extends Object> rowOfData : data) {
+ maxItemsPerRow = Math.max(maxItemsPerRow, rowOfData.size());
+ }
+
+ int i = 0;
+ final Object[][] odata = new Object[data.size()][maxItemsPerRow];
+ for (Collection<? extends Object> rowOfData : data) {
+ odata[i] = new String[maxItemsPerRow];
+ int j = 0;
+ for (Object pieceOfData : rowOfData) {
+ odata[i][j] = pieceOfData;
+ j++;
+ }
+ i++;
+ }
+
+ final int maxItemsPerRowFinal = maxItemsPerRow;
+ this.model = new AbstractTableModel() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ return odata[rowIndex][columnIndex];
+ }
+
+ @Override
+ public int getRowCount() {
+ return odata.length;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return maxItemsPerRowFinal;
+ }
+ };
+ }
+
+ @Override
+ public int getRowCount() {
+ return model.getRowCount();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return model.getColumnCount();
+ }
+
+ @Override
+ public String getColumnName(int columnIndex) {
+ return model.getColumnName(columnIndex);
+ }
+
+ @Override
+ public Class<?> getColumnClass(int columnIndex) {
+ return model.getColumnClass(columnIndex);
+ }
+
+ @Override
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ return model.isCellEditable(rowIndex, columnIndex);
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ return model.getValueAt(rowIndex, columnIndex);
+ }
+
+ @Override
+ public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+ model.setValueAt(aValue, rowIndex, columnIndex);
+ }
+
+ @Override
+ public void addTableModelListener(TableModelListener l) {
+ model.addTableModelListener(l);
+ }
+
+ @Override
+ public void removeTableModelListener(TableModelListener l) {
+ model.removeTableModelListener(l);
+ }
+
+ /**
+ * Helper method to convert an array to a collection.
+ *
+ * @param <T>
+ *
+ * @param data
+ * the data
+ *
+ * @return the data in another format
+ */
+ static <T> Collection<Collection<T>> convert(T[][] data) {
+ Collection<Collection<T>> dataCollection = new ArrayList<Collection<T>>(
+ data.length);
+ for (T pieceOfData[] : data) {
+ dataCollection.add(Arrays.asList(pieceOfData));
+ }
+
+ return dataCollection;
+ }
+}