package gui.views;
import gui.bsvComponents.BSVSpinner;
import gui.bsvComponents.ColorRenderer;
import gui.bsvComponents.NumberCellRenderer;
import gui.main.EventController;
import gui.settings.Settings;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.util.Observable;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTable;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import controller.DataHub;
import controller.SelectionController;
import controller.SubspaceController;
import db.DatabaseAccessException;
/**
* Provides a table view of the current data. Allows the user to sort it according to various parameters and do
* operations on it.
*/
public class TableView extends ViewPanel {
private static final long serialVersionUID = 281259919304409364L;
/**
* The actual table.
*/
private JTable table;
/**
* The TableModel that supplies the table with data.
*/
private DataTableModel dtm;
/**
* JScrollPane to allow scrolling.
*/
private JScrollPane scrollPane;
/**
* Sidebar for table options.
*/
private JPanel sidebar;
/**
* SelectAction for Button and Shortcut.
*/
private final AbstractAction selectAction;
/**
* DeselectAction for Button and Shortcut.
*/
private final AbstractAction deselectAction;
/**
* SelectAllAction for Button and Shortcut.
*/
private final AbstractAction selectAllAction;
/**
* DeselectAllAction for Button and Shortcut.
*/
private final AbstractAction deselectAllAction;
/**
* Sets the standard feature column width.
*/
private int featureColumnWidth = 100;
/**
* Sets the standard number of decimal places.
*/
private int decimalPlaces = 2;
/**
* The width of the id column.
*/
private static final int ID_COLUMN_WIDTH = 60;
/**
* The width of the color column.
*/
private static final int COLOR_COLUMN_WIDTH = 50;
/**
* The width of the selected column.
*/
private static final int IS_SELECTED_WIDTH = 20;
/**
* The minimum allowed decimal places.
*/
private static final int MIN_DECIMAL_PLACES = 0;
/**
* The maximum allowed decimal places.
*/
private static final int MAX_DECIMAL_PLACES = 9;
/**
* The minimum allowed widths of the feature column.
*/
private static final int MIN_FEATURE_WIDTH = 40;
/**
* The maximum allowed width of the feature column.
*/
private static final int MAX_FEATURE_WIDTH = 200;
/**
* Sets stepping for feature width spinner.
*/
private static final int FEATURE_WIDTH_STEP = 5;
/**
* Constructs a new table view.
*
* @param dataHub
* reference to DataHub.
* @param selectionController
* reference to SelectionController.
* @param subspaceController
* the preinitialized SubspaceController.
*/
public TableView(DataHub dataHub, SelectionController selectionController, SubspaceController subspaceController) {
super(dataHub, selectionController, subspaceController);
this.selectAction = new SelectAction();
this.deselectAction = new DeselectAction();
this.selectAllAction = new SelectAllAction();
this.deselectAllAction = new DeselectAllAction();
this.setBackground(Color.WHITE);
super.setLayout(new BorderLayout());
boolean valid;
valid = createNewTable();
if (valid) {
this.scrollPane = new JScrollPane(table);
customizeCellRenderer();
customizeColumns();
this.add(scrollPane, BorderLayout.CENTER);
buildSidebar();
} else {
scrollPane = null;
showErrorMessage();
}
}
@Override
public void update(Observable arg0, Object arg1) {
if (this.isVisible()) {
boolean valid;
if (scrollPane != null) {
this.remove(scrollPane);
}
valid = createNewTable();
if (valid) {
customizeCellRenderer();
customizeColumns();
scrollPane = new JScrollPane(table);
// Create the scroll pane and add the table to it.
this.add(scrollPane);
this.validate();
} else {
scrollPane = null;
showErrorMessage();
}
}
}
@Override
public String getName() {
return Settings.getInstance().getResourceBundle().getString("tableName");
}
private void buildSidebar() {
this.sidebar = new JPanel();
this.sidebar.setLayout(new BoxLayout(this.sidebar, BoxLayout.Y_AXIS));
this.add(this.sidebar, BorderLayout.EAST);
JPanel selectPanel = buildSelectPanel();
JPanel tableOptionPanel = buildTableOptionPanel();
JPanel extender = new JPanel();
extender.setMaximumSize(new Dimension(150, 20));
extender.setMinimumSize(new Dimension(150, 0));
extender.setPreferredSize(new Dimension(150, 0));
selectPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Settings
.getInstance().getResourceBundle().getString("tableLabelSelection")));
tableOptionPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), Settings
.getInstance().getResourceBundle().getString("tableLabelOptions")));
this.addToSidebar(selectPanel);
this.sidebar.add(Box.createRigidArea(new Dimension(0, 20)));
this.addToSidebar(tableOptionPanel);
this.addToSidebar(extender);
}
private JPanel buildSelectPanel() {
JPanel result = new JPanel();
result.setLayout(new BoxLayout(result, BoxLayout.Y_AXIS));
// create and add "Select" Button
JButton selectButton = new JButton(new SelectAction());
selectButton.setText(Settings.getInstance().getResourceBundle().getString("tableButtonSelect"));
JPanel selectPanel = new JPanel(new BorderLayout());
selectPanel.add(selectButton);
result.add(selectPanel);
// create and add "Deselect" Button
JButton deselectButton = new JButton(new DeselectAction());
deselectButton.setText(Settings.getInstance().getResourceBundle().getString("tableButtonDeselect"));
JPanel deselectPanel = new JPanel(new BorderLayout());
deselectPanel.add(deselectButton);
result.add(deselectPanel);
// create and add "Select All" Button
JButton selectAllButton = new JButton(new SelectAllAction());
selectAllButton.setText(Settings.getInstance().getResourceBundle().getString("tableButtonSelectAll"));
JPanel selectAllPanel = new JPanel(new BorderLayout());
selectAllPanel.add(selectAllButton);
result.add(selectAllPanel);
// create and add "Deselect All" Button
JButton deselectAllButton = new JButton(new DeselectAllAction());
deselectAllButton.setText(Settings.getInstance().getResourceBundle().getString("tableButtonDeselectAll"));
JPanel deselectAllPanel = new JPanel(new BorderLayout());
deselectAllPanel.add(deselectAllButton);
result.add(deselectAllPanel);
return result;
}
private JPanel buildTableOptionPanel() {
JPanel result = new JPanel();
result.setLayout(new BoxLayout(result, BoxLayout.Y_AXIS));
// create and add Label and Spinner to change number of decimal places
JLabel decimalPlacesLabel = new JLabel(Settings.getInstance().getResourceBundle().getString(
"tableLabelDecimalPlaces"));
JSpinner decimalSpinner = new BSVSpinner(new SpinnerNumberModel(decimalPlaces, MIN_DECIMAL_PLACES,
MAX_DECIMAL_PLACES, 1));
decimalSpinner.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent arg0) {
JSpinner source = (JSpinner) arg0.getSource();
decimalPlaces = (Integer) source.getValue();
customizeCellRenderer();
table.repaint();
}
});
JPanel decimalSpinnerPanel = new JPanel(new BorderLayout());
decimalSpinnerPanel.add(decimalPlacesLabel, BorderLayout.NORTH);
decimalSpinnerPanel.add(decimalSpinner, BorderLayout.CENTER);
result.add(decimalSpinnerPanel);
// create and add Label and Spinner to change width of feature columns
JLabel columnWidth = new JLabel(Settings.getInstance().getResourceBundle().getString("tableLabelColumnWidth"));
JSpinner widthSpinner = new BSVSpinner(new SpinnerNumberModel(featureColumnWidth, MIN_FEATURE_WIDTH,
MAX_FEATURE_WIDTH, FEATURE_WIDTH_STEP));
widthSpinner.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent arg0) {
JSpinner source = (JSpinner) arg0.getSource();
featureColumnWidth = (Integer) source.getValue();
customizeColumns();
table.repaint();
}
});
JPanel columnWidthPanel = new JPanel(new BorderLayout());
columnWidthPanel.add(columnWidth, BorderLayout.NORTH);
columnWidthPanel.add(widthSpinner, BorderLayout.CENTER);
result.add(columnWidthPanel);
return result;
}
/**
* Adds component to sidebar, used for tools and so forth.
*
* @param component
* component that should be added.
*/
protected void addToSidebar(JComponent component) {
component.setMaximumSize(new Dimension((int) component.getMaximumSize().getWidth(), (int) component
.getPreferredSize().getHeight()));
component.setAlignmentX(Component.LEFT_ALIGNMENT);
this.sidebar.add(component);
}
/**
* Gets the currently highlighted rows in the table and tells the SelectionController to select them.
*/
private void selectMarkedCells() {
int[] selectedRows = table.getSelectedRows();
int[] newSelection = new int[selectedRows.length];
for (int i = 0; i < newSelection.length; i++) {
newSelection[i] = new Integer(table.getValueAt(selectedRows[i], DataTableModel.ID_COLUMN).toString());
}
selectionController.deleteObserver(this);
selectionController.select(newSelection);
selectionController.addObserver(this);
table.clearSelection();
this.validate();
}
/**
* Gets the currently highlighted rows in the table and tells the SelectionController to deselect them.
*/
private void deselectMarkedCells() {
int[] selectedRows = table.getSelectedRows();
int[] newSelection = new int[selectedRows.length];
for (int i = 0; i < newSelection.length; i++) {
newSelection[i] = new Integer(table.getValueAt(selectedRows[i], DataTableModel.ID_COLUMN).toString());
}
selectionController.deleteObserver(this);
selectionController.unselect(newSelection);
selectionController.addObserver(this);
table.clearSelection();
this.validate();
}
/**
* Notifies the SelectionController to select every row.
*/
private void selectAll() {
int[] selectedIds = new int[table.getRowCount()];
for (int i = 0; i < selectedIds.length; i++) {
selectedIds[i] = new Integer(table.getValueAt(i, DataTableModel.ID_COLUMN).toString());
}
selectionController.deleteObserver(this);
selectionController.select(selectedIds);
selectionController.addObserver(this);
this.table.repaint();
}
/**
* Notifies the SelectionController to reset selection.
*/
private void deselectAll() {
selectionController.deleteObserver(this);
selectionController.reset();
selectionController.addObserver(this);
this.table.repaint();
}
/**
* Creates a new table with the latest data and stores it in a table member.
*
* @return true if result is valid.
*/
private boolean createNewTable() {
try {
dtm = new DataTableModel(this.subspaceController.getActiveSubspace().getFeatures(), this.dataHub.getData(),
selectionController, this);
} catch (DatabaseAccessException e) {
return false;
}
table = new JTable(dtm);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.setColumnSelectionAllowed(false);
table.setAutoCreateRowSorter(true);
table.setAutoCreateColumnsFromModel(true);
return true;
}
/**
* Sets the accurate cell renderers for the table columns.
*/
private void customizeCellRenderer() {
table.getColumnModel().getColumn(DataTableModel.COLOR_COLUMN).setCellRenderer(new ColorRenderer(table));
table.getColumnModel().getColumn(DataTableModel.IS_SELECTED_COLUMN).setCellRenderer(
table.getDefaultRenderer(Boolean.class));
table.getColumnModel().getColumn(DataTableModel.ID_COLUMN).setCellRenderer(new NumberCellRenderer(0));
// set feature columns width
for (int i = DataTableModel.FIRST_FEATURE_COLUMN; i < table.getColumnCount(); i++) {
table.getColumnModel().getColumn(i).setCellRenderer(new NumberCellRenderer(decimalPlaces));
}
}
/**
* Sets the accurate column widths.
*/
private void customizeColumns() {
// set isSelected column width
table.getColumnModel().getColumn(DataTableModel.IS_SELECTED_COLUMN).setMinWidth(IS_SELECTED_WIDTH);
table.getColumnModel().getColumn(DataTableModel.IS_SELECTED_COLUMN).setMaxWidth(IS_SELECTED_WIDTH);
table.getColumnModel().getColumn(DataTableModel.IS_SELECTED_COLUMN).setPreferredWidth(IS_SELECTED_WIDTH);
// set color column width
table.getColumnModel().getColumn(DataTableModel.COLOR_COLUMN).setMinWidth(COLOR_COLUMN_WIDTH);
table.getColumnModel().getColumn(DataTableModel.COLOR_COLUMN).setMaxWidth(COLOR_COLUMN_WIDTH);
table.getColumnModel().getColumn(DataTableModel.COLOR_COLUMN).setPreferredWidth(COLOR_COLUMN_WIDTH);
// set id column width
table.getColumnModel().getColumn(DataTableModel.ID_COLUMN).setMinWidth(ID_COLUMN_WIDTH);
table.getColumnModel().getColumn(DataTableModel.ID_COLUMN).setMaxWidth(ID_COLUMN_WIDTH);
table.getColumnModel().getColumn(DataTableModel.ID_COLUMN).setPreferredWidth(ID_COLUMN_WIDTH);
// set feature columns width
for (int i = DataTableModel.FIRST_FEATURE_COLUMN; i < table.getColumnCount(); i++) {
table.getColumnModel().getColumn(i).setMinWidth(featureColumnWidth);
table.getColumnModel().getColumn(i).setMaxWidth(featureColumnWidth);
table.getColumnModel().getColumn(i).setPreferredWidth(featureColumnWidth);
}
}
@Override
public void setVisible(boolean visible) {
if (visible) {
super.setVisible(visible);
this.update(null, null);
EventController.getInstance().registerKeyTarget(this);
EventController.getInstance().setAction(selectAction, "eventSetSelection");
EventController.getInstance().setAction(deselectAction, "eventUnsetSelection");
EventController.getInstance().setAction(selectAllAction, "eventAllSelection");
EventController.getInstance().setAction(deselectAllAction, "eventResetSelection");
} else {
EventController.getInstance().removeAction("eventSetSelection");
EventController.getInstance().removeAction("eventUnsetSelection");
EventController.getInstance().removeAction("eventAllSelection");
EventController.getInstance().removeAction("eventResetSelection");
super.setVisible(visible);
}
}
private class SelectAction extends AbstractAction {
private static final long serialVersionUID = 517346861972620697L;
@Override
public void actionPerformed(ActionEvent e) {
selectMarkedCells();
}
}
private class SelectAllAction extends AbstractAction {
private static final long serialVersionUID = 3299566724985145546L;
@Override
public void actionPerformed(ActionEvent e) {
selectAll();
}
}
private class DeselectAction extends AbstractAction {
private static final long serialVersionUID = -5893751844205324627L;
@Override
public void actionPerformed(ActionEvent e) {
deselectMarkedCells();
}
}
private class DeselectAllAction extends AbstractAction {
private static final long serialVersionUID = 6191139922770259940L;
@Override
public void actionPerformed(ActionEvent e) {
deselectAll();
}
}
private void showErrorMessage() {
JOptionPane.showMessageDialog(this, Settings.getInstance().getResourceBundle().getString("tableError"));
}
}