/*
* RapidMiner
*
* Copyright (C) 2001-2011 by Rapid-I and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapid-i.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.tools.dialogs.wizards.dataimport;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import com.rapidminer.gui.tools.ExtendedJScrollPane;
import com.rapidminer.gui.tools.ExtendedJTable;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.gui.tools.UpdateQueue;
import com.rapidminer.operator.io.AbstractDataReader;
import com.rapidminer.operator.io.AbstractDataReader.AttributeColumn;
import com.rapidminer.tools.Ontology;
import com.rapidminer.tools.Tools;
/**
* An editor to declare meta data information (attributes, value types, roles, ...) in a importing wizard
*
* @author Tobias Malbrecht
* @author Sebastian Loh (22.04.2010)
*/
public class MetaDataDeclarationEditor extends JPanel {
private static final long serialVersionUID = -520323914589387512L;
/**
* upper table, actually a simulation of an multi-row header for the previewTable
*/
private MetaDataTable metadataTable;
/** the preview table which contains a preview of the data */
private PreviewTable previewTable;
/**
* Listener to recognize if a column margin of the {@link MetaDataDeclarationEditor#metadataTable} changed. Not very
* nice to declare it globally but doesn't work otherwise.
*/
TableColumnModelListener metadataColumnListener;
private AbstractDataReader reader = null;
private UpdateQueue updateQueue = new UpdateQueue("ImportWizardDataRefresher");
/**
* The font color of an error cell.
*/
private static final Color BLUE = new Color(52, 86, 164); // new Color(217, 69, 69);
/** the background color of error cells */
private static final Color YELLOW = new Color(245, 223, 171); // new Color(255, 234, 128);
private final Color backGroundGray;
/** the row number of the role selection */
private static final int ROLE_ROW = 3;
/** the row number of the column selection editor (check boxes) */
private static final int IS_SELECTED_ROW = 2;
/** the row number of the rol type selection */
private static final int VALUE_TYPE_ROW = 1;
private static final int ATTRIBUTE_NAME_ROW = 0;
// private boolean lock = false;
/**
*
*/
public MetaDataDeclarationEditor(AbstractDataReader reader, final boolean showMetaDataEditor) {
super(new BorderLayout());
backGroundGray = this.getBackground();
this.reader = reader;
metadataTable = new MetaDataTable();
previewTable = new PreviewTable();
ExtendedJScrollPane metadataPane = new ExtendedJScrollPane(metadataTable);
ExtendedJScrollPane dataPane = new ExtendedJScrollPane(previewTable) {
private static final long serialVersionUID = 1L;
// Only show column header if there is no MetaDataTable
@Override
public void setColumnHeaderView(Component view) {
if (!showMetaDataEditor) {
super.setColumnHeaderView(view);
}
} // alternative work around: dataPane.setColumnHeader(null);
};
metadataColumnListener = new TableColumnModelListener() {
@Override
public void columnSelectionChanged(ListSelectionEvent e) {
}
@Override
public void columnRemoved(TableColumnModelEvent e) {
}
@Override
public void columnMoved(TableColumnModelEvent e) {
}
@Override
public void columnMarginChanged(ChangeEvent e) {
assert previewTable.getColumnCount() == metadataTable.getColumnCount();
for (int i = 0; i < previewTable.getColumnCount(); i++) {
int columnwidth = metadataTable.getColumnModel().getColumn(i).getWidth();
int oldwidth = previewTable.getColumnModel().getColumn(i).getPreferredWidth();
if (oldwidth == columnwidth) {
continue;
}
previewTable.getColumnModel().getColumn(i).setPreferredWidth(columnwidth);
}
previewTable.doLayout();
previewTable.repaint();
}
@Override
public void columnAdded(TableColumnModelEvent e) {
}
};
if (showMetaDataEditor) {
metadataPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
// synchronize the the scroll bars:
metadataPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
JScrollBar bar = metadataPane.getVerticalScrollBar();
JScrollBar dummyBar = new JScrollBar() {
private static final long serialVersionUID = 1L;
@Override
public void paint(Graphics g) {
// Color c = getParent().getBackground();
g.setColor(backGroundGray);
g.fillRect(0, 0, this.getSize().width, this.getSize().height);
}
};
dummyBar.setPreferredSize(bar.getPreferredSize());
metadataPane.setVerticalScrollBar(dummyBar);
final JScrollBar bar1 = metadataPane.getHorizontalScrollBar();
JScrollBar bar2 = dataPane.getHorizontalScrollBar();
bar2.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
bar1.setValue(e.getValue());
}
});
/*
* Synchronize the column margins if they are changed:
*/
// propagate metadataTable margin changes to previewTable:
metadataTable.getColumnModel().addColumnModelListener(metadataColumnListener);
metadataPane.setPreferredSize(new Dimension(400, 103)); // ugly
this.add(metadataPane, BorderLayout.NORTH);
}
this.add(dataPane, BorderLayout.CENTER);
this.doLayout();
updateQueue.start();
}
public void setData(List<Object[]> data) {
previewTable.setData(data);
metadataTable.updateTableStructure();
}
/*
* =========================
*
* BEGIN PRIVATE CLASSES:
*
* ValueTypeTable ValueTypeModle ValueTypeCellEditor CheckBoxCellEditor
*
* DataTable DataModle DataCellEditor
*
* ========================
*/
private class MetaDataTable extends ExtendedJTable implements TableModelListener {
private static final long serialVersionUID = 1L;
private final MetaDataModel fixedHeaderModel = new MetaDataModel();
private ValueTypeCellEditor valueTypeCellEditor = null;
private ValueTypeCellEditor valueTypeCellRenderer = null;
private ColumnSelectionCellEditor checkBoxCellEditor = null;
private ColumnSelectionCellEditor checkBoxCellRenderer = null;
private RoleSelectionCellEditor roleCellEditor = null;
private RoleSelectionCellEditor roleCellRenderer = null;
// is used to select or deselect all columns
private ColumnSelectionCellEditor globalCheckBoxCellEditor = new ColumnSelectionCellEditor();
private MetaDataTable() {
super(null, false, false, false, false, false);
valueTypeCellEditor = new ValueTypeCellEditor();
valueTypeCellRenderer = new ValueTypeCellEditor();
checkBoxCellEditor = new ColumnSelectionCellEditor();
checkBoxCellRenderer = new ColumnSelectionCellEditor();
roleCellEditor = new RoleSelectionCellEditor();
roleCellRenderer = new RoleSelectionCellEditor();
setModel(fixedHeaderModel);
setDefaultRenderer(String.class, new DefaultTableCellRenderer() {
private static final long serialVersionUID = 1L;
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
// RowNo. column
if (column == 0) {
// comp = table.getTableHeader().getDefaultRenderer().getTableCellRendererComponent(table,
// value, isSelected, hasFocus, row, column);
if (row == IS_SELECTED_ROW) {
return globalCheckBoxCellEditor.getTableCellEditorComponent(table, value, isSelected, row, column);
}
comp.setBackground(backGroundGray);
return comp;
} else {
column--; // decrease to not count static row column when accessing data reader
if (row == ATTRIBUTE_NAME_ROW) {
comp.setForeground(Color.BLACK);
if (!reader.hasParseErrorInColumn(column)) {
comp.setBackground(Color.WHITE);
} else {
comp.setBackground(YELLOW);
}
if (!reader.getAttributeColumn(column).isActivated()) {
comp.setForeground(Color.LIGHT_GRAY);
}
return comp;
}
if (row == VALUE_TYPE_ROW) {
return valueTypeCellRenderer.getTableCellEditorComponent(table, value, isSelected, row, column + 1);
}
if (row == IS_SELECTED_ROW) {
return checkBoxCellRenderer.getTableCellEditorComponent(table, value, isSelected, row, column + 1);
}
if (row == ROLE_ROW) {
return roleCellRenderer.getTableCellEditorComponent(table, value, isSelected, row, column + 1);
}
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column + 1);
}
}
});
setColumnSelectionAllowed(false);
setRowSelectionAllowed(false);
setCellSelectionEnabled(false);
this.getModel().fireTableStructureChanged();
}
@Override
public MetaDataModel getModel() {
return fixedHeaderModel;
}
public void updateTableStructure() {
this.getModel().fireTableStructureChanged();
this.packColumn();
// this.pack();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// fire the column margin changed event to sync
// the column margins
metadataColumnListener.columnMarginChanged(null);
}
});
}
@Override
public JPopupMenu createPopupMenu() {
JPopupMenu popUp = super.createPopupMenu();
// JMenuItem deselect = new JMenuItem(new ResourceAction("wizard.deselect_all") {
//
// @Override
// public void actionPerformed(ActionEvent e) {
// // TODO Auto-generated method stub
//
// }
// });
// TODO implement selectAll and deselectALL Columns MenuItems here
// popUp.add(deselect);
return popUp;
}
@Override
public TableCellEditor getCellEditor(int row, int column) {
if (column == 0) { // RowNo.
if (row == IS_SELECTED_ROW) {
return globalCheckBoxCellEditor;
}
return super.getCellEditor();
}
column--;
if (row == VALUE_TYPE_ROW) {
return valueTypeCellEditor;
}
if (row == IS_SELECTED_ROW) {
return checkBoxCellEditor;
}
if (row == ROLE_ROW) {
return roleCellEditor;
}
// ATTRIBUTE_NAME_ROW
return super.getCellEditor(row, column);
}
@Override
public boolean isCellEditable(int row, int column) {
if (column == 0) { // RowNo.
if (row == IS_SELECTED_ROW) {
return true;
}
return false;
}
if (row <= Math.max(Math.max(Math.max(ATTRIBUTE_NAME_ROW, VALUE_TYPE_ROW), IS_SELECTED_ROW), ROLE_ROW)) {
return true;
}
return false;
}
}
/**
* @author Sebastian Loh (23.04.2010)
*
* The table model for the fixed table that contains only the table header (attribute names) and the
* attributes value type.
*
*/
private class MetaDataModel extends AbstractTableModel {
private static final long serialVersionUID = -8096935282615030186L;
@Override
public int getColumnCount() {
// return previewTable.getColumnCount();
return reader.getColumnCount() + 1;
}
@Override
public int getRowCount() {
return Math.max(Math.max(VALUE_TYPE_ROW, IS_SELECTED_ROW), ROLE_ROW) + 1;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) { // RowNo.
if (rowIndex == IS_SELECTED_ROW) {
// return true for global column selection editor if at least one column is selected.
for (AttributeColumn col : reader.getAllAttributeColumns()) {
if (col.isActivated()) {
return true;
}
}
return false;
}
return "";
}
columnIndex--;
if (rowIndex == ATTRIBUTE_NAME_ROW) {
return reader.getAttributeColumn(columnIndex).getName();
}
if (rowIndex == VALUE_TYPE_ROW) {
return Ontology.VALUE_TYPE_NAMES[reader.getAttributeColumn(columnIndex).getValueType()];
}
if (rowIndex == IS_SELECTED_ROW) {
return reader.getAttributeColumn(columnIndex).isActivated();
}
if (rowIndex == ROLE_ROW) {
return reader.getAttributeColumn(columnIndex).getRole();
}
return null;
}
@Override
public String getColumnName(int columnIndex) {
if (columnIndex == 0) { // RowNo.
return "Row No.";
}
columnIndex--;
// here the table header is implicitly set to the attributes name
return Tools.getExcelColumnName(columnIndex);
// return reader.getAttributeColumn(columnIndex).getName();
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return String.class;
}
@Override
public void setValueAt(Object value, int row, int column) {
if (column == 0) { // RowNo.
if (row == IS_SELECTED_ROW) {
for (AttributeColumn col : reader.getAllAttributeColumns()) {
col.activateColumn((Boolean) value);
}
repaint();
}
return;
}
column--;
if (row == ATTRIBUTE_NAME_ROW) {
reader.setAttributeNamesDefinedByUser(true);
reader.getAttributeColumn(column).setName((String) value);
}
if (row == VALUE_TYPE_ROW) {
// update only if its not the same value
if (reader.getAttributeColumn(column).getValueType() != Ontology.ATTRIBUTE_VALUE_TYPE.mapName(value.toString())) {
reader.getAttributeColumn(column).setValueType(Ontology.ATTRIBUTE_VALUE_TYPE.mapName(value.toString()));
}
}
if (row == IS_SELECTED_ROW) {
reader.getAttributeColumn(column).activateColumn((Boolean) value);
}
if (row == ROLE_ROW) {
String role = (String) value;
if (role.equals(AttributeColumn.REGULAR)) {
reader.getAttributeColumn(column).setRole(role);
} else {
for (AttributeColumn attColumn : reader.getAllAttributeColumns()) {
if (attColumn.getRole().equals(role)) {
attColumn.setRole(AttributeColumn.REGULAR);
}
}
reader.getAttributeColumn(column).setRole(role);
fireTableDataChanged();
}
}
repaint();
}
// private void updatePreview() {
// updateQueue.execute(new Runnable() {
// @Override
// public void run() {
// // long running task
//
// SwingUtilities.invokeLater(new Runnable() {
// @Override
// public void run() {
// List<Object[]> result;
// try {
// result = reader.getPreviewAsList();
//
// MetaDataDeclarationEditor.this.previewTable.setData(result);
// // fire the column margin changed event to sync
// // the column margins
// metadataColumnListener.columnMarginChanged(null);
// metadataTable.repaint();
// } catch (OperatorException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// }
// });
// }
// });
// }
}
private class ValueTypeCellEditor extends DefaultCellEditor {
private static final long serialVersionUID = 7954919612214223430L;
// DropDown menus in the second row to select the value type
public ValueTypeCellEditor() {
super(new JComboBox());
ComboBoxModel model = new DefaultComboBoxModel() {
private static final long serialVersionUID = 914764579359633239L;
private String[] valueTypes;
{
int[] types = { Ontology.BINOMINAL, Ontology.NOMINAL, Ontology.INTEGER, Ontology.REAL, Ontology.DATE_TIME, Ontology.DATE, Ontology.TIME };
valueTypes = new String[types.length];
for (int i = 0; i < types.length; i++) {
valueTypes[i] = Ontology.ATTRIBUTE_VALUE_TYPE.mapIndex(types[i]);
}
}
@Override
public Object getElementAt(int index) {
return valueTypes[index];
}
@Override
public int getSize() {
return valueTypes.length;
}
};
((JComboBox) super.getComponent()).setEnabled(true);
((JComboBox) super.getComponent()).setModel(model);
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
Component comp = super.getTableCellEditorComponent(table, value, isSelected, row, column);
if (!reader.getAttributeColumn(column - 1).isActivated()) {
comp.setForeground(Color.LIGHT_GRAY);
} else if (reader.hasParseErrorInColumn(column - 1)) {
comp.setForeground(BLUE);
} else {
comp.setForeground(Color.BLACK);
}
return comp;
}
}
private class ColumnSelectionCellEditor extends DefaultCellEditor implements TableCellEditor {
private static final long serialVersionUID = 1L;
private ColumnSelectionCellEditor() {
super(new JCheckBox());
((JCheckBox) (this.getComponent())).setSelected(true);
}
@Override
public Component getTableCellEditorComponent(JTable arg0, Object arg1, boolean arg2, int row, int column) {
((JCheckBox) (this.getComponent())).setSelected((Boolean) metadataTable.getValueAt(row, column));
return ((this.getComponent()));
}
@Override
public Object getCellEditorValue() {
return ((JCheckBox) (this.getComponent())).isSelected();
}
}
private class RoleSelectionCellEditor extends DefaultCellEditor {
private static final long serialVersionUID = 6077812831224991517L;
public RoleSelectionCellEditor() {
super(new JComboBox(AbstractDataReader.ROLE_NAMES.toArray()));
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
Component comp = super.getTableCellEditorComponent(table, value, isSelected, row, column);
if (!reader.getAttributeColumn(column - 1).isActivated()) {
comp.setForeground(Color.LIGHT_GRAY);
} else {
comp.setForeground(Color.BLACK);
}
return comp;
}
}
private class PreviewTable extends ExtendedJTable {
private static final long serialVersionUID = 1L;
private PreviewModel dataModel = new PreviewModel();
private PreviewTable() {
super(null, false, false, false, false, false);
setModel(dataModel);
// new cell renderer which paints the text gray if the column is not
// activated for the import. It also alternates the background color
// and
// shows error cells
setDefaultRenderer(String.class, new DefaultTableCellRenderer() {
private static final long serialVersionUID = 1L;
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
Component comp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
if (col == 0) {
if (reader.hasParseErrorInRow(Integer.parseInt((String) dataModel.getValueAt(row, 0)) - 1)) {
comp.setForeground(BLUE);
comp.setBackground(YELLOW);
} else {
comp.setForeground(Color.BLACK);
comp.setBackground(backGroundGray);
}
return comp;
}
col--;
if (reader.getAttributeColumn(col).isActivated()) {
if (reader.hasParseError(col, Integer.parseInt((String) dataModel.getValueAt(row, 0)) - 1)) {
// if (reader.isErrorTolerant()) {
// comp.setForeground(Color.BLUE);
// comp.setBackground(SwingTools.LIGHT_YELLOW);
// } else{
comp.setForeground(BLUE);
comp.setBackground(YELLOW);
// }
} else {
comp.setForeground(Color.BLACK);
if (row % 2 == 0) {
comp.setBackground(Color.WHITE);
} else if (reader.getAttributeColumn(col).getRole().equals(AttributeColumn.REGULAR)) {
comp.setBackground(SwingTools.LIGHTEST_BLUE);
} else {
comp.setBackground(SwingTools.LIGHTEST_YELLOW); // appears
// as
// light
// red!
}
}
} else {
comp.setForeground(Color.LIGHT_GRAY);
if (row % 2 == 0) {
comp.setBackground(Color.WHITE);
} else {
comp.setBackground(SwingTools.LIGHTEST_BLUE);
}
}
return comp;
}
});
// columns are selectable
setColumnSelectionAllowed(true);
// rows are selectable
setRowSelectionAllowed(true);
// single cells are not selectable
setCellSelectionEnabled(false);
}
public void setData(List<Object[]> data) {
dataModel.setData(data);
}
// @Override
// public TableCellRenderer getCellRenderer(int row, int column){
// TableCellRenderer cellRenderer = super.getCellRenderer(row, col);
// cellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column)
// return ;
// }
@Override
public TableCellEditor getCellEditor(int row, int column) {
TableCellEditor cellEditor = super.getCellEditor(row, column);
return cellEditor;
}
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
// fit to column margin and other actions does not work due the
// synchronization of the two tables
@Override
public JPopupMenu createPopupMenu() {
// return super.createPopupMenu();
return new JPopupMenu();
}
}
private class PreviewModel extends AbstractTableModel {
private static final long serialVersionUID = -8096935282615030186L;
private ArrayList<Object[]> data = null;
private void setData(List<Object[]> data) {
this.data = new ArrayList<Object[]>(data);
this.fireTableStructureChanged();
}
@Override
public int getColumnCount() {
return reader.getColumnCount() + 1;
}
@Override
public String getColumnName(int columnIndex) {
if (columnIndex == 0) {
return "Row No.";
}
return reader.getAttributeColumn(columnIndex - 1).getName();
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return String.class;
}
@Override
public int getRowCount() {
return data != null ? (data.size()) : 0;
}
@Override
public Object getValueAt(int row, int column) {
Object[] values = data.get(row);
if (column == 0) {
return values[column].toString();
}
if (column >= values.length) {
return "";
}
int attributeType = reader.getAttributeColumn(column - 1).getValueType();
if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(attributeType, Ontology.DATE_TIME) || Ontology.ATTRIBUTE_VALUE_TYPE.isA(attributeType, Ontology.DATE_TIME) || Ontology.ATTRIBUTE_VALUE_TYPE.isA(attributeType, Ontology.TIME)) {
try {
return Tools.formatDateTime((Date) values[column]);
} catch (ClassCastException e) {
// do nothing, just return default value
}
}
// default value
return values[column].toString();
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize() throws Throwable {
updateQueue.shutdown();
super.finalize();
}
}