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.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
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.TableCellRenderer;
import javax.swing.table.TableModel;
import org.geogebra.common.euclidian.draw.DrawBoolean.CheckBoxIcon;
import org.geogebra.common.gui.view.data.DataAnalysisModel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.main.GeoGebraColorConstants;
import org.geogebra.common.main.settings.SpreadsheetSettings;
import org.geogebra.common.util.debug.Log;
import org.geogebra.desktop.awt.GColorD;
import org.geogebra.desktop.awt.GGraphics2DD;
import org.geogebra.desktop.main.AppD;
import org.geogebra.desktop.main.LocalizationD;
public class DataPanelD extends JPanel
implements ActionListener, StatPanelInterface {
private static final long serialVersionUID = 1L;
private AppD app;
private DataAnalysisViewD daView;
private DataAnalysisControllerD statController;
private JTable dataTable;
private JButton btnEnableAll;
private MyRowHeader rowHeader;
private MyColumnHeaderRenderer columnHeader;
private JScrollPane scrollPane;
private Boolean[] selectionList;
private JLabel lblHeader;
public int preferredColumnWidth = SpreadsheetSettings.TABLE_CELL_WIDTH;
private LocalizationD loc;
private static final Color DISABLED_BACKGROUND_COLOR = Color.LIGHT_GRAY;
private static final Color TABLE_GRID_COLOR = DataAnalysisViewD.TABLE_GRID_COLOR;
private static final Color TABLE_HEADER_COLOR = DataAnalysisViewD.TABLE_HEADER_COLOR;
/*************************************************
* Construct a DataPanel
*/
public DataPanelD(AppD app, DataAnalysisViewD statDialog) {
this.app = app;
this.loc = app.getLocalization();
this.daView = statDialog;
this.statController = statDialog.getController();
buildDataTable();
populateDataTable(statController.getDataArray());
createGUI();
}
private void buildDataTable() {
dataTable = new JTable() {
private static final long serialVersionUID = 1L;
// disable cell edits (for now)
@Override
public boolean isCellEditable(int rowIndex, int vColIndex) {
return false;
}
@Override
protected void configureEnclosingScrollPane() {
super.configureEnclosingScrollPane();
Container p = getParent();
if (p instanceof JViewport) {
((JViewport) p).setBackground(getBackground());
}
}
};
}
private void createGUI() {
// set table and column renderers
dataTable.setDefaultRenderer(Object.class, new MyCellRenderer());
columnHeader = new MyColumnHeaderRenderer();
columnHeader.setPreferredSize(new Dimension(preferredColumnWidth,
SpreadsheetSettings.TABLE_CELL_HEIGHT));
for (int i = 0; i < dataTable.getColumnCount(); ++i) {
dataTable.getColumnModel().getColumn(i)
.setHeaderRenderer(columnHeader);
dataTable.getColumnModel().getColumn(i)
.setPreferredWidth(preferredColumnWidth);
}
// disable row selection (for now)
dataTable.setColumnSelectionAllowed(false);
dataTable.setRowSelectionAllowed(false);
// dataTable.setAutoResizeMode(JTable.);
dataTable.setPreferredScrollableViewportSize(
dataTable.getPreferredSize());
dataTable.setMinimumSize(new Dimension(100, 50));
// dataTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
dataTable.setAutoCreateColumnsFromModel(false);
dataTable.setGridColor(TABLE_GRID_COLOR);
// create a scrollPane for the table
scrollPane = new JScrollPane(dataTable);
scrollPane.setBorder(BorderFactory.createEmptyBorder());
// create row header
rowHeader = new MyRowHeader(this, dataTable);
scrollPane.setRowHeaderView(rowHeader);
// create enableAll button and put it in the upper left corner
DataCheckBoxIcon cbIcon = new DataCheckBoxIcon(13);
ImageIcon iconUnChecked = cbIcon.createCheckBoxImageIcon(false, false);
ImageIcon iconChecked = cbIcon.createCheckBoxImageIcon(true, false);
btnEnableAll = new JButton();
btnEnableAll.setIcon(iconUnChecked);
btnEnableAll.setDisabledIcon(iconChecked);
btnEnableAll.setEnabled(false);
btnEnableAll.setBorderPainted(false);
btnEnableAll.setBackground(GColorD.getAwtColor(
GeoGebraColorConstants.TABLE_BACKGROUND_COLOR_HEADER));
btnEnableAll.setContentAreaFilled(false);
btnEnableAll.setHorizontalAlignment(SwingConstants.LEFT);
btnEnableAll.addActionListener(this);
Corner upperLeftCorner = new Corner();
upperLeftCorner.setLayout(new BorderLayout());
upperLeftCorner.add(btnEnableAll, loc.borderWest());
upperLeftCorner.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(0, 0, 1, 1, TABLE_GRID_COLOR),
BorderFactory.createEmptyBorder(0, 5, 0, 2)));
// set the other corners
scrollPane.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER,
upperLeftCorner);
scrollPane.setCorner(ScrollPaneConstants.LOWER_LEFT_CORNER,
new Corner());
scrollPane.setCorner(ScrollPaneConstants.UPPER_RIGHT_CORNER,
new Corner());
lblHeader = new JLabel();
lblHeader.setHorizontalAlignment(SwingConstants.LEFT);
lblHeader.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEtchedBorder(),
BorderFactory.createEmptyBorder(2, 5, 2, 2)));
// finally, load up our JPanel
this.setLayout(new BorderLayout());
this.add(lblHeader, BorderLayout.NORTH);
this.add(scrollPane, BorderLayout.CENTER);
this.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
this.setMinimumSize(dataTable.getPreferredSize());
}
@Override
public void setLabels() {
lblHeader.setText(loc.getMenu("Data"));
}
@Override
public void updatePanel() {
setRowHeight();
}
@Override
public void updateFonts(Font font) {
// TODO Auto-generated method stub
}
private Boolean[] updateSelectionList(ArrayList<GeoElement> dataArray) {
selectionList = new Boolean[dataArray.size()];
for (int i = 0; i < selectionList.length; ++i) {
selectionList[i] = true;
}
return selectionList;
}
private void populateDataTable(ArrayList<GeoElement> dataArray) {
if (dataArray == null || dataArray.size() < 1) {
return;
}
TableModel dataModel = null;
String[] titles = daView.getDataTitles();
switch (daView.getModel().getMode()) {
default:
case DataAnalysisModel.MODE_ONEVAR:
dataModel = new DefaultTableModel(dataArray.size(), 1);
for (int row = 0; row < dataArray.size(); ++row) {
dataModel
.setValueAt(
dataArray.get(row).toDefinedValueString(
StringTemplate.defaultTemplate),
row, 0);
}
dataTable.setModel(dataModel);
dataTable.getColumnModel().getColumn(0).setHeaderValue(titles[0]);
updateSelectionList(dataArray);
break;
case DataAnalysisModel.MODE_REGRESSION:
// a data source may be a list of points with a single title
// so we must create a title for the y column
String titleX = titles[0];
String titleY = titles.length == 1 ? titleX : titles[1];
dataModel = new DefaultTableModel(dataArray.size(), 2);
for (int row = 0; row < dataArray.size(); ++row) {
dataModel.setValueAt(
((GeoPoint) (dataArray.get(row))).getInhomX(), row, 0);
dataModel.setValueAt(
((GeoPoint) (dataArray.get(row))).getInhomY(), row, 1);
}
dataTable.setModel(dataModel);
// handle x,y titles
if (daView.getDataSource().isPointData()) {
dataTable.getColumnModel().getColumn(0)
.setHeaderValue(loc.getMenu("Column.X"));
// quick fix for GGB-1392
if (dataTable.getColumnModel().getColumnCount() > 1) {
dataTable.getColumnModel().getColumn(1)
.setHeaderValue(loc.getMenu("Column.Y"));
} else {
Log.error("problem setting title for 2nd column");
}
} else {
dataTable.getColumnModel().getColumn(0).setHeaderValue(
loc.getMenu("Column.X") + ": " + titleX);
// quick fix for GGB-1392
if (dataTable.getColumnModel().getColumnCount() > 1) {
dataTable.getColumnModel().getColumn(1).setHeaderValue(
loc.getMenu("Column.Y") + ": " + titleY);
} else {
Log.error("problem setting title for 2nd column");
}
}
updateSelectionList(dataArray);
break;
}
}
/**
* Loads the data table. Called on data set changes.
*/
public void loadDataTable(ArrayList<GeoElement> dataArray) {
// load the data model
populateDataTable(dataArray);
// prepare boolean selection list for the checkboxes
selectionList = new Boolean[dataArray.size()];
for (int i = 0; i < dataArray.size(); ++i) {
selectionList[i] = true;
}
// create a new header
rowHeader = new MyRowHeader(this, dataTable);
scrollPane.setRowHeaderView(rowHeader);
updateFonts(getFont());
// repaint
dataTable.repaint();
rowHeader.repaint();
}
public void ensureTableFill() {
Container p = getParent();
DefaultTableModel dataModel = (DefaultTableModel) dataTable.getModel();
if (dataTable.getHeight() < p.getHeight()) {
int newRows = (p.getHeight() - dataTable.getHeight())
/ dataTable.getRowHeight();
dataModel.setRowCount(dataTable.getRowCount() + newRows);
for (int i = 0; i <= dataTable.getRowCount(); ++i) {
if (rowHeader.getModel().getElementAt(i) != null) {
((DefaultListModel) rowHeader.getModel()).add(i, true);
}
}
}
}
private static class Corner extends JPanel {
private static final long serialVersionUID = 1L;
@Override
protected void paintComponent(Graphics g) {
g.setColor(TABLE_HEADER_COLOR);
g.fillRect(0, 0, getWidth(), getHeight());
}
}
@Override
public void setFont(Font font) {
super.setFont(font);
if (dataTable != null && dataTable.getRowCount() > 0
&& dataTable.getColumnCount() > 0) {
// set the font for each component
dataTable.setFont(font);
if (dataTable.getTableHeader() != null) {
dataTable.getTableHeader().setFont(font);
}
rowHeader.setFont(font);
setRowHeight();
// set the column width
int size = font.getSize();
if (size < 12)
{
size = 12; // minimum size
}
double multiplier = (size) / 12.0;
preferredColumnWidth = (int) (SpreadsheetSettings.TABLE_CELL_WIDTH
* multiplier);
// columnHeader.setPreferredSize(new Dimension(preferredColumnWidth,
// (int)(MyTable.TABLE_CELL_HEIGHT * multiplier)));
// this.validate();
// dataTable.repaint();
}
if (dataTable != null) {
dataTable.setPreferredScrollableViewportSize(
dataTable.getPreferredSize());
}
}
private void setRowHeight() {
// get row height needed to draw an "X" character
int h = dataTable.getCellRenderer(0, 0).getTableCellRendererComponent(
dataTable, "X", false, false, 0, 0).getPreferredSize().height;
// use this height to set the table and row header heights
dataTable.setRowHeight(h);
rowHeader.setFixedCellHeight(h);
}
public MyRowHeader getRowHeader() {
return rowHeader;
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == btnEnableAll) {
rowHeader.enableAll();
btnEnableAll.setEnabled(false);
}
}
// =================================================
// Column Header Renderer
// =================================================
protected class MyColumnHeaderRenderer extends JLabel
implements TableCellRenderer {
private static final long serialVersionUID = 1L;
public MyColumnHeaderRenderer() {
super("", SwingConstants.CENTER);
setOpaque(true);
setBackground(TABLE_HEADER_COLOR);
setBorder(BorderFactory.createMatteBorder(0, 0, 1, 1,
TABLE_GRID_COLOR));
setFont(app.getPlainFont());
}
@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus,
int rowIndex, int colIndex) {
setFont(app.getPlainFont());
setText(value.toString());
return this;
}
}
// ======================================================
// Table Cell Renderer
// ======================================================
class MyCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
public MyCellRenderer() {
setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
}
@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
if (value == null) {
setText("");
return this;
}
setFont(app.getPlainFont());
String text = value.toString();
// if (isSelected)
// setBackground(SELECTED_BACKGROUND_COLOR_HEADER);
// setBackground(table.getBackground());
if (!selectionList[row]) {
setBackground(DISABLED_BACKGROUND_COLOR);
} else {
setBackground(table.getBackground());
}
setText(text);
return this;
}
}
// ======================================================
// Row Header
// ======================================================
public class MyRowHeader extends JList implements MouseListener {
private static final long serialVersionUID = 1L;
// DefaultListModel model;
JTable table;
DataPanelD dataPanel;
public MyRowHeader(DataPanelD dataPanel, JTable table) {
super(selectionList);
this.table = table;
this.dataPanel = dataPanel;
setCellRenderer(new RowHeaderRenderer(table));
setSelectionModel(table.getSelectionModel());
this.addMouseListener(this);
}
class RowHeaderRenderer extends JLabel implements ListCellRenderer {
private static final long serialVersionUID = 1L;
private ImageIcon iconChecked, iconUnChecked;
RowHeaderRenderer(JTable table) {
DataCheckBoxIcon cbIcon = new DataCheckBoxIcon(13);
iconUnChecked = cbIcon.createCheckBoxImageIcon(false, false);
iconChecked = cbIcon.createCheckBoxImageIcon(true, false);
setOpaque(true);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(0, 0, 1, 1,
GColorD.getAwtColor(
GeoGebraColorConstants.TABLE_GRID_COLOR)),
BorderFactory.createEmptyBorder(0, 5, 0, 2)));
setHorizontalAlignment(LEFT);
setFont(table.getFont());
}
@Override
public Component getListCellRendererComponent(JList list,
Object value, int index, boolean isSelected,
boolean cellHasFocus) {
setText("" + (index + 1));
// add/remove icons
if ((Boolean) value) {
setIcon(iconChecked);
} else {
setIcon(iconUnChecked);
}
if (!(Boolean) value) {
setBackground(DISABLED_BACKGROUND_COLOR);
} else {
setBackground(TABLE_HEADER_COLOR);
}
return this;
}
}
@Override
public void mouseClicked(MouseEvent e) {
// only handle mouse pressed
}
@Override
public void mouseEntered(MouseEvent arg0) {
// only handle mouse pressed
}
@Override
public void mouseExited(MouseEvent arg0) {
// only handle mouse pressed
}
@Override
public void mousePressed(MouseEvent e) {
// check if we clicked in checkbox icon area
int index = this.locationToIndex(e.getPoint());
Rectangle rect = getCellBounds(index, index);
boolean iconClicked = rect != null && e.getX() - rect.x < 16; // distance
// from
// left
// border
if (iconClicked) {
// icon clicked: toggle enable/disable data
selectionList[this.getSelectedIndex()] = !selectionList[this
.getSelectedIndex()];
statController.updateSelectedDataList(this.getSelectedIndex(),
selectionList[this.getSelectedIndex()]);
btnEnableAll.setEnabled(!isAllEnabled());
table.repaint();
repaint();
return;
}
}
@Override
public void mouseReleased(MouseEvent arg0) {
// only handle mouse pressed
}
public void enableAll() {
for (int i = 0; i < selectionList.length; ++i) {
if (!selectionList[i]) {
statController.updateSelectedDataList(i, true);
selectionList[i] = true;
}
}
rowHeader.repaint();
table.repaint();
}
public boolean isAllEnabled() {
for (int i = 0; i < selectionList.length; ++i) {
if (!selectionList[i]) {
return false;
}
}
return true;
}
}
public static class DataCheckBoxIcon {
public int csize;
public DataCheckBoxIcon(int csize) {
this.csize = csize;
}
public ImageIcon createCheckBoxImageIcon(boolean checked,
boolean highlighted) {
DataCheckBoxIcon cbIcon = new DataCheckBoxIcon(13);
BufferedImage image = new BufferedImage(13, 13,
(BufferedImage.TYPE_INT_ARGB));
ImageIcon icon = new ImageIcon(image);
Graphics2D g2d = image.createGraphics();
CheckBoxIcon.paintIcon(checked, highlighted, new GGraphics2DD(g2d),
0, 0, cbIcon.csize);
return icon;
}
}
}