1 package com
.googlecode
.lanterna
.gui2
.table
;
3 import com
.googlecode
.lanterna
.*;
4 import com
.googlecode
.lanterna
.gui2
.Direction
;
5 import com
.googlecode
.lanterna
.gui2
.ScrollBar
;
6 import com
.googlecode
.lanterna
.gui2
.TextGUIGraphics
;
8 import java
.util
.ArrayList
;
12 * Default implementation of {@code TableRenderer}
13 * @param <V> Type of data stored in each table cell
16 public class DefaultTableRenderer
<V
> implements TableRenderer
<V
> {
18 private final ScrollBar verticalScrollBar
;
19 private final ScrollBar horizontalScrollBar
;
21 private TableCellBorderStyle headerVerticalBorderStyle
;
22 private TableCellBorderStyle headerHorizontalBorderStyle
;
23 private TableCellBorderStyle cellVerticalBorderStyle
;
24 private TableCellBorderStyle cellHorizontalBorderStyle
;
26 //So that we don't have to recalculate the size every time. This still isn't optimal but shouganai.
27 private TerminalSize cachedSize
;
28 private List
<Integer
> columnSizes
;
29 private List
<Integer
> rowSizes
;
30 private int headerSizeInRows
;
35 public DefaultTableRenderer() {
36 verticalScrollBar
= new ScrollBar(Direction
.VERTICAL
);
37 horizontalScrollBar
= new ScrollBar(Direction
.HORIZONTAL
);
39 headerVerticalBorderStyle
= TableCellBorderStyle
.None
;
40 headerHorizontalBorderStyle
= TableCellBorderStyle
.EmptySpace
;
41 cellVerticalBorderStyle
= TableCellBorderStyle
.None
;
42 cellHorizontalBorderStyle
= TableCellBorderStyle
.EmptySpace
;
46 columnSizes
= new ArrayList
<Integer
>();
47 rowSizes
= new ArrayList
<Integer
>();
52 * Sets the style to be used when separating the table header row from the actual "data" cells below. This will
53 * cause a new line to be added under the header labels, unless set to {@code TableCellBorderStyle.None}.
55 * @param headerVerticalBorderStyle Style to use to separate Table header from body
57 public void setHeaderVerticalBorderStyle(TableCellBorderStyle headerVerticalBorderStyle
) {
58 this.headerVerticalBorderStyle
= headerVerticalBorderStyle
;
62 * Sets the style to be used when separating the table header labels from each other. This will cause a new
63 * column to be added in between each label, unless set to {@code TableCellBorderStyle.None}.
65 * @param headerHorizontalBorderStyle Style to use when separating header columns horizontally
67 public void setHeaderHorizontalBorderStyle(TableCellBorderStyle headerHorizontalBorderStyle
) {
68 this.headerHorizontalBorderStyle
= headerHorizontalBorderStyle
;
72 * Sets the style to be used when vertically separating table cells from each other. This will cause a new line
73 * to be added between every row, unless set to {@code TableCellBorderStyle.None}.
75 * @param cellVerticalBorderStyle Style to use to separate table cells vertically
77 public void setCellVerticalBorderStyle(TableCellBorderStyle cellVerticalBorderStyle
) {
78 this.cellVerticalBorderStyle
= cellVerticalBorderStyle
;
82 * Sets the style to be used when horizontally separating table cells from each other. This will cause a new
83 * column to be added between every row, unless set to {@code TableCellBorderStyle.None}.
85 * @param cellHorizontalBorderStyle Style to use to separate table cells horizontally
87 public void setCellHorizontalBorderStyle(TableCellBorderStyle cellHorizontalBorderStyle
) {
88 this.cellHorizontalBorderStyle
= cellHorizontalBorderStyle
;
91 private boolean isHorizontallySpaced() {
92 return headerHorizontalBorderStyle
!= TableCellBorderStyle
.None
||
93 cellHorizontalBorderStyle
!= TableCellBorderStyle
.None
;
97 public TerminalSize
getPreferredSize(Table
<V
> table
) {
98 //Quick bypass if the table hasn't changed
99 if(!table
.isInvalid() && cachedSize
!= null) {
103 TableModel
<V
> tableModel
= table
.getTableModel();
104 int viewLeftColumn
= table
.getViewLeftColumn();
105 int viewTopRow
= table
.getViewTopRow();
106 int visibleColumns
= table
.getVisibleColumns();
107 int visibleRows
= table
.getVisibleRows();
108 List
<List
<V
>> rows
= tableModel
.getRows();
109 List
<String
> columnHeaders
= tableModel
.getColumnLabels();
110 TableHeaderRenderer
<V
> tableHeaderRenderer
= table
.getTableHeaderRenderer();
111 TableCellRenderer
<V
> tableCellRenderer
= table
.getTableCellRenderer();
113 if(visibleColumns
== 0) {
114 visibleColumns
= tableModel
.getColumnCount();
116 if(visibleRows
== 0) {
117 visibleRows
= tableModel
.getRowCount();
123 if(tableModel
.getColumnCount() == 0) {
124 return TerminalSize
.ZERO
;
127 for(int rowIndex
= 0; rowIndex
< rows
.size(); rowIndex
++) {
128 List
<V
> row
= rows
.get(rowIndex
);
129 for(int columnIndex
= viewLeftColumn
; columnIndex
< Math
.min(row
.size(), viewLeftColumn
+ visibleColumns
); columnIndex
++) {
130 V cell
= row
.get(columnIndex
);
131 int columnSize
= tableCellRenderer
.getPreferredSize(table
, cell
, columnIndex
, rowIndex
).getColumns();
132 int listOffset
= columnIndex
- viewLeftColumn
;
133 if(columnSizes
.size() == listOffset
) {
134 columnSizes
.add(columnSize
);
137 if(columnSizes
.get(listOffset
) < columnSize
) {
138 columnSizes
.set(listOffset
, columnSize
);
143 //Do the headers too, on the first iteration
145 for(int columnIndex
= viewLeftColumn
; columnIndex
< Math
.min(row
.size(), viewLeftColumn
+ visibleColumns
); columnIndex
++) {
146 int columnSize
= tableHeaderRenderer
.getPreferredSize(table
, columnHeaders
.get(columnIndex
), columnIndex
).getColumns();
147 int listOffset
= columnIndex
- viewLeftColumn
;
148 if(columnSizes
.size() == listOffset
) {
149 columnSizes
.add(columnSize
);
152 if(columnSizes
.get(listOffset
) < columnSize
) {
153 columnSizes
.set(listOffset
, columnSize
);
160 for(int columnIndex
= 0; columnIndex
< columnHeaders
.size(); columnIndex
++) {
161 for(int rowIndex
= viewTopRow
; rowIndex
< Math
.min(rows
.size(), viewTopRow
+ visibleRows
); rowIndex
++) {
162 V cell
= rows
.get(rowIndex
).get(columnIndex
);
163 int rowSize
= tableCellRenderer
.getPreferredSize(table
, cell
, columnIndex
, rowIndex
).getRows();
164 int listOffset
= rowIndex
- viewTopRow
;
165 if(rowSizes
.size() == listOffset
) {
166 rowSizes
.add(rowSize
);
169 if(rowSizes
.get(listOffset
) < rowSize
) {
170 rowSizes
.set(listOffset
, rowSize
);
176 int preferredRowSize
= 0;
177 int preferredColumnSize
= 0;
178 for(int size
: columnSizes
) {
179 preferredColumnSize
+= size
;
181 for(int size
: rowSizes
) {
182 preferredRowSize
+= size
;
185 headerSizeInRows
= 0;
186 for(int columnIndex
= 0; columnIndex
< columnHeaders
.size(); columnIndex
++) {
187 int headerRows
= tableHeaderRenderer
.getPreferredSize(table
, columnHeaders
.get(columnIndex
), columnIndex
).getRows();
188 if(headerSizeInRows
< headerRows
) {
189 headerSizeInRows
= headerRows
;
192 preferredRowSize
+= headerSizeInRows
;
194 if(headerVerticalBorderStyle
!= TableCellBorderStyle
.None
) {
195 preferredRowSize
++; //Spacing between header and body
197 if(cellVerticalBorderStyle
!= TableCellBorderStyle
.None
) {
198 if(!rows
.isEmpty()) {
199 preferredRowSize
+= Math
.min(rows
.size(), visibleRows
) - 1; //Vertical space between cells
202 if(isHorizontallySpaced()) {
203 if(!columnHeaders
.isEmpty()) {
204 preferredColumnSize
+= Math
.min(tableModel
.getColumnCount(), visibleColumns
) - 1; //Spacing between the columns
208 //Add on space taken by scrollbars (if needed)
209 if(visibleRows
< rows
.size()) {
210 preferredColumnSize
++;
212 if(visibleColumns
< tableModel
.getColumnCount()) {
216 cachedSize
= new TerminalSize(preferredColumnSize
, preferredRowSize
);
221 public TerminalPosition
getCursorLocation(Table
<V
> component
) {
226 public void drawComponent(TextGUIGraphics graphics
, Table
<V
> table
) {
228 TerminalSize area
= graphics
.getSize();
231 if(area
.getRows() == 0 || area
.getColumns() == 0) {
235 int topPosition
= drawHeader(graphics
, table
);
236 drawRows(graphics
, table
, topPosition
);
239 private int drawHeader(TextGUIGraphics graphics
, Table
<V
> table
) {
240 TableHeaderRenderer
<V
> tableHeaderRenderer
= table
.getTableHeaderRenderer();
241 List
<String
> headers
= table
.getTableModel().getColumnLabels();
242 int viewLeftColumn
= table
.getViewLeftColumn();
243 int visibleColumns
= table
.getVisibleColumns();
244 if(visibleColumns
== 0) {
245 visibleColumns
= table
.getTableModel().getColumnCount();
248 int leftPosition
= 0;
249 int endColumnIndex
= Math
.min(headers
.size(), viewLeftColumn
+ visibleColumns
);
250 for(int index
= viewLeftColumn
; index
< endColumnIndex
; index
++) {
251 String label
= headers
.get(index
);
252 TerminalSize size
= new TerminalSize(columnSizes
.get(index
- viewLeftColumn
), headerSizeInRows
);
253 tableHeaderRenderer
.drawHeader(table
, label
, index
, graphics
.newTextGraphics(new TerminalPosition(leftPosition
, 0), size
));
254 leftPosition
+= size
.getColumns();
255 if(headerHorizontalBorderStyle
!= TableCellBorderStyle
.None
&& index
< (endColumnIndex
- 1)) {
256 graphics
.applyThemeStyle(graphics
.getThemeDefinition(Table
.class).getNormal());
257 graphics
.setCharacter(leftPosition
, 0, getVerticalCharacter(headerHorizontalBorderStyle
));
261 topPosition
+= headerSizeInRows
;
263 if(headerVerticalBorderStyle
!= TableCellBorderStyle
.None
) {
265 graphics
.applyThemeStyle(graphics
.getThemeDefinition(Table
.class).getNormal());
266 for(int i
= 0; i
< columnSizes
.size(); i
++) {
268 graphics
.setCharacter(
271 getJunctionCharacter(
272 headerVerticalBorderStyle
,
273 headerHorizontalBorderStyle
,
274 cellHorizontalBorderStyle
));
277 int columnWidth
= columnSizes
.get(i
);
278 graphics
.drawLine(leftPosition
, topPosition
, leftPosition
+ columnWidth
- 1, topPosition
, getHorizontalCharacter(headerVerticalBorderStyle
));
279 leftPosition
+= columnWidth
;
281 //Expand out the line in case the area is bigger
282 if(leftPosition
< graphics
.getSize().getColumns()) {
283 graphics
.drawLine(leftPosition
, topPosition
, graphics
.getSize().getColumns() - 1, topPosition
, getHorizontalCharacter(headerVerticalBorderStyle
));
290 private void drawRows(TextGUIGraphics graphics
, Table
<V
> table
, int topPosition
) {
291 TerminalSize area
= graphics
.getSize();
292 TableCellRenderer
<V
> tableCellRenderer
= table
.getTableCellRenderer();
293 TableModel
<V
> tableModel
= table
.getTableModel();
294 List
<List
<V
>> rows
= tableModel
.getRows();
295 int viewTopRow
= table
.getViewTopRow();
296 int viewLeftColumn
= table
.getViewLeftColumn();
297 int visibleRows
= table
.getVisibleRows();
298 int visibleColumns
= table
.getVisibleColumns();
299 if(visibleColumns
== 0) {
300 visibleColumns
= tableModel
.getColumnCount();
302 if(visibleRows
== 0) {
303 visibleRows
= tableModel
.getRowCount();
306 //Exit if there are no rows
311 //Draw scrollbars (if needed)
312 if(visibleRows
< rows
.size()) {
313 TerminalSize verticalScrollBarPreferredSize
= verticalScrollBar
.getPreferredSize();
314 int scrollBarHeight
= graphics
.getSize().getRows() - topPosition
;
315 if(visibleColumns
< tableModel
.getColumnCount()) {
318 verticalScrollBar
.setPosition(new TerminalPosition(graphics
.getSize().getColumns() - verticalScrollBarPreferredSize
.getColumns(), topPosition
));
319 verticalScrollBar
.setSize(verticalScrollBarPreferredSize
.withRows(scrollBarHeight
));
320 verticalScrollBar
.setScrollMaximum(rows
.size());
321 verticalScrollBar
.setViewSize(visibleRows
);
322 verticalScrollBar
.setScrollPosition(viewTopRow
);
323 verticalScrollBar
.draw(graphics
.newTextGraphics(verticalScrollBar
.getPosition(), verticalScrollBar
.getSize()));
324 graphics
= graphics
.newTextGraphics(TerminalPosition
.TOP_LEFT_CORNER
, graphics
.getSize().withRelativeColumns(-verticalScrollBarPreferredSize
.getColumns()));
326 if(visibleColumns
< tableModel
.getColumnCount()) {
327 TerminalSize horizontalScrollBarPreferredSize
= horizontalScrollBar
.getPreferredSize();
328 int scrollBarWidth
= graphics
.getSize().getColumns();
329 horizontalScrollBar
.setPosition(new TerminalPosition(0, graphics
.getSize().getRows() - horizontalScrollBarPreferredSize
.getRows()));
330 horizontalScrollBar
.setSize(horizontalScrollBarPreferredSize
.withColumns(scrollBarWidth
));
331 horizontalScrollBar
.setScrollMaximum(tableModel
.getColumnCount());
332 horizontalScrollBar
.setViewSize(visibleColumns
);
333 horizontalScrollBar
.setScrollPosition(viewLeftColumn
);
334 horizontalScrollBar
.draw(graphics
.newTextGraphics(horizontalScrollBar
.getPosition(), horizontalScrollBar
.getSize()));
335 graphics
= graphics
.newTextGraphics(TerminalPosition
.TOP_LEFT_CORNER
, graphics
.getSize().withRelativeRows(-horizontalScrollBarPreferredSize
.getRows()));
339 for(int rowIndex
= viewTopRow
; rowIndex
< Math
.min(viewTopRow
+ visibleRows
, rows
.size()); rowIndex
++) {
341 List
<V
> row
= rows
.get(rowIndex
);
342 for(int columnIndex
= viewLeftColumn
; columnIndex
< Math
.min(viewLeftColumn
+ visibleColumns
, row
.size()); columnIndex
++) {
343 if(columnIndex
> viewLeftColumn
) {
344 if(table
.getSelectedRow() == rowIndex
&& !table
.isCellSelection()) {
345 if(table
.isFocused()) {
346 graphics
.applyThemeStyle(graphics
.getThemeDefinition(Table
.class).getActive());
349 graphics
.applyThemeStyle(graphics
.getThemeDefinition(Table
.class).getSelected());
353 graphics
.applyThemeStyle(graphics
.getThemeDefinition(Table
.class).getNormal());
355 graphics
.setCharacter(leftPosition
, topPosition
, getVerticalCharacter(cellHorizontalBorderStyle
));
358 V cell
= row
.get(columnIndex
);
359 TerminalPosition cellPosition
= new TerminalPosition(leftPosition
, topPosition
);
360 TerminalSize cellArea
= new TerminalSize(columnSizes
.get(columnIndex
- viewLeftColumn
), rowSizes
.get(rowIndex
- viewTopRow
));
361 tableCellRenderer
.drawCell(table
, cell
, columnIndex
, rowIndex
, graphics
.newTextGraphics(cellPosition
, cellArea
));
362 leftPosition
+= cellArea
.getColumns();
363 if(leftPosition
> area
.getColumns()) {
367 topPosition
+= rowSizes
.get(rowIndex
- viewTopRow
);
368 if(cellVerticalBorderStyle
!= TableCellBorderStyle
.None
) {
370 graphics
.applyThemeStyle(graphics
.getThemeDefinition(Table
.class).getNormal());
371 for(int i
= 0; i
< columnSizes
.size(); i
++) {
373 graphics
.setCharacter(
376 getJunctionCharacter(
377 cellVerticalBorderStyle
,
378 cellHorizontalBorderStyle
,
379 cellHorizontalBorderStyle
));
382 int columnWidth
= columnSizes
.get(i
);
383 graphics
.drawLine(leftPosition
, topPosition
, leftPosition
+ columnWidth
- 1, topPosition
, getHorizontalCharacter(cellVerticalBorderStyle
));
384 leftPosition
+= columnWidth
;
388 if(topPosition
> area
.getRows()) {
394 private char getHorizontalCharacter(TableCellBorderStyle style
) {
397 return Symbols
.SINGLE_LINE_HORIZONTAL
;
399 return Symbols
.DOUBLE_LINE_HORIZONTAL
;
405 private char getVerticalCharacter(TableCellBorderStyle style
) {
408 return Symbols
.SINGLE_LINE_VERTICAL
;
410 return Symbols
.DOUBLE_LINE_VERTICAL
;
416 private char getJunctionCharacter(TableCellBorderStyle mainStyle
, TableCellBorderStyle styleAbove
, TableCellBorderStyle styleBelow
) {
417 if(mainStyle
== TableCellBorderStyle
.SingleLine
) {
418 if(styleAbove
== TableCellBorderStyle
.SingleLine
) {
419 if(styleBelow
== TableCellBorderStyle
.SingleLine
) {
420 return Symbols
.SINGLE_LINE_CROSS
;
422 else if(styleBelow
== TableCellBorderStyle
.DoubleLine
) {
423 //There isn't any character for this, give upper side priority
424 return Symbols
.SINGLE_LINE_T_UP
;
427 return Symbols
.SINGLE_LINE_T_UP
;
430 else if(styleAbove
== TableCellBorderStyle
.DoubleLine
) {
431 if(styleBelow
== TableCellBorderStyle
.SingleLine
) {
432 //There isn't any character for this, give upper side priority
433 return Symbols
.SINGLE_LINE_T_DOUBLE_UP
;
435 else if(styleBelow
== TableCellBorderStyle
.DoubleLine
) {
436 return Symbols
.DOUBLE_LINE_VERTICAL_SINGLE_LINE_CROSS
;
439 return Symbols
.SINGLE_LINE_T_DOUBLE_UP
;
443 if(styleBelow
== TableCellBorderStyle
.SingleLine
) {
444 return Symbols
.SINGLE_LINE_T_DOWN
;
446 else if(styleBelow
== TableCellBorderStyle
.DoubleLine
) {
447 return Symbols
.SINGLE_LINE_T_DOUBLE_DOWN
;
450 return Symbols
.SINGLE_LINE_HORIZONTAL
;
454 else if(mainStyle
== TableCellBorderStyle
.DoubleLine
) {
455 if(styleAbove
== TableCellBorderStyle
.SingleLine
) {
456 if(styleBelow
== TableCellBorderStyle
.SingleLine
) {
457 return Symbols
.DOUBLE_LINE_HORIZONTAL_SINGLE_LINE_CROSS
;
459 else if(styleBelow
== TableCellBorderStyle
.DoubleLine
) {
460 //There isn't any character for this, give upper side priority
461 return Symbols
.DOUBLE_LINE_T_SINGLE_UP
;
464 return Symbols
.DOUBLE_LINE_T_SINGLE_UP
;
467 else if(styleAbove
== TableCellBorderStyle
.DoubleLine
) {
468 if(styleBelow
== TableCellBorderStyle
.SingleLine
) {
469 //There isn't any character for this, give upper side priority
470 return Symbols
.DOUBLE_LINE_T_UP
;
472 else if(styleBelow
== TableCellBorderStyle
.DoubleLine
) {
473 return Symbols
.DOUBLE_LINE_CROSS
;
476 return Symbols
.DOUBLE_LINE_T_UP
;
480 if(styleBelow
== TableCellBorderStyle
.SingleLine
) {
481 return Symbols
.DOUBLE_LINE_T_SINGLE_DOWN
;
483 else if(styleBelow
== TableCellBorderStyle
.DoubleLine
) {
484 return Symbols
.DOUBLE_LINE_T_DOWN
;
487 return Symbols
.DOUBLE_LINE_HORIZONTAL
;