/* 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; import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import javax.swing.ImageIcon; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTable; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.table.DefaultTableModel; import org.biomart.builder.model.Column; import org.biomart.builder.model.ComponentStatus; import org.biomart.builder.model.DataSet; import org.biomart.builder.model.Key; import org.biomart.builder.model.Relation; import org.biomart.builder.model.Schema; import org.biomart.builder.model.Table; import org.biomart.builder.model.Key.ForeignKey; import org.biomart.builder.model.Key.PrimaryKey; import org.biomart.builder.model.Relation.Cardinality; import org.biomart.builder.view.gui.MartTabSet.MartTab; import org.biomart.builder.view.gui.diagrams.AllSchemasDiagram; import org.biomart.builder.view.gui.diagrams.Diagram; import org.biomart.builder.view.gui.diagrams.SchemaDiagram; import org.biomart.builder.view.gui.diagrams.contexts.DiagramContext; import org.biomart.builder.view.gui.dialogs.KeyDialog; import org.biomart.builder.view.gui.dialogs.SchemaConnectionDialog; import org.biomart.common.resources.Resources; import org.biomart.common.utils.Transaction; import org.biomart.common.view.gui.LongProcess; import org.biomart.common.view.gui.SwingWorker; import org.biomart.common.view.gui.dialogs.ProgressDialog; import org.biomart.common.view.gui.dialogs.StackTrace; /** * This tabset has one tab for the diagram which represents all schemas, and one * tab each for each schema in the mart. It provides methods for working with a * given schema, such as adding or removing them, or grouping them together. It * can update itself based on the schemas in the mart on request. * <p> * Like a diagram, it can have a {@link DiagramContext} associated with it. * Whenever this context changes, all {@link Diagram} instances represented in * the tabs have the same context applied. * * @author Richard Holland <holland@ebi.ac.uk> * @version $Revision: 1.114 $, $Date: 2008-02-21 09:35:26 $, modified by * $Author: rh4 $ * @since 0.5 */ public class SchemaTabSet extends JTabbedPane { private static final long serialVersionUID = 1; private AllSchemasDiagram allSchemasDiagram; private DiagramContext diagramContext; private MartTab martTab; // Schema hashcodes change, so we must use a double-list. private final Map schemaToDiagram = new HashMap(); // Make a listener which knows how to handle masking and // renaming. private final PropertyChangeListener updateListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { final Schema sch = (Schema) evt.getSource(); if (evt.getPropertyName().equals("name")) { // Rename in diagram set. SchemaTabSet.this.schemaToDiagram.put(evt.getNewValue(), SchemaTabSet.this.schemaToDiagram.remove(evt .getOldValue())); SchemaTabSet.this.setTitleAt(SchemaTabSet.this .indexOfTab((String) evt.getOldValue()), (String) evt .getNewValue()); } else if (evt.getPropertyName().equals("masked")) { // For masks, if unmasking, add a tab, otherwise // remove the tab. final boolean masked = ((Boolean) evt.getNewValue()) .booleanValue(); if (masked) SchemaTabSet.this.removeSchemaTab(sch.getName(), true); else SchemaTabSet.this.addSchemaTab(sch, false); } } }; // A listener for updating our tabs. private final PropertyChangeListener tabListener = new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent evt) { // Listen to masked schema and rename // schema events on each new schema added // regardless of tab presence. // Mass change. Copy to prevent concurrent mods. final Set oldSchs = new HashSet(SchemaTabSet.this.schemaToDiagram .keySet()); for (final Iterator i = SchemaTabSet.this.martTab.getMart() .getSchemas().values().iterator(); i.hasNext();) { final Schema sch = (Schema) i.next(); if (!oldSchs.remove(sch.getName())) { // Single-add. if (!sch.isMasked()) SchemaTabSet.this.addSchemaTab(sch, true); sch.addPropertyChangeListener("masked", SchemaTabSet.this.updateListener); sch.addPropertyChangeListener("name", SchemaTabSet.this.updateListener); } } for (final Iterator i = oldSchs.iterator(); i.hasNext();) SchemaTabSet.this.removeSchemaTab((String) i.next(), true); } }; /** * Creates a new set of tabs to represent the schemas in a mart. The mart is * obtained by using methods on the mart tab passed in as a parameter. The * mart tab is the parent tab that this schema tabset will appear inside the * tabs of. * * @param martTab * the parent tab this schema tabset will appear inside the tabs * of. */ public SchemaTabSet(final MartTab martTab) { super(); // Remember the mart tabset we are shown inside. this.martTab = martTab; // Add the all-schemas overview tab. This tab displays a diagram // in which all schemas appear, linked where necessary by external // relations. This diagram could be quite large, so it is held inside // a scrollpane. this.allSchemasDiagram = new AllSchemasDiagram(this.martTab); final JScrollPane scroller = new JScrollPane(this.allSchemasDiagram); scroller.getViewport().setBackground( this.allSchemasDiagram.getBackground()); scroller.getHorizontalScrollBar().addAdjustmentListener( this.allSchemasDiagram); scroller.getVerticalScrollBar().addAdjustmentListener( this.allSchemasDiagram); this.addTab(Resources.get("multiSchemaOverviewTab"), scroller); // Populate the map to hold the relation between schemas and the // diagrams representing them. for (final Iterator i = martTab.getMart().getSchemas().values() .iterator(); i.hasNext();) { final Schema sch = (Schema) i.next(); // Don't add schemas which are initially masked. if (!sch.isMasked()) this.addSchemaTab(sch, false); sch.addPropertyChangeListener("masked", this.updateListener); sch.addPropertyChangeListener("name", this.updateListener); } // Listen to add/remove/mass change schema events. martTab.getMart().getSchemas().addPropertyChangeListener( this.tabListener); } /** * Works out which schema tab is selected, and return it. * * @return the currently selected schema, or <tt>null</tt> if none is * selected. */ public Schema getSelectedSchema() { if (this.getSelectedIndex() <= 0 || !this.isShowing()) return null; final SchemaDiagram selectedDiagram = (SchemaDiagram) ((JScrollPane) this .getSelectedComponent()).getViewport().getView(); return selectedDiagram.getSchema(); } private synchronized void addSchemaTab(final Schema schema, final boolean selectNewSchema) { // Create the diagram to represent this schema. final SchemaDiagram schemaDiagram = new SchemaDiagram(this.martTab, schema); // Create a scroller to contain the diagram. final JScrollPane scroller = new JScrollPane(schemaDiagram); scroller.getViewport().setBackground(schemaDiagram.getBackground()); scroller.getHorizontalScrollBar().addAdjustmentListener(schemaDiagram); scroller.getVerticalScrollBar().addAdjustmentListener(schemaDiagram); // Add a tab containing the scroller, with the same name as the schema. this.addTab(schema.getName(), scroller); // Remember which diagram the schema is connected with. this.schemaToDiagram.put(schema.getName(), schemaDiagram); // Set the current context on the diagram to be the same as the // current context on this schema tabset. schemaDiagram.setDiagramContext(this.getDiagramContext()); if (selectNewSchema) { // Fake a click on the schema tab and on the button // that selects the schema editor in the current mart tabset. this.setSelectedIndex(this.indexOfTab(schema.getName())); this.martTab.selectSchemaEditor(); } } private String askUserForSchemaName(final String defaultResponse) { // Ask user for a name, giving them the default suggestion. String name = (String) JOptionPane.showInputDialog(null, Resources .get("requestSchemaName"), Resources.get("questionTitle"), JOptionPane.QUESTION_MESSAGE, null, null, defaultResponse); // If they didn't select anything, return null. if (name == null) return null; // If they entered an empty string, ie. deleted the default // but didn't type anything else, make it as though // it had not been deleted. else if (name.trim().length() == 0) name = defaultResponse; // Return the response. return name; } private Key askUserForTargetKey(final Key from) { // Given a particular key, work out which other keys, in any schema, // this key may be linked to. // Start by making a list to contain the candidates. final List candidates = new ArrayList(); // We want all keys that have the same number of columns. for (final Iterator i = this.martTab.getMart().getSchemas().values() .iterator(); i.hasNext();) for (final Iterator j = ((Schema) i.next()).getTables().values() .iterator(); j.hasNext();) { final Table tbl = (Table) j.next(); for (final Iterator k = tbl.getKeys().iterator(); k.hasNext();) { final Key key = (Key) k.next(); if (key.getColumns().length == from.getColumns().length && !key.equals(from)) candidates.add(key); } } // Alphabetize. Collections.sort(candidates); // Put up a box asking which key to link this key to, based on the // list of candidates we just made. Return the key that the user // selects, or null if none was selected. return (Key) JOptionPane.showInputDialog(null, Resources .get("whichKeyToLinkRelationTo"), Resources .get("questionTitle"), JOptionPane.QUESTION_MESSAGE, null, candidates.toArray(), null); } private JPopupMenu getSchemaTabContextMenu(final Schema schema) { // This menu will appear when a schema tab is right-clicked on // (that is, the tab itself, not the contents of the tab). // The empty menu to start with. final JPopupMenu contextMenu = new JPopupMenu(); // Update schema. final JMenuItem updateSchema = new JMenuItem(Resources .get("updateSchemaTitle"), new ImageIcon(Resources .getResourceAsURL("refresh.gif"))); updateSchema.setMnemonic(Resources.get("updateSchemaMnemonic") .charAt(0)); updateSchema.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent evt) { SchemaTabSet.this.requestModifySchema(schema); } }); contextMenu.add(updateSchema); // Add an option to rename this schema tab and associated schema. final JMenuItem rename = new JMenuItem(Resources .get("renameSchemaTitle")); rename.setMnemonic(Resources.get("renameSchemaMnemonic").charAt(0)); rename.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent evt) { SchemaTabSet.this.requestRenameSchema(schema); } }); contextMenu.add(rename); // Add an option to remove this schema tab, and the // associated schema from the mart. final JMenuItem close = new JMenuItem(Resources .get("removeSchemaTitle"), new ImageIcon(Resources .getResourceAsURL("cut.gif"))); close.setMnemonic(Resources.get("removeSchemaMnemonic").charAt(0)); close.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent evt) { SchemaTabSet.this.requestRemoveSchema(schema); } }); contextMenu.add(close); // Return the menu. return contextMenu; } private synchronized void removeSchemaTab(final String schemaName, final boolean select) { // Work out the currently selected tab. final int currentTab = this.getSelectedIndex(); // Work out the tab index for the schema. final int tabIndex = this.indexOfTab(schemaName); // Remove the tab. Also remove schema mapping from the schema-to-diagram // map. this.remove(tabIndex); this.schemaToDiagram.remove(schemaName); if (select) // Fake a click on the last tab before this one to ensure // at least one tab remains visible and up-to-date. this.setSelectedIndex(currentTab == 0 ? 0 : Math.max(tabIndex - 1, 0)); } protected void processMouseEvent(final MouseEvent evt) { boolean eventProcessed = false; // Is it a right-click? if (evt.isPopupTrigger()) { // Where was the click? final int selectedIndex = this.indexAtLocation(evt.getX(), evt .getY()); // Was the click on a tab? if (selectedIndex >= 0) { // Work out which tab was selected and which diagram // is displayed in that tab. final Component selectedComponent = this .getComponentAt(selectedIndex); if (selectedComponent instanceof JScrollPane) { final Component selectedDiagram = ((JScrollPane) selectedComponent) .getViewport().getView(); if (selectedDiagram instanceof SchemaDiagram) { // Set the schema diagram as the currently selected one. this.setSelectedIndex(selectedIndex); // Work out the schema inside the diagram. final Schema schema = ((SchemaDiagram) selectedDiagram) .getSchema(); // Show the context-menu for the tab for this schema. this.getSchemaTabContextMenu(schema).show(this, evt.getX(), evt.getY()); // We've handled the event so mark it as processed. eventProcessed = true; } } } } // Pass the event on up if we're not interested. if (!eventProcessed) super.processMouseEvent(evt); } /** * Returns the diagram context currently being used by {@link Diagram}s in * this schema tabset. * * @return the diagram context currently being used. */ public DiagramContext getDiagramContext() { return this.diagramContext; } /** * Returns the mart tab that this schema tabset lives inside. * * @return the parent mart tab. */ public MartTab getMartTab() { return this.martTab; } /** * Asks user to define a new schema, then adds it. */ public void requestAddSchema() { // Pop up a dialog to get the details of the new schema, then // obtain a copy of that schema. final Schema schema = SchemaConnectionDialog.createSchema(this.martTab .getMart()); // If no schema was defined, ignore the request. if (schema == null) return; // Add the schema to the mart, then synchronise it. SchemaTabSet.this.martTab.getMart().getSchemas().put( schema.getOriginalName(), schema); // Sync it. this.requestSynchroniseSchema(schema, false); } /** * Update a key status. * * @param key * the key to update the status of. * @param status * the new status to give it. */ public void requestChangeKeyStatus(final Key key, final ComponentStatus status) { new LongProcess() { public void run() { Transaction.start(false); key.setStatus(status); Transaction.end(); } }.start(); } /** * Update a relation cardinality. * * @param relation * the relation to change cardinality of. * @param cardinality * the new cardinality to give it. */ public void requestChangeRelationCardinality(final Relation relation, final Cardinality cardinality) { new LongProcess() { public void run() throws Exception { try { Transaction.start(true); relation.setCardinality(cardinality); if (!relation.getStatus().equals(ComponentStatus.HANDMADE)) relation .setStatus(cardinality.equals(relation .getOriginalCardinality()) ? ComponentStatus.INFERRED : ComponentStatus.MODIFIED); } finally { Transaction.end(); } } }.start(); } /** * Asks the user if they are sure they want to remove all schema partitions. */ public void requestRemoveAllSchemaPartitions() { // Confirm the decision first. final int choice = JOptionPane.showConfirmDialog(null, Resources .get("confirmUnpartitionAllSchemas"), Resources .get("questionTitle"), JOptionPane.YES_NO_OPTION); // Refuse to do it if they said no. if (choice != JOptionPane.YES_OPTION) return; new LongProcess() { public void run() { Transaction.start(true); for (final Iterator i = SchemaTabSet.this.martTab.getMart() .getSchemas().values().iterator(); i.hasNext();) { final Schema sch = (Schema) i.next(); sch.setPartitionNameExpression(null); sch.setPartitionRegex(null); } Transaction.end(); } }.start(); } /** * Update a relation status. * * @param relation * the relation to change the status for. * @param status * the new status to give it. */ public void requestChangeRelationStatus(final Relation relation, final ComponentStatus status) { new LongProcess() { public void run() throws Exception { try { Transaction.start(false); relation.setStatus(status); } finally { Transaction.end(); } } }.start(); } /** * Ask the user to define a foreign key on a table, then create it. * * @param table * the table to define the key on. */ public void requestCreateForeignKey(final Table table) { // Pop up a dialog to ask which columns to use. final KeyDialog dialog = new KeyDialog(table, Resources .get("newFKDialogTitle"), Resources.get("addButton"), null); dialog.setLocationRelativeTo(null); dialog.setVisible(true); final Column[] cols = dialog.getSelectedColumns(); dialog.dispose(); // If they chose some columns, create the key. if (cols.length > 0) this.requestCreateForeignKey(table, cols); } /** * Given a set of columns, create a foreign key on the given table that * contains those columns in the order they appear in the iterator. * * @param table * the table to create the key over. * @param columns * the columns to include the key. */ public void requestCreateForeignKey(final Table table, final Column[] columns) { new LongProcess() { public void run() { Transaction.start(false); final ForeignKey fk = new ForeignKey(columns); fk.setStatus(ComponentStatus.HANDMADE); table.getForeignKeys().add(fk); Transaction.end(); } }.start(); } /** * Ask the user to define a primary key on a table, then create it. * * @param table * the table to define the key on. */ public void requestCreatePrimaryKey(final Table table) { // Pop up a dialog to ask which columns to use. final KeyDialog dialog = new KeyDialog(table, Resources .get("newPKDialogTitle"), Resources.get("addButton"), null); dialog.setLocationRelativeTo(null); dialog.setVisible(true); final Column[] cols = dialog.getSelectedColumns(); dialog.dispose(); // If they chose some columns, create the key. if (cols.length > 0) this.requestCreatePrimaryKey(table, cols); } /** * Given a set of columns, create a primary key on the given table that * contains those columns in the order they appear in the iterator. This * will replace any existing primary key on the table. * * @param table * the table to create the key over. * @param columns * the columns to include the key. */ public void requestCreatePrimaryKey(final Table table, final Column[] columns) { new LongProcess() { public void run() { Transaction.start(false); final PrimaryKey pk = new PrimaryKey(columns); pk.setStatus(ComponentStatus.HANDMADE); table.setPrimaryKey(pk); Transaction.end(); } }.start(); } /** * Given a key, ask the user which other key they want to make a relation to * from this key. * * @param from * the key to make a relation from. */ public void requestCreateRelation(final Key from) { // Ask them which key they want to link to. final Key to = this.askUserForTargetKey(from); // If they selected something, create the relation to it. if (to != null) this.requestCreateRelation(from, to); } /** * Given a pair of keys, establish a relation between them. * * @param from * one end of the relation. * @param to * the other end. */ public void requestCreateRelation(final Key from, final Key to) { // Create the relation in the background. new LongProcess() { public void run() throws Exception { try { Transaction.start(true); final Relation rel = new Relation( from, to, from instanceof PrimaryKey ? (to instanceof PrimaryKey ? Cardinality.ONE : Cardinality.MANY_A) : (to instanceof PrimaryKey ? Cardinality.MANY_B : Cardinality.MANY_A)); rel.setStatus(ComponentStatus.HANDMADE); from.getRelations().add(rel); to.getRelations().add(rel); } finally { Transaction.end(); } } }.start(); } /** * Asks that a table be (un)ignored. * * @param table * the table to (un)ignore. * @param ignored * ignore it? */ public void requestIgnoreTable(final Table table, final boolean ignored) { new LongProcess() { public void run() { Transaction.start(false); table.setMasked(ignored); Transaction.end(); } }.start(); } /** * Asks that a schema be (un)masked. * * @param s * the schema we are working with. * @param masked * mask it? */ public void requestMaskSchema(final Schema s, final boolean masked) { new LongProcess() { public void run() { Transaction.start(false); s.setMasked(masked); Transaction.end(); } }.start(); } /** * Pop up a dialog describing the key, and ask the user to modify it, before * carrying out the modification. * * @param key * the key to edit. */ public void requestEditKey(final Key key) { // Pop up the dialog which describes the key, and obtain the // list of columns they selected in response. final KeyDialog dialog = new KeyDialog(key.getTable(), Resources .get("editKeyDialogTitle"), Resources.get("modifyButton"), key .getColumns()); dialog.setLocationRelativeTo(null); dialog.setVisible(true); final Column[] cols = dialog.getSelectedColumns(); dialog.dispose(); // If they selected any columns, modify the key. if (cols.length > 0) new LongProcess() { public void run() { Transaction.start(false); key.setColumns(cols); key.setStatus(ComponentStatus.HANDMADE); Transaction.end(); } }.start(); } /** * Turn keyguessing on for a schema. * * @param schema * the schema to turn keyguessing on for. * @param keyGuessing * <tt>true</tt> to turn it on, not for off. */ public void requestKeyGuessing(final Schema schema, final boolean keyGuessing) { // Create a progress monitor. final ProgressDialog progressMonitor = new ProgressDialog(this, 0, 100, false); progressMonitor.setVisible(true); // Start the construction in a thread. It does not need to be // Swing-thread-safe because it will never access the GUI. All // GUI interaction is done through the Timer below. final SwingWorker worker = new SwingWorker() { public Object construct() { Transaction.start(true); try { schema.setKeyGuessing(keyGuessing); } catch (final Throwable t) { SwingUtilities.invokeLater(new Runnable() { public void run() { StackTrace.showStackTrace(t); } }); } Transaction.end(); return null; } public void finished() { // Close the progress dialog. progressMonitor.setVisible(false); progressMonitor.dispose(); // This is to ensure that any modified flags get cleared. ((SchemaDiagram) SchemaTabSet.this.schemaToDiagram.get(schema .getName())).repaintDiagram(); } }; // Create a timer thread that will update the progress dialog. // We use the Swing Timer to make it Swing-thread-safe. (1000 millis // equals 1 second.) final Timer timer = new Timer(300, null); timer.setInitialDelay(0); // Start immediately upon request. timer.setCoalesce(true); // Coalesce delayed events. timer.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { SwingUtilities.invokeLater(new Runnable() { public void run() { final double progress = schema.getProgress(); // Did the job complete yet? if (progress < 100.0 && progressMonitor.isVisible()) // If not, update the progress report. progressMonitor.setProgress((int) progress); else { // If it completed, close the task and tidy up. // Stop the timer. timer.stop(); } } }); } }); // Start the timer. timer.start(); worker.start(); } /** * Pops up a dialog with details of the schema, which allows the user to * modify them. * * @param schema * the schema to modify. */ public void requestModifySchema(final Schema schema) { if (SchemaConnectionDialog.modifySchema(schema)) this.requestSynchroniseSchema(schema, true); } /** * Remove a key. * * @param key * the key to remove. */ public void requestRemoveKey(final Key key) { new LongProcess() { public void run() { Transaction.start(false); if (key instanceof PrimaryKey) key.getTable().setPrimaryKey(null); else key.getTable().getForeignKeys().remove(key); Transaction.end(); } }.start(); } /** * Remove a relation. * * @param relation * the relation to remove. */ public void requestRemoveRelation(final Relation relation) { new LongProcess() { public void run() { Transaction.start(false); relation.getFirstKey().getRelations().remove(relation); relation.getSecondKey().getRelations().remove(relation); Transaction.end(); } }.start(); } /** * Confirms with user then removes a schema. * * @param schema * the schema to remove. */ public void requestRemoveSchema(final Schema schema) { // Confirm if the user really wants to do it. final int choice = JOptionPane.showConfirmDialog(null, Resources .get("confirmDelSchema"), Resources.get("questionTitle"), JOptionPane.YES_NO_OPTION); // If they don't, cancel out. if (choice != JOptionPane.YES_OPTION) return; new LongProcess() { public void run() { Transaction.start(false); SchemaTabSet.this.martTab.getMart().getSchemas().remove( schema.getOriginalName()); Transaction.end(); } }.start(); } /** * Asks user for a new name, then renames a schema. * * @param schema * the schema to rename. */ public void requestRenameSchema(final Schema schema) { // Ask for a new name, suggesting the schema's existing name // as the default response. this.requestRenameSchema(schema, this.askUserForSchemaName(schema .getName())); } /** * Requests that the schema be given the new name, now, without further * prompting * * @param schema * the schema to rename. * @param name * the new name to give it. */ public void requestRenameSchema(final Schema schema, final String name) { // Ask for a new name, suggesting the schema's existing name // as the default response. final String newName = name == null ? "" : name.trim(); // If they cancelled or entered the same name, ignore the request. if (newName.length() == 0) return; new LongProcess() { public void run() { Transaction.start(false); schema.setName(newName); Transaction.end(); } }.start(); } /** * Shows some rows of the table in a {@link JTable} in a popup dialog. * * @param table * the table to show rows from. * @param count * how many rows to show. */ public void requestShowRows(final Table table, final int count) { new LongProcess() { public void run() throws Exception { // Get the rows. final Collection rows = table.getSchema().getRows( martTab.getPartitionViewSelection(), table, count); // Convert to a nested vector. final Vector data = new Vector(); for (final Iterator i = rows.iterator(); i.hasNext();) data.add(new Vector((List) i.next())); // Get the column names. final Vector colNames = new Vector(table.getColumns().keySet()); // Construct a JTable. final JTable jtable = new JTable(new DefaultTableModel(data, colNames)); final Dimension size = new Dimension(); size.width = 0; size.height = jtable.getRowHeight() * count; for (int i = 0; i < jtable.getColumnCount(); i++) size.width += jtable.getColumnModel().getColumn(i) .getPreferredWidth(); size.width = Math.min(size.width, 800); // Arbitrary. size.height = Math.min(size.height, 200); // Arbitrary. jtable.setPreferredScrollableViewportSize(size); // Display them. SwingUtilities.invokeLater(new Runnable() { public void run() { JOptionPane.showMessageDialog(null, new JScrollPane( jtable), Resources.get("showRowsDialogTitle", new String[] { "" + count, table.getName() }), JOptionPane.INFORMATION_MESSAGE); } }); } }.start(); } /** * Synchronises all schemas in the mart. */ public void requestSynchroniseAllSchemas() { // Create a progress monitor. final ProgressDialog progressMonitor = new ProgressDialog(this, 0, 100, false); progressMonitor.setVisible(true); // Start the construction in a thread. It does not need to be // Swing-thread-safe because it will never access the GUI. All // GUI interaction is done through the Timer below. final List allSchemas = new ArrayList(SchemaTabSet.this.martTab .getMart().getSchemas().values()); final List doneSchemas = new ArrayList(); final double scale = 1.0 / allSchemas.size(); final SwingWorker worker = new SwingWorker() { public Object construct() { Transaction.start(true); while (allSchemas.size() > 0) try { ((Schema) allSchemas.get(0)).synchronise(); doneSchemas.add(allSchemas.remove(0)); } catch (final Throwable t) { SwingUtilities.invokeLater(new Runnable() { public void run() { StackTrace.showStackTrace(t); } }); } Transaction.end(); return null; } public void finished() { // Close the progress dialog. progressMonitor.setVisible(false); progressMonitor.dispose(); // This is to ensure that any modified flags get cleared. for (final Iterator i = SchemaTabSet.this.schemaToDiagram .values().iterator(); i.hasNext();) ((SchemaDiagram) i.next()).repaintDiagram(); } }; worker.start(); // Create a timer thread that will update the progress dialog. // We use the Swing Timer to make it Swing-thread-safe. (1000 millis // equals 1 second.) final Timer timer = new Timer(300, null); timer.setInitialDelay(300); // Start immediately upon request. timer.setCoalesce(true); // Coalesce delayed events. timer.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { SwingUtilities.invokeLater(new Runnable() { public void run() { final double progress = scale * 100.0 * doneSchemas.size() + (allSchemas.size() == 0 ? 0.0 : ((Schema) allSchemas.get(0)) .getProgress() * scale); // Did the job complete yet? if (progress < 100.0 && progressMonitor.isVisible()) // If not, update the progress report. progressMonitor.setProgress((int) progress); else { // If it completed, close the task and tidy up. // Stop the timer. timer.stop(); } } }); } }); // Start the timer. timer.start(); } /** * Syncs this schema against the database. * * @param schema * the schema to synchronise. * @param transactionMod * <tt>true</tt> if the transaction is allowed to show visible * modifications. */ public void requestSynchroniseSchema(final Schema schema, final boolean transactionMod) { // Create a progress monitor. final ProgressDialog progressMonitor = new ProgressDialog(this, 0, 100, false); progressMonitor.setVisible(true); // Start the construction in a thread. It does not need to be // Swing-thread-safe because it will never access the GUI. All // GUI interaction is done through the Timer below. final SwingWorker worker = new SwingWorker() { public Object construct() { Transaction.start(transactionMod); try { schema.synchronise(); } catch (final Throwable t) { SwingUtilities.invokeLater(new Runnable() { public void run() { StackTrace.showStackTrace(t); } }); } Transaction.end(); return null; } public void finished() { // Close the progress dialog. progressMonitor.setVisible(false); progressMonitor.dispose(); // This is to ensure that any modified flags get cleared. ((SchemaDiagram) SchemaTabSet.this.schemaToDiagram.get(schema .getName())).repaintDiagram(); } }; // Create a timer thread that will update the progress dialog. // We use the Swing Timer to make it Swing-thread-safe. (1000 millis // equals 1 second.) final Timer timer = new Timer(300, null); timer.setInitialDelay(0); // Start immediately upon request. timer.setCoalesce(true); // Coalesce delayed events. timer.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { SwingUtilities.invokeLater(new Runnable() { public void run() { final double progress = schema.getProgress(); // Did the job complete yet? if (progress < 100.0 && progressMonitor.isVisible()) // If not, update the progress report. progressMonitor.setProgress((int) progress); else { // If it completed, close the task and tidy up. // Stop the timer. timer.stop(); } } }); } }); // Start the timer. timer.start(); worker.start(); } /** * Request that all changes on this schema are accepted. * * @param sch * the target schema. */ public void requestAcceptAll(final Schema sch) { new LongProcess() { public void run() { final List modTbls = new ArrayList(); Transaction.start(true); for (final Iterator i = sch.getTables().values().iterator(); i .hasNext();) { final Table tbl = (Table) i.next(); if (tbl.isVisibleModified()) { modTbls.add(tbl); for (final Iterator k = tbl.getColumns().values().iterator(); k.hasNext(); ) ((Column)k.next()).setVisibleModified(false); for (final Iterator k = tbl.getRelations().iterator(); k.hasNext(); ) ((Relation)k.next()).setVisibleModified(false); } } Transaction.end(); for (final Iterator i = sch.getMart().getDataSets().values() .iterator(); i.hasNext();) { final DataSet ds = (DataSet) i.next(); if (!ds.isVisibleModified()) continue; for (final Iterator j = modTbls.iterator(); j.hasNext();) { final Table modTbl = (Table)j.next(); SchemaTabSet.this.getMartTab().getDataSetTabSet() .requestAcceptAll(ds, modTbl); } } } }.start(); } /** * Request that all changes on this schema are rejected. * * @param sch * the target schema. */ public void requestRejectAll(final Schema sch) { new LongProcess() { public void run() { final List modTbls = new ArrayList(); Transaction.start(true); for (final Iterator i = sch.getTables().values().iterator(); i .hasNext();) { final Table tbl = (Table) i.next(); if (tbl.isVisibleModified()) { modTbls.add(tbl); for (final Iterator k = tbl.getColumns().values().iterator(); k.hasNext(); ) ((Column)k.next()).setVisibleModified(false); for (final Iterator k = tbl.getRelations().iterator(); k.hasNext(); ) ((Relation)k.next()).setVisibleModified(false); } } Transaction.end(); for (final Iterator i = sch.getMart().getDataSets().values() .iterator(); i.hasNext();) { final DataSet ds = (DataSet) i.next(); if (!ds.isVisibleModified()) continue; for (final Iterator j = modTbls.iterator(); j.hasNext();) { final Table modTbl = (Table)j.next(); SchemaTabSet.this.getMartTab().getDataSetTabSet() .requestRejectAll(ds, modTbl); } } } }.start(); } /** * Sets the diagram context to use for all {@link Diagram}s inside this * schema tabset. Once set, * {@link Diagram#setDiagramContext(DiagramContext)} is called on each * diagram in the tabset in turn so that they are all working with the same * context. * * @param diagramContext * the context to use for all {@link Diagram}s in this schema * tabset. */ public void setDiagramContext(final DiagramContext diagramContext) { this.diagramContext = diagramContext; this.allSchemasDiagram.setDiagramContext(diagramContext); for (final Iterator i = this.schemaToDiagram.values().iterator(); i .hasNext();) ((Diagram) i.next()).setDiagramContext(diagramContext); } }