-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 <V> Type of data stored in each table cell
- * @author Martin
- */
-public class DefaultTableRenderer<V> implements TableRenderer<V> {
-
- 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<Integer> columnSizes;
- private List<Integer> 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<Integer>();
- rowSizes = new ArrayList<Integer>();
- 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<V> table) {
- //Quick bypass if the table hasn't changed
- if(!table.isInvalid() && cachedSize != null) {
- return cachedSize;
- }
-
- TableModel<V> tableModel = table.getTableModel();
- int viewLeftColumn = table.getViewLeftColumn();
- int viewTopRow = table.getViewTopRow();
- int visibleColumns = table.getVisibleColumns();
- int visibleRows = table.getVisibleRows();
- List<List<V>> rows = tableModel.getRows();
- List<String> columnHeaders = tableModel.getColumnLabels();
- TableHeaderRenderer<V> tableHeaderRenderer = table.getTableHeaderRenderer();
- TableCellRenderer<V> 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<V> 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<V> component) {
- return null;
- }
-
- @Override
- public void drawComponent(TextGUIGraphics graphics, Table<V> 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<V> table) {
- TableHeaderRenderer<V> tableHeaderRenderer = table.getTableHeaderRenderer();
- List<String> 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<V> table, int topPosition) {
- TerminalSize area = graphics.getSize();
- TableCellRenderer<V> tableCellRenderer = table.getTableCellRenderer();
- TableModel<V> tableModel = table.getTableModel();
- List<List<V>> 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<V> 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 ' ';
- }
- }
-}