package org.geogebra.desktop.gui.view.data;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.SystemColor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.HashMap;
import java.util.Map.Entry;
import javax.swing.BorderFactory;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ListCellRenderer;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.text.JTextComponent;
import org.geogebra.common.main.GeoGebraColorConstants;
import org.geogebra.desktop.main.AppD;
import org.geogebra.desktop.main.LocalizationD;
public class StatTable extends JScrollPane {
private static final long serialVersionUID = 1L;
private MyTable myTable;
private MyRowHeader rowHeader;
boolean isRowHeaderPainted = true;
// layout
private static final Color TABLE_GRID_COLOR = DataAnalysisViewD.TABLE_GRID_COLOR;
private static final Color TABLE_HEADER_COLOR = DataAnalysisViewD.TABLE_HEADER_COLOR;
private static final Color SELECTED_BACKGROUND_COLOR = org.geogebra.desktop.awt.GColorD
.getAwtColor(
GeoGebraColorConstants.TABLE_SELECTED_BACKGROUND_COLOR);
protected DefaultTableModel tableModel;
private HashMap<Point, MyComboBoxEditor> comboBoxEditorMap;
private HashMap<Point, MyComboBoxRenderer> comboBoxRendererMap;
private ActionListener al;
AppD app;
final LocalizationD loc;
public StatTable(AppD app) {
this.app = app;
this.loc = app.getLocalization();
// create and initialize the table
initTable();
// enclose the table in this scrollPane
setViewportView(myTable);
myTable.setBorder(BorderFactory.createEmptyBorder());
// setBorder(BorderFactory.createEmptyBorder());
myTable.setBorder(
BorderFactory.createLineBorder(SystemColor.controlShadow));
// set the corners
setCorner(ScrollPaneConstants.UPPER_RIGHT_CORNER, new Corner());
setCorner(ScrollPaneConstants.LOWER_RIGHT_CORNER, new Corner());
setCorner(ScrollPaneConstants.LOWER_LEFT_CORNER, new Corner());
this.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER, new Corner());
if (isRowHeaderPainted) {
((JPanel) this.getCorner(ScrollPaneConstants.UPPER_LEFT_CORNER))
.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 1,
TABLE_GRID_COLOR));
}
myTable.setPreferredScrollableViewportSize(myTable.getPreferredSize());
myTable.setBackground(this.getBackground());
}
public MyTable getTable() {
return myTable;
}
private void initTable() {
myTable = new MyTable();
// table settings
myTable.setDefaultRenderer(Object.class, new MyCellRenderer(this));
myTable.setColumnSelectionAllowed(true);
myTable.setRowSelectionAllowed(true);
myTable.setShowGrid(true);
myTable.setGridColor(TABLE_GRID_COLOR);
myTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
// ((JLabel)
// statTable.getTableHeader().getDefaultRenderer()).setHorizontalAlignment(SwingConstants.CENTER);
myTable.setBackground(Color.white);
}
private static class Corner extends JPanel {
private static final long serialVersionUID = 1L;
@Override
protected void paintComponent(Graphics g) {
g.setColor(this.getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
}
/**
* Sets the dimensions and header values for the table. This should only be
* called once.
*
* @param rows
* number of rows
* @param rowNames
* array of row header strings, if null then a row header is not
* drawn
* @param columns
* number of columns
* @param columnNames
* array of column header strings, if null then a column header
* is not drawn
*/
public void setStatTable(int rows, String[] rowNames, int columns,
String[] columnNames) {
// TODO: cannot remove columns ... call this again with fewer columns
// and the older columns persist ????
tableModel = new DefaultTableModel(rows, columns);
myTable.setModel(tableModel);
// set column names
if (columnNames == null) {
myTable.setTableHeader(null);
this.setColumnHeaderView(null);
} else {
tableModel.setColumnCount(0);
for (int i = 0; i < columnNames.length; i++) {
tableModel.addColumn(columnNames[i]);
}
}
// create row header
if (rowNames != null) {
rowHeader = new MyRowHeader(myTable, rowNames, this);
// rowHeaderModel = new DefaultListModel();
// .setModel(rowHeaderModel);
setRowHeaderView(rowHeader);
} else {
setRowHeaderView(null);
}
myTable.setPreferredScrollableViewportSize(myTable.getPreferredSize());
// statTable.setMinimumSize(statTable.getPreferredSize());
this.revalidate();
repaint();
}
/**
* Sets all cells values to the blank string " ". Does not change table
* dimensions.
*/
public void clear() {
for (int r = 0; r < myTable.getRowCount(); r++) {
for (int c = 0; c < myTable.getColumnCount(); c++) {
myTable.setValueAt(" ", r, c);
}
}
}
/**
* Sets the table cells that will use a ComboBox
*
* @param cellMap
*/
public void setComboBoxCells(HashMap<Point, String[]> cellMap,
ActionListener al) {
this.al = al;
if (comboBoxEditorMap == null) {
comboBoxEditorMap = new HashMap<Point, MyComboBoxEditor>();
}
comboBoxEditorMap.clear();
if (comboBoxRendererMap == null) {
comboBoxRendererMap = new HashMap<Point, MyComboBoxRenderer>();
}
comboBoxRendererMap.clear();
for (Entry<Point, String[]> entry : cellMap.entrySet()) {
Point cell = entry.getKey();
// get the String data for this combo box
String[] items = entry.getValue();
// extract the menu items and the combo box label
String comboBoxLabel = items[items.length - 1];
String[] comboBoxItems = new String[items.length - 1];
System.arraycopy(items, 0, comboBoxItems, 0, comboBoxItems.length);
// create the comboBox editors/renderers and map them
comboBoxEditorMap.put(cell, new MyComboBoxEditor(comboBoxItems));
comboBoxRendererMap.put(cell,
new MyComboBoxRenderer(comboBoxLabel, comboBoxItems));
}
}
/**
* Gets the selected index for a cell given cell comboBox
*
* @param row
* @param column
* @return
*/
public Integer getComboCellEditorSelectedIndex(int row, int column) {
if (comboBoxEditorMap == null) {
return null;
}
int modelColumn = myTable.convertColumnIndexToModel(column);
Point cell = new Point(row, modelColumn);
if (comboBoxEditorMap.keySet().contains(cell)) {
return comboBoxEditorMap.get(cell).getSelectedIndex();
}
return null;
}
/**
* Sets the selected index for a cell given cell comboBox
*
* @param index
* @param row
* @param column
* @return
*/
public boolean setComboCellSelectedIndex(int index, int row, int column) {
if (comboBoxRendererMap == null) {
return false;
}
int modelColumn = myTable.convertColumnIndexToModel(column);
Point cell = new Point(row, modelColumn);
if (comboBoxEditorMap.keySet().contains(cell)) {
comboBoxEditorMap.get(cell).setSelectedIndex(index);
return true;
}
return false;
}
public void setLabels(String[] rowNames, String[] columnNames) {
// set column names
if (columnNames != null) {
for (int i = 0; i < columnNames.length; i++) {
myTable.getColumnModel().getColumn(i)
.setHeaderValue(columnNames[i]);
}
}
if (rowNames != null) {
rowHeader = new MyRowHeader(myTable, rowNames, this);
setRowHeaderView(rowHeader);
}
repaint();
}
public DefaultTableModel getModel() {
return tableModel;
}
public void updateFonts(Font font) {
setFont(font);
if (myTable != null && myTable.getRowCount() > 0) {
myTable.setFont(font);
autoFitRowHeight();
if (rowHeader != null) {
rowHeader.setFont(font);
rowHeader.setFixedCellHeight(myTable.getRowHeight());
}
if (myTable.getTableHeader() != null) {
myTable.getTableHeader().setFont(font);
}
}
if (myTable != null) {
myTable.setPreferredScrollableViewportSize(
myTable.getPreferredSize());
}
}
/**
* Adjust the width of a column to fit the maximum preferred width of its
* cell contents.
*/
public void autoFitColumnWidth(int column, int defaultColumnWidth) {
MyTable table = myTable;
if (table.getRowCount() <= 0) {
return;
}
TableColumn tableColumn = table.getColumnModel().getColumn(column);
int prefWidth = 0;
int tempWidth = -1;
for (int row = 0; row < table.getRowCount(); row++) {
if (table.getValueAt(row, column) != null) {
tempWidth = (int) table.getCellRenderer(row, column)
.getTableCellRendererComponent(table,
table.getValueAt(row, column), false, false,
row, column)
.getPreferredSize().getWidth();
prefWidth = Math.max(prefWidth, tempWidth);
}
}
// set the new column width
if (tempWidth == -1) {
// column is empty
prefWidth = defaultColumnWidth - table.getIntercellSpacing().width;
} else {
prefWidth = Math.max(prefWidth, tableColumn.getMinWidth());
// System.out.println("pref width: " + prefWidth);
}
table.getTableHeader().setResizingColumn(tableColumn);
tableColumn.setWidth(prefWidth + table.getIntercellSpacing().width);
}
/**
* Adjust the height of a row to fit the maximum preferred height of its
* cell contents.
*/
public void autoFitRowHeight() {
MyTable table = myTable;
if (table.getRowCount() <= 0) {
return;
}
// iterate through the rows and find the preferred height
int prefHeight = table.getRowHeight();
int tempHeight = -1;
for (int row = 0; row < table.getRowCount(); row++) {
for (int column = 0; column < table.getColumnCount(); column++) {
if (table.getValueAt(row, column) != null) {
tempHeight = (int) table.getCellRenderer(row, column)
.getTableCellRendererComponent(table,
table.getValueAt(row, column), false, false,
row, column)
.getPreferredSize().getHeight();
prefHeight = Math.max(prefHeight, tempHeight);
}
}
}
// set the new row height
table.setRowHeight(prefHeight);
}
private int alignment = SwingConstants.LEFT;
public void setHorizontalAlignment(int alignment) {
this.alignment = alignment;
TableCellRenderer renderer = myTable.getTableHeader()
.getDefaultRenderer();
JLabel label = (JLabel) renderer;
label.setHorizontalAlignment(alignment);
}
public int getHorizontalAlignment() {
return alignment;
}
// ======================================================
// Table Cell Renderer
// ======================================================
private static class MyCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
private StatTable statTable;
public MyCellRenderer(StatTable statTable) {
// cell padding
setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
this.statTable = statTable;
}
@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
setFont(table.getFont());
setText((String) value);
setHorizontalAlignment(statTable.getHorizontalAlignment());
if (isSelected) {
setBackground(SELECTED_BACKGROUND_COLOR);
} else {
setBackground(Color.white);
}
return this;
}
}
// ======================================================
// Row Header
// ======================================================
public class MyRowHeader extends JList {
private static final long serialVersionUID = 1L;
JTable table;
public MyRowHeader(JTable table, String[] rowNames,
StatTable statTable) {
super(rowNames);
this.table = table;
setCellRenderer(new RowHeaderRenderer(table));
setFixedCellHeight(table.getRowHeight());
}
class RowHeaderRenderer extends JLabel implements ListCellRenderer {
private static final long serialVersionUID = 1L;
public RowHeaderRenderer(JTable table) {
if (isRowHeaderPainted) {
setOpaque(true);
setBackground(TABLE_HEADER_COLOR);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(0, 0, 1, 1,
TABLE_GRID_COLOR),
BorderFactory.createEmptyBorder(2, 5, 2, 5)));
} else {
setOpaque(true);
setBackground(table.getBackground());
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(0, 0, 0, 1,
TABLE_GRID_COLOR),
BorderFactory.createEmptyBorder(0, 5, 0, 5)));
}
setFont(table.getFont());
}
@Override
public Component getListCellRendererComponent(JList list,
Object value, int index, boolean isSelected,
boolean cellHasFocus) {
setFont(table.getFont());
setText((String) value);
// setHorizontalAlignment(statTable.getHorizontalAlignment());
return this;
}
}
}
// ======================================================
// ComboBox Renderer
// ======================================================
public class MyComboBoxRenderer extends JPanel
implements TableCellRenderer {
private static final long serialVersionUID = 1L;
JComboBox comboBox;
JLabel label;
public MyComboBoxRenderer(String text, String[] items) {
setLayout(new BorderLayout());
comboBox = new JComboBox(items);
add(comboBox, loc.borderEast());
if (text != null) {
label = new JLabel(text);
add(label, BorderLayout.CENTER);
}
}
@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
setFont(table.getFont());
setForeground(table.getForeground());
setBackground(table.getBackground());
comboBox.setSelectedIndex(
getComboCellEditorSelectedIndex(row, column));
return this;
}
}
// ======================================================
// ComboBox Editor
// ======================================================
public class MyComboBoxEditor extends DefaultCellEditor
implements ItemListener {
private static final long serialVersionUID = 1L;
JComboBox comboBox;
JLabel label;
int row, column;
public MyComboBoxEditor(String[] items) {
super(new JComboBox(items));
comboBox = (JComboBox) editorComponent;
comboBox.addItemListener(this);
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
setFont(table.getFont());
this.row = row;
this.column = column;
return editorComponent;
}
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() != ItemEvent.SELECTED) {
return;
}
myTable.getModel().setValueAt(comboBox.getSelectedIndex(), row,
column);
al.actionPerformed(new ActionEvent(this,
ActionEvent.ACTION_PERFORMED, "updateTable"));
}
public int getSelectedIndex() {
return comboBox.getSelectedIndex();
}
public void setSelectedIndex(int index) {
comboBox.setSelectedIndex(index);
}
}
/**
* @param allowCellEdit
* true if table cell can be edited
*/
public void setAllowCellEdit(boolean allowCellEdit) {
myTable.setAllowCellEdit(allowCellEdit);
}
// ======================================================
// MyTable
// ======================================================
public class MyTable extends JTable {
private static final long serialVersionUID = 1L;
private boolean allowCellEdit = false;
/**
* @param allowCellEdit
* true if table cell can be edited
*/
public void setAllowCellEdit(boolean allowCellEdit) {
this.allowCellEdit = allowCellEdit;
}
// disable cell editing
@Override
public boolean isCellEditable(int rowIndex, int colIndex) {
if (allowCellEdit) {
return true;
}
if (comboBoxEditorMap == null) {
return false;
}
int modelColumn = convertColumnIndexToModel(colIndex);
Point cell = new Point(rowIndex, modelColumn);
return comboBoxEditorMap.keySet().contains(cell);
}
// fill empty scroll pane space with table background color
@Override
protected void configureEnclosingScrollPane() {
super.configureEnclosingScrollPane();
Container p = getParent();
if (p instanceof JViewport) {
((JViewport) p).setBackground(getBackground());
}
}
// select all when editing starts
@Override
public Component prepareEditor(TableCellEditor editor, int row,
int column) {
Component c = super.prepareEditor(editor, row, column);
if (c instanceof JTextComponent) {
((JTextComponent) c).selectAll();
}
return c;
}
// Determine if comboCellEditor should be used
@Override
public TableCellEditor getCellEditor(int row, int column) {
if (comboBoxEditorMap == null) {
return super.getCellEditor(row, column);
}
int modelColumn = convertColumnIndexToModel(column);
Point cell = new Point(row, modelColumn);
if (comboBoxEditorMap.keySet().contains(cell)) {
return comboBoxEditorMap.get(cell);
}
return super.getCellEditor(row, column);
}
// Determine if comboCellRenderer should be used
@Override
public TableCellRenderer getCellRenderer(int row, int column) {
if (comboBoxRendererMap == null) {
return super.getCellRenderer(row, column);
}
int modelColumn = convertColumnIndexToModel(column);
Point cell = new Point(row, modelColumn);
if (comboBoxRendererMap.keySet().contains(cell)) {
return comboBoxRendererMap.get(cell);
}
return super.getCellRenderer(row, column);
}
}
}