/*
Copyright (C) 2006 EBI
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the itmplied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.biomart.builder.view.gui.diagrams.components;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import org.biomart.builder.model.Column;
import org.biomart.builder.model.DataSet;
import org.biomart.builder.model.Key;
import org.biomart.builder.model.Relation;
import org.biomart.builder.model.Table;
import org.biomart.builder.model.DataSet.DataSetColumn;
import org.biomart.builder.model.DataSet.DataSetTable;
import org.biomart.builder.model.DataSet.DataSetTableType;
import org.biomart.builder.view.gui.diagrams.Diagram;
import org.biomart.builder.view.gui.diagrams.ExplainTransformationDiagram.FakeTable;
import org.biomart.builder.view.gui.diagrams.ExplainTransformationDiagram.RealisedTable;
import org.biomart.common.resources.Resources;
/**
* Table components are box-shaped, and represent an individual table. Inside
* them may appear a number of key or column components, and a button which
* shows or hides the columns. They have a label indicating their full name, and
* a secondary label indicating which schema they belong to.
*
* @author Richard Holland <holland@ebi.ac.uk>
* @version $Revision: 1.49 $, $Date: 2008-02-29 11:09:44 $, modified by
* $Author: rh4 $
* @since 0.5
*/
public class TableComponent extends BoxShapedComponent {
private static final long serialVersionUID = 1;
private static final Color TRANSPARENT_COLOR = new Color(0, 0, 0, 0);
/**
* Background colour for all normal tables.
*/
public static final Color BACKGROUND_COLOUR = Color.PINK;
/**
* Background colour for masked tables.
*/
public static Color MASKED_COLOUR = Color.LIGHT_GRAY;
/**
* Background colour for unrolled tables.
*/
public static Color UNROLLED_COLOUR = Color.CYAN;
private static final Color NORMAL_COLOUR = Color.BLACK;
/**
* Background colour for all ignored tables.
*/
public static Color IGNORE_COLOUR = Color.LIGHT_GRAY;
private static final Font ITALIC_FONT = Font.decode("SansSerif-ITALIC-10");
private static final Font BOLD_FONT = Font.decode("SansSerif-BOLD-10");
private JComponent columnsListPanel;
private GridBagConstraints constraints;
private GridBagLayout layout;
private JButton showHide;
private boolean hidingMaskedCols = false;
private final PropertyChangeListener repaintListener = new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent e) {
TableComponent.this.needsRepaint = true;
}
};
private final PropertyChangeListener recalcListener = new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent e) {
TableComponent.this.needsRecalc = true;
}
};
/**
* This constructor makes a new table component, associated with a
* particular table, and remembers that this component appears in a
* particular diagram. All operations on the component will be related back
* to that diagram where necessary.
*
* @param table
* the table we wish to represent in the diagram.
* @param diagram
* the diagram we wish to make the table appear in.
*/
public TableComponent(final Table table, final Diagram diagram) {
super(table, diagram);
// Table components are set out in a vertical list.
this.layout = new GridBagLayout();
this.setLayout(this.layout);
// Constraints for each component within the table component.
this.constraints = new GridBagConstraints();
this.constraints.gridwidth = GridBagConstraints.REMAINDER;
this.constraints.fill = GridBagConstraints.HORIZONTAL;
this.constraints.anchor = GridBagConstraints.CENTER;
this.constraints.insets = new Insets(0, 2, 0, 2);
// Set the background colour.
this.setForeground(TableComponent.NORMAL_COLOUR);
// Draw our contents.
this.recalculateDiagramComponent();
// Repaint events.
table.addPropertyChangeListener("directModified", this.repaintListener);
// Listen to all relations on this table and repaint when needed.
// We don't need to monitor relations themselves as the entire
// diagram gets recalculated if they change.
for (final Iterator i = table.getRelations().iterator(); i.hasNext();) {
final Relation rel = (Relation) i.next();
rel.addPropertyChangeListener("directModified",
this.repaintListener);
}
// If this is a dataset table, listen to the partition table
// conversion and merge/unrolled signals and repaint on that too.
if (table instanceof DataSetTable) {
final DataSetTable dsTbl = (DataSetTable) table;
if (dsTbl.getType().equals(DataSetTableType.MAIN)) {
dsTbl.getDataSet().addPropertyChangeListener(
"partitionTableApplication", this.repaintListener);
dsTbl.getDataSet().addPropertyChangeListener("partitionTable",
this.repaintListener);
} else {
dsTbl.addPropertyChangeListener("partitionTableApplication",
this.repaintListener);
dsTbl.getFocusRelation().addPropertyChangeListener(
"mergeRelation", this.repaintListener);
dsTbl.getFocusRelation().addPropertyChangeListener(
"unrolledRelation", this.repaintListener);
}
}
// Recalc events.
table.addPropertyChangeListener("tableRename", this.recalcListener);
table.getKeys().addPropertyChangeListener(this.recalcListener);
table.getColumns().addPropertyChangeListener(this.recalcListener);
}
/**
* Gets the table this component is representing.
*
* @return the table we represent.
*/
public Table getTable() {
return (Table) this.getObject();
}
/**
* Is this table component hiding masked columns currently?
*
* @return <tt>true</tt> if it is.
*/
public boolean isHidingMaskedCols() {
return this.hidingMaskedCols;
}
protected void doRecalculateDiagramComponent() {
// Clear subcomponents.
this.getSubComponents().clear();
// Add the table name label.
final JTextField name = new JTextField() {
private static final long serialVersionUID = 1L;
private Color opaqueBackground;
// work around transparency issue in OS X 10.5
public void setOpaque(boolean opaque) {
if (opaque != isOpaque()) {
if (opaque) {
super.setBackground(opaqueBackground);
} else if (opaqueBackground != null) {
opaqueBackground = getBackground();
super.setBackground(TRANSPARENT_COLOR);
}
}
super.setOpaque(opaque);
}
// work around transparency issue in OS X 10.5
public void setBackground(Color color) {
if (isOpaque()) {
super.setBackground(color);
} else {
opaqueBackground = color;
}
}
};
name.setFont(TableComponent.BOLD_FONT);
this.setRenameTextField(name);
this.add(name, this.constraints);
// Add the schema name label.
final JLabel label = new JLabel(this.getTable().getSchema().getName());
label.setFont(TableComponent.ITALIC_FONT);
this.add(label, this.constraints);
// Start masked?
if (TableComponent.this.getTable() instanceof DataSetTable)
this.hidingMaskedCols = ((DataSetTable) TableComponent.this
.getTable()).isTableHideMasked();
// Add a key component as a sub-component of this table,
// for each of the foreign keys in the table.
for (final Iterator i = this.getTable().getKeys().iterator(); i
.hasNext();) {
final Key key = (Key) i.next();
final KeyComponent keyComponent = new KeyComponent(key, this
.getDiagram());
// Add it as a sub-component (internal representation only).
this.addSubComponent(key, keyComponent);
this.getSubComponents().putAll(keyComponent.getSubComponents());
for (int j = 0; j < key.getColumns().length; j++)
key.getColumns()[j].addPropertyChangeListener("columnRename",
this.recalcListener);
// Physically add it to the table component layout.
this.add(keyComponent, this.constraints);
}
// Now the columns, as a vertical list in their own panel.
this.columnsListPanel = new JPanel();
this.columnsListPanel.setLayout(new BoxLayout(this.columnsListPanel,
BoxLayout.Y_AXIS));
// Do a bit of sorting to make them alphabetical first.
final Map sortedColMap = new TreeMap();
for (final Iterator i = this.getTable().getColumns().values()
.iterator(); i.hasNext();) {
final Column col = (Column) i.next();
sortedColMap.put(
col instanceof DataSetColumn ? ((DataSetColumn) col)
.getModifiedName() : col.getName(), col);
}
// If dataset table...
if (this.getTable() instanceof DataSetTable
|| this.getTable() instanceof RealisedTable
|| this.getTable() instanceof FakeTable) {
final JCheckBox hideMaskedButton = new JCheckBox(Resources
.get("hideMaskedTitle"), this.hidingMaskedCols);
hideMaskedButton.setFont(TableComponent.BOLD_FONT);
this.columnsListPanel.add(hideMaskedButton);
hideMaskedButton.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
TableComponent.this.hidingMaskedCols = hideMaskedButton
.isSelected();
if (TableComponent.this.getTable() instanceof DataSetTable)
TableComponent.this.getDiagram().getMartTab()
.getDataSetTabSet().requestTableHideMasked(
(DataSet) TableComponent.this
.getTable().getSchema(),
(DataSetTable) TableComponent.this
.getTable(),
TableComponent.this.hidingMaskedCols);
// Recalculate the diagram.
for (final Iterator i = TableComponent.this
.getSubComponents().values().iterator(); i
.hasNext();) {
final DiagramComponent comp = (DiagramComponent) i
.next();
if (comp instanceof ColumnComponent)
comp.repaintDiagramComponent();
}
}
});
}
// Show/hide the columns panel with a button.
this.showHide = new JButton(Resources.get("showColumnsButton"));
this.showHide.setFont(TableComponent.BOLD_FONT);
this.add(this.showHide, this.constraints);
this.showHide.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
if (TableComponent.this.getState().equals(Boolean.TRUE))
TableComponent.this.setState(Boolean.FALSE);
else
TableComponent.this.setState(Boolean.TRUE);
}
});
this.showHide.setEnabled(sortedColMap.size() > 0);
this.add(this.columnsListPanel, this.constraints);
this.columnsListPanel.setVisible(false);
// Add columns to the list one by one, as column sub-components.
for (final Iterator i = sortedColMap.values().iterator(); i.hasNext();) {
final Column col = (Column) i.next();
final ColumnComponent colComponent = new ColumnComponent(col, this
.getDiagram());
// Add it as a sub-component (internal representation only).
this.addSubComponent(col, colComponent);
this.getSubComponents().putAll(colComponent.getSubComponents());
// Physically add it to the list of columns.
this.columnsListPanel.add(colComponent);
}
// Recalculate the diagram if masking as the recalc can sometimes
// lose it.
if (this.hidingMaskedCols)
for (final Iterator i = TableComponent.this.getSubComponents()
.values().iterator(); i.hasNext();) {
final DiagramComponent comp = (DiagramComponent) i.next();
if (comp instanceof ColumnComponent)
comp.repaintDiagramComponent();
}
// Set our initial display state as false, which means columns are
// hidden.
this.setState(Boolean.FALSE);
}
public void performRename(final String newName) {
this.getDiagram().getMartTab().getDataSetTabSet()
.requestRenameDataSetTable((DataSetTable) this.getTable(),
newName);
}
public String getEditableName() {
return this.getTable() instanceof DataSetTable ? ((DataSetTable) this
.getTable()).getModifiedName() : this.getTable().getName();
}
public String getDisplayName() {
return this.getEditableName();
}
public void setState(final Object state) {
// For us, state is TRUE if we want the columns panel visible.
if (state != null && state.equals(Boolean.TRUE)) {
if (this.getState() != null
&& this.getState().equals(Boolean.FALSE))
this.columnsListPanel.setVisible(true);
this.showHide.setText(Resources.get("hideColumnsButton"));
} else {
if (this.getState() != null && this.getState().equals(Boolean.TRUE))
this.columnsListPanel.setVisible(false);
this.showHide.setText(Resources.get("showColumnsButton"));
}
// Delegate upwards, so that the state is remembered for later.
super.setState(state);
}
}