package com.googlecode.lanterna.gui2.table; import com.googlecode.lanterna.*; import com.googlecode.lanterna.gui2.Direction; import com.googlecode.lanterna.gui2.ScrollBar; import com.googlecode.lanterna.gui2.TextGUIGraphics; import java.util.ArrayList; import java.util.List; /** * Default implementation of {@code TableRenderer} * @param Type of data stored in each table cell * @author Martin */ public class DefaultTableRenderer implements TableRenderer { private final ScrollBar verticalScrollBar; private final ScrollBar horizontalScrollBar; private TableCellBorderStyle headerVerticalBorderStyle; private TableCellBorderStyle headerHorizontalBorderStyle; private TableCellBorderStyle cellVerticalBorderStyle; private TableCellBorderStyle cellHorizontalBorderStyle; //So that we don't have to recalculate the size every time. This still isn't optimal but shouganai. private TerminalSize cachedSize; private List columnSizes; private List rowSizes; private int headerSizeInRows; /** * Default constructor */ public DefaultTableRenderer() { verticalScrollBar = new ScrollBar(Direction.VERTICAL); horizontalScrollBar = new ScrollBar(Direction.HORIZONTAL); headerVerticalBorderStyle = TableCellBorderStyle.None; headerHorizontalBorderStyle = TableCellBorderStyle.EmptySpace; cellVerticalBorderStyle = TableCellBorderStyle.None; cellHorizontalBorderStyle = TableCellBorderStyle.EmptySpace; cachedSize = null; columnSizes = new ArrayList(); rowSizes = new ArrayList(); headerSizeInRows = 0; } /** * Sets the style to be used when separating the table header row from the actual "data" cells below. This will * cause a new line to be added under the header labels, unless set to {@code TableCellBorderStyle.None}. * * @param headerVerticalBorderStyle Style to use to separate Table header from body */ public void setHeaderVerticalBorderStyle(TableCellBorderStyle headerVerticalBorderStyle) { this.headerVerticalBorderStyle = headerVerticalBorderStyle; } /** * Sets the style to be used when separating the table header labels from each other. This will cause a new * column to be added in between each label, unless set to {@code TableCellBorderStyle.None}. * * @param headerHorizontalBorderStyle Style to use when separating header columns horizontally */ public void setHeaderHorizontalBorderStyle(TableCellBorderStyle headerHorizontalBorderStyle) { this.headerHorizontalBorderStyle = headerHorizontalBorderStyle; } /** * Sets the style to be used when vertically separating table cells from each other. This will cause a new line * to be added between every row, unless set to {@code TableCellBorderStyle.None}. * * @param cellVerticalBorderStyle Style to use to separate table cells vertically */ public void setCellVerticalBorderStyle(TableCellBorderStyle cellVerticalBorderStyle) { this.cellVerticalBorderStyle = cellVerticalBorderStyle; } /** * Sets the style to be used when horizontally separating table cells from each other. This will cause a new * column to be added between every row, unless set to {@code TableCellBorderStyle.None}. * * @param cellHorizontalBorderStyle Style to use to separate table cells horizontally */ public void setCellHorizontalBorderStyle(TableCellBorderStyle cellHorizontalBorderStyle) { this.cellHorizontalBorderStyle = cellHorizontalBorderStyle; } private boolean isHorizontallySpaced() { return headerHorizontalBorderStyle != TableCellBorderStyle.None || cellHorizontalBorderStyle != TableCellBorderStyle.None; } @Override public TerminalSize getPreferredSize(Table table) { //Quick bypass if the table hasn't changed if(!table.isInvalid() && cachedSize != null) { return cachedSize; } TableModel tableModel = table.getTableModel(); int viewLeftColumn = table.getViewLeftColumn(); int viewTopRow = table.getViewTopRow(); int visibleColumns = table.getVisibleColumns(); int visibleRows = table.getVisibleRows(); List> rows = tableModel.getRows(); List columnHeaders = tableModel.getColumnLabels(); TableHeaderRenderer tableHeaderRenderer = table.getTableHeaderRenderer(); TableCellRenderer tableCellRenderer = table.getTableCellRenderer(); if(visibleColumns == 0) { visibleColumns = tableModel.getColumnCount(); } if(visibleRows == 0) { visibleRows = tableModel.getRowCount(); } columnSizes.clear(); rowSizes.clear(); if(tableModel.getColumnCount() == 0) { return TerminalSize.ZERO; } for(int rowIndex = 0; rowIndex < rows.size(); rowIndex++) { List row = rows.get(rowIndex); for(int columnIndex = viewLeftColumn; columnIndex < Math.min(row.size(), viewLeftColumn + visibleColumns); columnIndex++) { V cell = row.get(columnIndex); int columnSize = tableCellRenderer.getPreferredSize(table, cell, columnIndex, rowIndex).getColumns(); int listOffset = columnIndex - viewLeftColumn; if(columnSizes.size() == listOffset) { columnSizes.add(columnSize); } else { if(columnSizes.get(listOffset) < columnSize) { columnSizes.set(listOffset, columnSize); } } } //Do the headers too, on the first iteration if(rowIndex == 0) { for(int columnIndex = viewLeftColumn; columnIndex < Math.min(row.size(), viewLeftColumn + visibleColumns); columnIndex++) { int columnSize = tableHeaderRenderer.getPreferredSize(table, columnHeaders.get(columnIndex), columnIndex).getColumns(); int listOffset = columnIndex - viewLeftColumn; if(columnSizes.size() == listOffset) { columnSizes.add(columnSize); } else { if(columnSizes.get(listOffset) < columnSize) { columnSizes.set(listOffset, columnSize); } } } } } for(int columnIndex = 0; columnIndex < columnHeaders.size(); columnIndex++) { for(int rowIndex = viewTopRow; rowIndex < Math.min(rows.size(), viewTopRow + visibleRows); rowIndex++) { V cell = rows.get(rowIndex).get(columnIndex); int rowSize = tableCellRenderer.getPreferredSize(table, cell, columnIndex, rowIndex).getRows(); int listOffset = rowIndex - viewTopRow; if(rowSizes.size() == listOffset) { rowSizes.add(rowSize); } else { if(rowSizes.get(listOffset) < rowSize) { rowSizes.set(listOffset, rowSize); } } } } int preferredRowSize = 0; int preferredColumnSize = 0; for(int size: columnSizes) { preferredColumnSize += size; } for(int size: rowSizes) { preferredRowSize += size; } headerSizeInRows = 0; for(int columnIndex = 0; columnIndex < columnHeaders.size(); columnIndex++) { int headerRows = tableHeaderRenderer.getPreferredSize(table, columnHeaders.get(columnIndex), columnIndex).getRows(); if(headerSizeInRows < headerRows) { headerSizeInRows = headerRows; } } preferredRowSize += headerSizeInRows; if(headerVerticalBorderStyle != TableCellBorderStyle.None) { preferredRowSize++; //Spacing between header and body } if(cellVerticalBorderStyle != TableCellBorderStyle.None) { if(!rows.isEmpty()) { preferredRowSize += Math.min(rows.size(), visibleRows) - 1; //Vertical space between cells } } if(isHorizontallySpaced()) { if(!columnHeaders.isEmpty()) { preferredColumnSize += Math.min(tableModel.getColumnCount(), visibleColumns) - 1; //Spacing between the columns } } //Add on space taken by scrollbars (if needed) if(visibleRows < rows.size()) { preferredColumnSize++; } if(visibleColumns < tableModel.getColumnCount()) { preferredRowSize++; } cachedSize = new TerminalSize(preferredColumnSize, preferredRowSize); return cachedSize; } @Override public TerminalPosition getCursorLocation(Table component) { return null; } @Override public void drawComponent(TextGUIGraphics graphics, Table table) { //Get the size TerminalSize area = graphics.getSize(); //Don't even bother if(area.getRows() == 0 || area.getColumns() == 0) { return; } int topPosition = drawHeader(graphics, table); drawRows(graphics, table, topPosition); } private int drawHeader(TextGUIGraphics graphics, Table table) { TableHeaderRenderer tableHeaderRenderer = table.getTableHeaderRenderer(); List headers = table.getTableModel().getColumnLabels(); int viewLeftColumn = table.getViewLeftColumn(); int visibleColumns = table.getVisibleColumns(); if(visibleColumns == 0) { visibleColumns = table.getTableModel().getColumnCount(); } int topPosition = 0; int leftPosition = 0; int endColumnIndex = Math.min(headers.size(), viewLeftColumn + visibleColumns); for(int index = viewLeftColumn; index < endColumnIndex; index++) { String label = headers.get(index); TerminalSize size = new TerminalSize(columnSizes.get(index - viewLeftColumn), headerSizeInRows); tableHeaderRenderer.drawHeader(table, label, index, graphics.newTextGraphics(new TerminalPosition(leftPosition, 0), size)); leftPosition += size.getColumns(); if(headerHorizontalBorderStyle != TableCellBorderStyle.None && index < (endColumnIndex - 1)) { graphics.applyThemeStyle(graphics.getThemeDefinition(Table.class).getNormal()); graphics.setCharacter(leftPosition, 0, getVerticalCharacter(headerHorizontalBorderStyle)); leftPosition++; } } topPosition += headerSizeInRows; if(headerVerticalBorderStyle != TableCellBorderStyle.None) { leftPosition = 0; graphics.applyThemeStyle(graphics.getThemeDefinition(Table.class).getNormal()); for(int i = 0; i < columnSizes.size(); i++) { if(i > 0) { graphics.setCharacter( leftPosition, topPosition, getJunctionCharacter( headerVerticalBorderStyle, headerHorizontalBorderStyle, cellHorizontalBorderStyle)); leftPosition++; } int columnWidth = columnSizes.get(i); graphics.drawLine(leftPosition, topPosition, leftPosition + columnWidth - 1, topPosition, getHorizontalCharacter(headerVerticalBorderStyle)); leftPosition += columnWidth; } //Expand out the line in case the area is bigger if(leftPosition < graphics.getSize().getColumns()) { graphics.drawLine(leftPosition, topPosition, graphics.getSize().getColumns() - 1, topPosition, getHorizontalCharacter(headerVerticalBorderStyle)); } topPosition++; } return topPosition; } private void drawRows(TextGUIGraphics graphics, Table table, int topPosition) { TerminalSize area = graphics.getSize(); TableCellRenderer tableCellRenderer = table.getTableCellRenderer(); TableModel tableModel = table.getTableModel(); List> rows = tableModel.getRows(); int viewTopRow = table.getViewTopRow(); int viewLeftColumn = table.getViewLeftColumn(); int visibleRows = table.getVisibleRows(); int visibleColumns = table.getVisibleColumns(); if(visibleColumns == 0) { visibleColumns = tableModel.getColumnCount(); } if(visibleRows == 0) { visibleRows = tableModel.getRowCount(); } //Exit if there are no rows if(rows.isEmpty()) { return; } //Draw scrollbars (if needed) if(visibleRows < rows.size()) { TerminalSize verticalScrollBarPreferredSize = verticalScrollBar.getPreferredSize(); int scrollBarHeight = graphics.getSize().getRows() - topPosition; if(visibleColumns < tableModel.getColumnCount()) { scrollBarHeight--; } verticalScrollBar.setPosition(new TerminalPosition(graphics.getSize().getColumns() - verticalScrollBarPreferredSize.getColumns(), topPosition)); verticalScrollBar.setSize(verticalScrollBarPreferredSize.withRows(scrollBarHeight)); verticalScrollBar.setScrollMaximum(rows.size()); verticalScrollBar.setViewSize(visibleRows); verticalScrollBar.setScrollPosition(viewTopRow); verticalScrollBar.draw(graphics.newTextGraphics(verticalScrollBar.getPosition(), verticalScrollBar.getSize())); graphics = graphics.newTextGraphics(TerminalPosition.TOP_LEFT_CORNER, graphics.getSize().withRelativeColumns(-verticalScrollBarPreferredSize.getColumns())); } if(visibleColumns < tableModel.getColumnCount()) { TerminalSize horizontalScrollBarPreferredSize = horizontalScrollBar.getPreferredSize(); int scrollBarWidth = graphics.getSize().getColumns(); horizontalScrollBar.setPosition(new TerminalPosition(0, graphics.getSize().getRows() - horizontalScrollBarPreferredSize.getRows())); horizontalScrollBar.setSize(horizontalScrollBarPreferredSize.withColumns(scrollBarWidth)); horizontalScrollBar.setScrollMaximum(tableModel.getColumnCount()); horizontalScrollBar.setViewSize(visibleColumns); horizontalScrollBar.setScrollPosition(viewLeftColumn); horizontalScrollBar.draw(graphics.newTextGraphics(horizontalScrollBar.getPosition(), horizontalScrollBar.getSize())); graphics = graphics.newTextGraphics(TerminalPosition.TOP_LEFT_CORNER, graphics.getSize().withRelativeRows(-horizontalScrollBarPreferredSize.getRows())); } int leftPosition; for(int rowIndex = viewTopRow; rowIndex < Math.min(viewTopRow + visibleRows, rows.size()); rowIndex++) { leftPosition = 0; List row = rows.get(rowIndex); for(int columnIndex = viewLeftColumn; columnIndex < Math.min(viewLeftColumn + visibleColumns, row.size()); columnIndex++) { if(columnIndex > viewLeftColumn) { if(table.getSelectedRow() == rowIndex && !table.isCellSelection()) { if(table.isFocused()) { graphics.applyThemeStyle(graphics.getThemeDefinition(Table.class).getActive()); } else { graphics.applyThemeStyle(graphics.getThemeDefinition(Table.class).getSelected()); } } else { graphics.applyThemeStyle(graphics.getThemeDefinition(Table.class).getNormal()); } graphics.setCharacter(leftPosition, topPosition, getVerticalCharacter(cellHorizontalBorderStyle)); leftPosition++; } V cell = row.get(columnIndex); TerminalPosition cellPosition = new TerminalPosition(leftPosition, topPosition); TerminalSize cellArea = new TerminalSize(columnSizes.get(columnIndex - viewLeftColumn), rowSizes.get(rowIndex - viewTopRow)); tableCellRenderer.drawCell(table, cell, columnIndex, rowIndex, graphics.newTextGraphics(cellPosition, cellArea)); leftPosition += cellArea.getColumns(); if(leftPosition > area.getColumns()) { break; } } topPosition += rowSizes.get(rowIndex - viewTopRow); if(cellVerticalBorderStyle != TableCellBorderStyle.None) { leftPosition = 0; graphics.applyThemeStyle(graphics.getThemeDefinition(Table.class).getNormal()); for(int i = 0; i < columnSizes.size(); i++) { if(i > 0) { graphics.setCharacter( leftPosition, topPosition, getJunctionCharacter( cellVerticalBorderStyle, cellHorizontalBorderStyle, cellHorizontalBorderStyle)); leftPosition++; } int columnWidth = columnSizes.get(i); graphics.drawLine(leftPosition, topPosition, leftPosition + columnWidth - 1, topPosition, getHorizontalCharacter(cellVerticalBorderStyle)); leftPosition += columnWidth; } topPosition += 1; } if(topPosition > area.getRows()) { break; } } } private char getHorizontalCharacter(TableCellBorderStyle style) { switch(style) { case SingleLine: return Symbols.SINGLE_LINE_HORIZONTAL; case DoubleLine: return Symbols.DOUBLE_LINE_HORIZONTAL; default: return ' '; } } private char getVerticalCharacter(TableCellBorderStyle style) { switch(style) { case SingleLine: return Symbols.SINGLE_LINE_VERTICAL; case DoubleLine: return Symbols.DOUBLE_LINE_VERTICAL; default: return ' '; } } private char getJunctionCharacter(TableCellBorderStyle mainStyle, TableCellBorderStyle styleAbove, TableCellBorderStyle styleBelow) { if(mainStyle == TableCellBorderStyle.SingleLine) { if(styleAbove == TableCellBorderStyle.SingleLine) { if(styleBelow == TableCellBorderStyle.SingleLine) { return Symbols.SINGLE_LINE_CROSS; } else if(styleBelow == TableCellBorderStyle.DoubleLine) { //There isn't any character for this, give upper side priority return Symbols.SINGLE_LINE_T_UP; } else { return Symbols.SINGLE_LINE_T_UP; } } else if(styleAbove == TableCellBorderStyle.DoubleLine) { if(styleBelow == TableCellBorderStyle.SingleLine) { //There isn't any character for this, give upper side priority return Symbols.SINGLE_LINE_T_DOUBLE_UP; } else if(styleBelow == TableCellBorderStyle.DoubleLine) { return Symbols.DOUBLE_LINE_VERTICAL_SINGLE_LINE_CROSS; } else { return Symbols.SINGLE_LINE_T_DOUBLE_UP; } } else { if(styleBelow == TableCellBorderStyle.SingleLine) { return Symbols.SINGLE_LINE_T_DOWN; } else if(styleBelow == TableCellBorderStyle.DoubleLine) { return Symbols.SINGLE_LINE_T_DOUBLE_DOWN; } else { return Symbols.SINGLE_LINE_HORIZONTAL; } } } else if(mainStyle == TableCellBorderStyle.DoubleLine) { if(styleAbove == TableCellBorderStyle.SingleLine) { if(styleBelow == TableCellBorderStyle.SingleLine) { return Symbols.DOUBLE_LINE_HORIZONTAL_SINGLE_LINE_CROSS; } else if(styleBelow == TableCellBorderStyle.DoubleLine) { //There isn't any character for this, give upper side priority return Symbols.DOUBLE_LINE_T_SINGLE_UP; } else { return Symbols.DOUBLE_LINE_T_SINGLE_UP; } } else if(styleAbove == TableCellBorderStyle.DoubleLine) { if(styleBelow == TableCellBorderStyle.SingleLine) { //There isn't any character for this, give upper side priority return Symbols.DOUBLE_LINE_T_UP; } else if(styleBelow == TableCellBorderStyle.DoubleLine) { return Symbols.DOUBLE_LINE_CROSS; } else { return Symbols.DOUBLE_LINE_T_UP; } } else { if(styleBelow == TableCellBorderStyle.SingleLine) { return Symbols.DOUBLE_LINE_T_SINGLE_DOWN; } else if(styleBelow == TableCellBorderStyle.DoubleLine) { return Symbols.DOUBLE_LINE_T_DOWN; } else { return Symbols.DOUBLE_LINE_HORIZONTAL; } } } else { return ' '; } } }