2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 David "Niki" ROULET
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author David ROULET [niki@nikiroo.be]
29 package be
.nikiroo
.jexer
;
31 import java
.util
.ArrayList
;
32 import java
.util
.Collection
;
33 import java
.util
.List
;
35 import javax
.swing
.table
.TableModel
;
37 import be
.nikiroo
.jexer
.TTableCellRenderer
.CellRendererMode
;
40 import jexer
.bits
.CellAttributes
;
43 * A table widget to display and browse through tabular data.
45 * Currently, you can only select a line (a row) at a time, but the data you
46 * present is still tabular. You also access the data in a tabular way (by
47 * <tt>(raw,column)</tt>).
51 public class TTable
extends TBrowsableWidget
{
52 // Default renderers use text mode
53 static private TTableCellRenderer defaultSeparatorRenderer
= new TTableCellRendererText(
54 CellRendererMode
.SEPARATOR
);
55 static private TTableCellRenderer defaultHeaderRenderer
= new TTableCellRendererText(
56 CellRendererMode
.HEADER
);
57 static private TTableCellRenderer defaultHeaderSeparatorRenderer
= new TTableCellRendererText(
58 CellRendererMode
.HEADER_SEPARATOR
);
60 private boolean showHeader
;
62 private List
<TTableColumn
> columns
= new ArrayList
<TTableColumn
>();
63 private TableModel model
;
65 private int selectedColumn
;
67 private TTableCellRenderer separatorRenderer
;
68 private TTableCellRenderer headerRenderer
;
69 private TTableCellRenderer headerSeparatorRenderer
;
72 * The action to perform when the user selects an item (clicks or enter).
74 private TAction enterAction
= null;
77 * The action to perform when the user navigates with keyboard.
79 private TAction moveAction
= null;
82 * Create a new {@link TTable}.
91 * the width of the {@link TTable}
93 * the height of the {@link TTable}
95 * an action to call when a cell is selected
97 * an action to call when the currently active cell is changed
99 public TTable(TWidget parent
, int x
, int y
, int width
, int height
,
100 final TAction enterAction
, final TAction moveAction
) {
101 this(parent
, x
, y
, width
, height
, enterAction
, moveAction
, null, false);
105 * Create a new {@link TTable}.
114 * the width of the {@link TTable}
116 * the height of the {@link TTable}
118 * an action to call when a cell is selected
120 * an action to call when the currently active cell is changed
122 * the headers of the {@link TTable}
124 * TRUE to show the headers on screen
126 public TTable(TWidget parent
, int x
, int y
, int width
, int height
,
127 final TAction enterAction
, final TAction moveAction
,
128 List
<?
extends Object
> headers
, boolean showHeaders
) {
129 super(parent
, x
, y
, width
, height
);
131 this.model
= new TTableModel(new Object
[][] {});
133 this.selectedColumn
= -1;
135 setHeaders(headers
, showHeaders
);
137 this.enterAction
= enterAction
;
138 this.moveAction
= moveAction
;
144 * The data model (containing the actual data) used by this {@link TTable},
145 * as with the usual Swing tables.
149 public TableModel
getModel() {
154 * The data model (containing the actual data) used by this {@link TTable},
155 * as with the usual Swing tables.
157 * Will reset all the rendering cells.
162 public void setModel(TableModel model
) {
168 * The columns used by this {@link TTable} (you need to access them if you
169 * want to change the way they are rendered, for instance, or their size).
171 * @return the columns
173 public List
<TTableColumn
> getColumns() {
178 * The {@link TTableCellRenderer} used by the separators (one separator
179 * between two data columns).
181 * @return the renderer, or the default one if none is set (never NULL)
183 public TTableCellRenderer
getSeparatorRenderer() {
184 return separatorRenderer
!= null ? separatorRenderer
185 : defaultSeparatorRenderer
;
189 * The {@link TTableCellRenderer} used by the separators (one separator
190 * between two data columns).
192 * @param separatorRenderer
193 * the new renderer, or NULL to use the default renderer
195 public void setSeparatorRenderer(TTableCellRenderer separatorRenderer
) {
196 this.separatorRenderer
= separatorRenderer
;
200 * The {@link TTableCellRenderer} used by the headers (if
201 * {@link TTable#isShowHeader()} is enabled, the first line represents the
202 * headers with the column names).
204 * @return the renderer, or the default one if none is set (never NULL)
206 public TTableCellRenderer
getHeaderRenderer() {
207 return headerRenderer
!= null ? headerRenderer
: defaultHeaderRenderer
;
211 * The {@link TTableCellRenderer} used by the headers (if
212 * {@link TTable#isShowHeader()} is enabled, the first line represents the
213 * headers with the column names).
215 * @param headerRenderer
216 * the new renderer, or NULL to use the default renderer
218 public void setHeaderRenderer(TTableCellRenderer headerRenderer
) {
219 this.headerRenderer
= headerRenderer
;
223 * The {@link TTableCellRenderer} to use on separators in header lines (see
224 * the related methods to understand what each of them is).
226 * @return the renderer, or the default one if none is set (never NULL)
228 public TTableCellRenderer
getHeaderSeparatorRenderer() {
229 return headerSeparatorRenderer
!= null ? headerSeparatorRenderer
230 : defaultHeaderSeparatorRenderer
;
234 * The {@link TTableCellRenderer} to use on separators in header lines (see
235 * the related methods to understand what each of them is).
237 * @param headerSeparatorRenderer
238 * the new renderer, or NULL to use the default renderer
240 public void setHeaderSeparatorRenderer(
241 TTableCellRenderer headerSeparatorRenderer
) {
242 this.headerSeparatorRenderer
= headerSeparatorRenderer
;
246 * Show the header row on this {@link TTable}.
248 * @return TRUE if we show them
250 public boolean isShowHeader() {
255 * Show the header row on this {@link TTable}.
260 public void setShowHeader(boolean showHeader
) {
261 this.showHeader
= showHeader
;
262 setYOffset(showHeader ?
2 : 0);
267 * Change the headers of the table.
269 * Note that this method is a convenience method that will create columns of
270 * the corresponding names and set them. As such, the previous columns if
271 * any will be replaced.
276 public void setHeaders(List
<?
extends Object
> headers
) {
277 setHeaders(headers
, showHeader
);
281 * Change the headers of the table.
283 * Note that this method is a convenience method that will create columns of
284 * the corresponding names and set them in the same order. As such, the
285 * previous columns if any will be replaced.
290 * TRUE to show them on screen
292 public void setHeaders(List
<?
extends Object
> headers
, boolean showHeader
) {
293 if (headers
== null) {
294 headers
= new ArrayList
<Object
>();
298 this.columns
= new ArrayList
<TTableColumn
>();
299 for (Object header
: headers
) {
300 this.columns
.add(new TTableColumn(i
++, header
, getModel()));
303 setShowHeader(showHeader
);
307 * Set the data and create a new {@link TTableModel} for them.
310 * the data to set into this table, as an array of rows, that is,
311 * an array of arrays of values
314 public void setRowData(Object
[][] data
) {
315 setRowData(TTableModel
.convert(data
));
319 * Set the data and create a new {@link TTableModel} for them.
322 * the data to set into this table, as a collection of rows, that
323 * is, a collection of collections of values
325 public void setRowData(
326 final Collection
<?
extends Collection
<?
extends Object
>> data
) {
327 setModel(new TTableModel(data
));
331 * The currently selected cell.
335 public Object
getSelectedCell() {
336 int selectedRow
= getSelectedRow();
337 if (selectedRow
>= 0 && selectedColumn
>= 0) {
338 return model
.getValueAt(selectedRow
, selectedColumn
);
345 public int getRowCount() {
349 return model
.getRowCount();
353 public int getColumnCount() {
357 return model
.getColumnCount();
361 public void dispatchEnter(int selectedRow
) {
362 super.dispatchEnter(selectedRow
);
363 if (enterAction
!= null) {
369 public void dispatchMove(int fromRow
, int toRow
) {
370 super.dispatchMove(fromRow
, toRow
);
371 if (moveAction
!= null) {
377 * Clear the content of the {@link TTable}.
379 * It will not affect the headers.
381 * You may want to call {@link TTable#reflowData()} when done to see the
384 public void clear() {
387 setModel(new TTableModel(new Object
[][] {}));
391 public void reflowData() {
394 int lastAutoColumn
= -1;
398 for (TTableColumn tcol
: columns
) {
401 if (!tcol
.isForcedWidth()) {
405 rowWidth
+= tcol
.getWidth();
410 if (!columns
.isEmpty()) {
411 rowWidth
+= (i
- 1) * getSeparatorRenderer().getWidthOf(null);
413 int extraWidth
= getWidth() - rowWidth
;
414 if (extraWidth
> 0) {
415 if (lastAutoColumn
< 0) {
416 lastAutoColumn
= columns
.size() - 1;
418 TTableColumn tcol
= columns
.get(lastAutoColumn
);
419 tcol
.expandWidthTo(tcol
.getWidth() + extraWidth
);
420 rowWidth
+= extraWidth
;
427 int begin
= vScroller
.getValue();
428 int y
= this.showHeader ?
2 : 0;
431 CellAttributes colorHeaders
= getHeaderRenderer()
432 .getCellAttributes(getTheme(), false, isAbsoluteActive());
434 String formatString
= "%-" + Integer
.toString(getWidth()) + "s";
435 String data
= String
.format(formatString
, "");
436 getScreen().putStringXY(0, 1, data
, colorHeaders
);
439 // draw the actual rows until no more,
440 // then pad the rest with blank rows
441 for (int i
= begin
; i
< getRowCount(); i
++) {
445 // -2: window borders
446 if (y
>= getHeight() - 2 - getHorizontalScroller().getHeight()) {
451 CellAttributes emptyRowColor
= getSeparatorRenderer()
452 .getCellAttributes(getTheme(), false, isAbsoluteActive());
453 for (int i
= getRowCount(); i
< getHeight(); i
++) {
454 getScreen().hLineXY(0, y
, getWidth() - 1, ' ', emptyRowColor
);
460 protected int getVirtualWidth() {
463 if (getColumns() != null) {
464 for (TTableColumn tcol
: getColumns()) {
465 width
+= tcol
.getWidth();
468 if (getColumnCount() > 0) {
469 width
+= (getColumnCount() - 1)
470 * getSeparatorRenderer().getWidthOf(null);
478 protected int getVirtualHeight() {
479 // TODO: allow changing the height of one row
480 return (showHeader ?
2 : 0) + (getRowCount() * 1);
484 * Draw the given row (it <b>MUST</b> exist) at the specified index and
488 * the index of the row to draw or -1 for the headers
492 private void drawRow(int rowIndex
, int y
) {
493 for (int i
= 0; i
< getColumnCount(); i
++) {
494 TTableColumn tcol
= columns
.get(i
);
497 value
= tcol
.getHeaderValue();
499 value
= model
.getValueAt(rowIndex
, tcol
.getModelIndex());
503 TTableCellRenderer sep
= rowIndex
< 0 ?
getHeaderSeparatorRenderer()
504 : getSeparatorRenderer();
505 sep
.renderTableCell(this, null, rowIndex
, i
- 1, y
);
510 .renderTableCell(this, value
, rowIndex
, i
, y
);
512 tcol
.getRenderer().renderTableCell(this, value
, rowIndex
, i
, y
);