/* 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.BorderLayout; import java.awt.CardLayout; 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.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import javax.swing.ButtonGroup; import javax.swing.DefaultComboBoxModel; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButton; import javax.swing.JTabbedPane; import javax.swing.JToolBar; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.border.EmptyBorder; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import javax.swing.filechooser.FileFilter; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoManager; import org.biomart.builder.controller.MartBuilderXML; import org.biomart.builder.controller.MartConstructor.ConstructorRunnable; import org.biomart.builder.exceptions.ConstructorException; import org.biomart.builder.model.DataSet; import org.biomart.builder.model.Mart; import org.biomart.builder.model.Schema; import org.biomart.builder.view.gui.diagrams.contexts.SchemaContext; import org.biomart.builder.view.gui.dialogs.MartRunnerConnectionDialog; import org.biomart.builder.view.gui.dialogs.MartRunnerMonitorDialog; import org.biomart.builder.view.gui.dialogs.SaveDDLDialog; import org.biomart.common.exceptions.TransactionException; import org.biomart.common.resources.Resources; import org.biomart.common.resources.Settings; import org.biomart.common.utils.Transaction; import org.biomart.common.utils.Transaction.TransactionEvent; import org.biomart.common.utils.Transaction.TransactionListener; 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; import org.ensembl.mart.editor.MartEditor; /** * Displays a set of tabs, one per mart currently loaded. Each tab keeps track * of the mart inside it, including all datasets and schemas. * * @author Richard Holland <holland@ebi.ac.uk> * @version $Revision: 1.73 $, $Date: 2008-02-25 10:37:27 $, modified by * $Author: rh4 $ * @since 0.5 */ public class MartTabSet extends JTabbedPane implements TransactionListener { private static final long serialVersionUID = 1; private MartBuilder martBuilder; // Mart hashcodes don't change, so it is safe to use a Map. private Map martModifiedStatus; // Mart hashcodes don't change, so it is safe to use a Map. private Map martXMLFile; private JFileChooser xmlFileChooser; private final UndoManager undoManager = new UndoManager(); private final PropertyChangeListener updateListener = new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent evt) { final Mart mart = (Mart) evt.getSource(); if (evt.getNewValue().equals(Boolean.TRUE) && !Boolean.TRUE.equals(MartTabSet.this.martModifiedStatus .put(mart, Boolean.TRUE))) MartTabSet.this.updateMartTitle(mart); } }; /** * Creates a new set of tabs and associates them with a given MartBuilder * GUI. * * @param martBuilder * the GUI these tabs belong to. */ public MartTabSet(final MartBuilder martBuilder) { // Tabbed-pane stuff first. super(); Transaction.addTransactionListener(this); // Create the file chooser for opening MartBuilder XML files. this.xmlFileChooser = new JFileChooser() { private static final long serialVersionUID = 1L; public File getSelectedFile() { File file = super.getSelectedFile(); if (file != null && !file.exists()) { final String filename = file.getName(); final String extension = Resources.get("xmlExtension"); if (!filename.endsWith(extension) && filename.indexOf('.') < 0) file = new File(file.getParentFile(), filename + extension); } return file; } }; this.xmlFileChooser.setFileFilter(new FileFilter() { // Accepts only files ending in ".xml". public boolean accept(final File f) { return f.isDirectory() || f.getName().toLowerCase().endsWith( Resources.get("xmlExtension")); } public String getDescription() { return Resources.get("XMLFileFilterDescription"); } }); this.xmlFileChooser.setMultiSelectionEnabled(true); // Now set up and remember our variables. this.martBuilder = martBuilder; this.martModifiedStatus = new HashMap(); this.martXMLFile = new HashMap(); } /** * Get the undo manager for this tabset. * * @return the undo manager. */ public UndoManager getUndoManager() { return this.undoManager; } private Object stateBeforeTransaction = null; /** * Save the application state for undo/redo and return that state as an * object. * * @return the state. */ public Object saveState() { try { final ByteArrayOutputStream so = new ByteArrayOutputStream(); final ObjectOutputStream oo = new ObjectOutputStream(so); // FIXME At some point actually work out how to do this. oo.close(); return so.toByteArray(); } catch (final Exception e) { // Ignore. return null; } } /** * Restore the application state from undo/redo. * * @param state * the state to restore. */ public void restoreState(final Object state) { try { final ByteArrayInputStream si = new ByteArrayInputStream( (byte[]) state); final ObjectInputStream oi = new ObjectInputStream(si); // FIXME At some point actually work out how to do this. oi.close(); } catch (final Exception e) { // Ignore. } } public boolean isDirectModified() { return false; } public boolean isVisibleModified() { return false; } public void setDirectModified(final boolean modified) { // Ignore. } public void setVisibleModified(final boolean modified) { // Ignore. } public void transactionEnded(final TransactionEvent evt) throws TransactionException { if (this.stateBeforeTransaction != null) { final Object preservedStateBeforeTransaction = this.stateBeforeTransaction; final Object stateAfterTransaction = this.saveState(); this.undoManager.addEdit(new AbstractUndoableEdit() { private static final long serialVersionUID = 1L; public void redo() throws CannotRedoException { MartTabSet.this.restoreState(stateAfterTransaction); } public void undo() throws CannotUndoException { MartTabSet.this .restoreState(preservedStateBeforeTransaction); } }); } this.stateBeforeTransaction = null; } public void transactionResetDirectModified() { // Ignore. } public void transactionResetVisibleModified() { // Ignore. } public void transactionStarted(final TransactionEvent evt) { this.stateBeforeTransaction = this.saveState(); } /** * Adds a new tab to the tabset representing a new mart. * * @param mart * the mart to put in the tab. * @param martXMLFile * the file the mart came from. May be <tt>null</tt> if the * mart is new. */ private synchronized void addMartTab(final Mart mart, final File martXMLFile) { this.martXMLFile.put(mart, martXMLFile); this.martModifiedStatus.put(mart, Boolean.FALSE); final MartTab martTab = new MartTab(this, mart); final String martTabName = this.suggestTabName(mart, true); this.addTab(martTabName, martTab); // Select the tab we just created. this.setSelectedIndex(this.getTabCount() - 1); // Within that tab, select the all-schemas and all-datasets tabs. martTab.getDataSetTabSet().setSelectedIndex(0); martTab.getSchemaTabSet().setSelectedIndex(0); // Listen to modified changes. mart.addPropertyChangeListener("directModified", this.updateListener); } private void updateMartTitle(final Mart mart) { // Update the tab title to indicate modification status. this.setTitleAt(this.getSelectedIndex(), this .suggestTabName(mart, true)); } /** * Construct a context menu for a given mart tab. This is the context menu * on the tab itself, not it's contents. * * @return the popup menu. */ private JPopupMenu getMartTabContextMenu() { final JPopupMenu contextMenu = new JPopupMenu(); // The close option closes the selected mart, confirming first // that it's OK to do so. final JMenuItem close = new JMenuItem(Resources.get("closeMartTitle")); close.setMnemonic(Resources.get("closeMartMnemonic").charAt(0)); close.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent evt) { MartTabSet.this.requestCloseMart(); } }); contextMenu.add(close); // Return the menu. return contextMenu; } /** * Suggests a tab name based on a mart's filename. If the mart has no * filename, "unsaved" is used. */ private String suggestTabName(final Mart mart, final boolean includeModified) { // Start with "unsaved". String basename = Resources.get("unsavedMart"); // See if this mart came from a file. If so, use the filename. final File filename = (File) this.martXMLFile.get(mart); if (filename != null) basename = filename.getName(); // If it's modified, append a "*" to make it obvious. return basename + (includeModified && this.martModifiedStatus.get(mart).equals( Boolean.TRUE) ? " *" : ""); } 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()); // Did we actually click on any tab? if (selectedIndex >= 0) { // Select that tab. this.setSelectedIndex(selectedIndex); // Pop up the context menu for it. this.getMartTabContextMenu().show(this, evt.getX(), evt.getY()); // We've processed the mouse event. eventProcessed = true; } } // Pass it on up if we're not interested. if (!eventProcessed) super.processMouseEvent(evt); } /** * On a request to close all marts, check that none of them are modified. If * any of them are, ask the user if they're sure they want to close them * all. * * @return <tt>true</tt> if its OK to close all the marts, <tt>false</tt> * if not. */ public boolean requestCloseAllMarts() { for (final Iterator i = this.martModifiedStatus.values().iterator(); i .hasNext();) if (i.next().equals(Boolean.TRUE)) { final int choice = JOptionPane.showConfirmDialog(null, Resources.get("okToCloseAll"), Resources .get("questionTitle"), JOptionPane.YES_NO_OPTION); return choice == JOptionPane.YES_OPTION; } return true; } /** * On a request to close the current mart, check that it is not modified. If * it is, ask the user if they're sure they want to close it. If they say * yes, or if it is not modified, close it. */ public void requestCloseMart() { // If nothing is selected, forget it, they can't close! if (this.getSelectedMartTab() == null) return; // Work out the current selected mart. final MartTab currentMartTab = this.getSelectedMartTab(); final Mart currentMart = currentMartTab.getMart(); // Is it modified? If so, ask user for confirmation. boolean canClose = true; if (this.martModifiedStatus.get(currentMart).equals(Boolean.TRUE)) { // Modified, so must confirm action first. final int choice = JOptionPane.showConfirmDialog(null, Resources .get("okToClose"), Resources.get("questionTitle"), JOptionPane.YES_NO_OPTION); canClose = choice == JOptionPane.YES_OPTION; } // If it's OK to close, remove the tab and the mart itself. if (canClose) { // Remove the tab. this .removeTabAt(this.indexOfComponent(this .getSelectedComponent())); // Remove the mart from the modified map. this.martModifiedStatus.remove(currentMart); // Remove the XML file the mart came from from the file map. this.martXMLFile.remove(currentMart); } } /** * Retrieves the parent MartBuilder GUI. * * @return the parent mart builder GUI. */ public MartBuilder getMartBuilder() { return this.martBuilder; } /** * Gets whether any currently open mart is modified. * * @return <tt>true</tt> if any of them are, <tt>false</tt> if not. */ public boolean getModifiedStatus() { return this.martModifiedStatus.values().contains(Boolean.TRUE); } /** * Works out which mart tab is selected, and return it. * * @return the currently selected mart tab, or <tt>null</tt> if none is * selected. */ public MartTab getSelectedMartTab() { return (MartTab) this.getSelectedComponent(); } /** * Loads a schema from a user-specified file(s), by popping up a dialog * allowing them to choose the file(s). If they choose a file, it is loaded * and parsed and a new tab is added representing its contents. */ public void requestLoadMart() { // Open the file chooser. final String currentDir = Settings.getProperty("currentOpenDir"); this.xmlFileChooser.setCurrentDirectory(currentDir == null ? null : new File(currentDir)); if (this.xmlFileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { // Update the load dialog. Settings.setProperty("currentOpenDir", this.xmlFileChooser .getCurrentDirectory().getPath()); // Find out which files they selected. final File[] loadFiles = this.xmlFileChooser.getSelectedFiles(); for (int i = 0; i < loadFiles.length; i++) this.requestLoadMart(loadFiles[i]); } } /** * Loads a schema from a user-specified file(s), by popping up a dialog * allowing them to choose the file(s). If they choose a file, it is loaded * and parsed and a new tab is added representing its contents. * * @param file * the file to load. If it does not exist, this delegates to the * normal load method. */ public void requestLoadMart(final File file) { // Open the file chooser. // In the background, load them in turn. new LongProcess() { public void run() throws Exception { // Do we need to close the existing unsaved // unmodified default tab? MartTab defaultTab = MartTabSet.this.getSelectedMartTab(); final int defaultIndex = MartTabSet.this.getSelectedIndex(); if (MartTabSet.this.getComponentCount() > 1 || defaultTab != null && !MartTabSet.this.getTitleAt(defaultIndex).equals( Resources.get("unsavedMart"))) defaultTab = null; // Load the files. Transaction.start(false); final Mart mart; try { mart = MartBuilderXML.load(file); } finally { Transaction.end(); } MartTabSet.this.martModifiedStatus.put(mart, Boolean.FALSE); MartTabSet.this.addMartTab(mart, file); // Save XML filename in history of accessed // files. final Properties history = new Properties(); history.setProperty("location", file.getPath()); Settings.saveHistoryProperties(MartTabSet.class, MartTabSet.this.suggestTabName(mart, false), history); // Finally, remove the unsaved default tab if // we need to. if (defaultTab != null) { // Remove the tab. MartTabSet.this.removeTabAt(defaultIndex); // Remove the mart from the modified map. MartTabSet.this.martModifiedStatus.remove(defaultTab .getMart()); // Remove the XML file the mart came from from // the file map. MartTabSet.this.martXMLFile.remove(defaultTab.getMart()); } } }.start(); } /** * On a request to create DDL for the current mart, open the DDL creation * window with all the datasets for this mart selected. */ public void requestCreateDDL() { this.requestSaveDDLDialog(SaveDDLDialog.VIEW_DDL); } /** * On a request to run DDL for the current mart, open the DDL creation * window with all the datasets for this mart selected and MartRunner option * selected. */ public void requestRunDDL() { this.requestSaveDDLDialog(SaveDDLDialog.RUN_DDL); } private void requestSaveDDLDialog(final String generateOption) { // If nothing is selected, forget it, they can't close! if (this.getSelectedMartTab() == null) return; // Work out the current selected mart. final MartTab currentMartTab = this.getSelectedMartTab(); // If the mart has no datasets, ignore the request. final Mart mart = currentMartTab.getMart(); final Collection datasets = new ArrayList(mart.getDataSets().values()); // Remove partition table datasets from the list. // Also remove masked datasets. for (final Iterator i = datasets.iterator(); i.hasNext();) { final DataSet ds = (DataSet) i.next(); if (ds.isPartitionTable() || ds.isMasked()) i.remove(); } if (datasets.size() == 0) JOptionPane.showMessageDialog(null, Resources .get("noDatasetsToGenerate"), Resources.get("messageTitle"), JOptionPane.INFORMATION_MESSAGE); else // Open the DDL creation dialog and let it do it's stuff. (new SaveDDLDialog(currentMartTab, datasets, currentMartTab .getPartitionViewSelection() == null ? currentMartTab .getAllSchemaPrefixes() : Collections .singleton(currentMartTab.getPartitionViewSelection()), generateOption)).setVisible(true); } /** * Sets the output database on the currently selected mart. * * @param outputDatabase * the new output database. */ public void requestSetOutputDatabase(final String outputDatabase) { Transaction.start(false); this.getSelectedMartTab().getMart().setOutputDatabase(outputDatabase); Transaction.end(); } /** * Sets the output schema on the currently selected mart. * * @param outputSchema * the new output schema. */ public void requestSetOutputSchema(final String outputSchema) { Transaction.start(false); this.getSelectedMartTab().getMart().setOutputSchema(outputSchema); Transaction.end(); } /** * Sets the output host on the currently selected mart. * * @param host * the new output host. */ public void requestSetOutputHost(final String host) { Transaction.start(false); this.getSelectedMartTab().getMart().setOutputHost(host); Transaction.end(); } /** * Sets the output port on the currently selected mart. * * @param port * the new output port. */ public void requestSetOutputPort(final String port) { Transaction.start(false); this.getSelectedMartTab().getMart().setOutputPort(port); Transaction.end(); } /** * Sets the override JDBC host on the currently selected mart. * * @param host * the new host. */ public void requestSetOverrideHost(final String host) { Transaction.start(false); this.getSelectedMartTab().getMart().setOverrideHost(host); Transaction.end(); } /** * Sets the override JDBC port on the currently selected mart. * * @param port * the new port. */ public void requestSetOverridePort(final String port) { Transaction.start(false); this.getSelectedMartTab().getMart().setOverridePort(port); Transaction.end(); } /** * Runs the given {@link ConstructorRunnable} and monitors it's progress. * * @param constructor * the constructor that will build a mart. */ public void requestMonitorConstructorRunnable( final ConstructorRunnable constructor) { // Create a progress monitor. final ProgressDialog progressMonitor = new ProgressDialog(this, 0, 100, true); 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() { constructor.run(); return null; } }; 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() { // Did the job complete yet? if (constructor.isAlive()) { if (progressMonitor.isCanceled()) // Stop the thread if required. constructor.cancel(); // If not, update the progress report. progressMonitor.setProgress(constructor .getPercentComplete()); } else { // If it completed, close the task and tidy up. // Stop the timer. timer.stop(); // Close the progress dialog. progressMonitor.setVisible(false); progressMonitor.dispose(); // If it failed, show the exception. final Exception failure = constructor .getFailureException(); // By singling out ConstructorException we can show // users useful messages straight away. if (failure != null) StackTrace .showStackTrace(failure instanceof ConstructorException ? failure : new ConstructorException( Resources .get("martConstructionFailed"), failure)); // Inform user of success, if it succeeded. else if(MartEditor.HAS_GUI) JOptionPane.showMessageDialog(null, Resources .get("martConstructionComplete"), Resources.get("messageTitle"), JOptionPane.INFORMATION_MESSAGE); } } }); } }); // Start the timer. timer.start(); } /** * Ask the user which remote host to monitor, then open the dialog box that * monitors that host. */ public void requestMonitorRemoteHost() { final MartRunnerConnectionDialog d = new MartRunnerConnectionDialog( this.getSelectedMartTab() == null ? null : this .getSelectedMartTab().getMart()); d.setVisible(true); // Cancelled by user? if (d.getHost() == null) return; else this.requestMonitorRemoteHost(d.getHost(), d.getPort()); d.dispose(); } /** * Opens the dialog box to monitor the specified remote host and port. * * @param host * the host to connect to. * @param port * the port the host is listening on. */ public void requestMonitorRemoteHost(final String host, final String port) { Transaction.start(false); MartTabSet.this.getSelectedMartTab().getMart().setOutputHost(host); MartTabSet.this.getSelectedMartTab().getMart().setOutputPort(port); Transaction.end(); // Open remote host monitor dialog. MartRunnerMonitorDialog.monitor(host, port); } /** * Creates a new, empty mart and adds a tab for it. */ public void requestNewMart() { this.addMartTab(new Mart(), null); } /** * Saves the current mart to the file currently defined for it. */ public void requestSaveMart() { // If nothing selected, refuse. if (this.getSelectedMartTab() == null) return; // Work out if we already have a file for this mart. If not, // do a save-as instead. final Mart currentMart = this.getSelectedMartTab().getMart(); if (this.martXMLFile.get(currentMart) == null) this.requestSaveMartAs(); else // Save it in the background to the existing file. new LongProcess() { public void run() throws Exception { // Save it. MartBuilderXML .save(currentMart, (File) MartTabSet.this.martXMLFile .get(currentMart)); // We're not modified any more! MartTabSet.this.martModifiedStatus.put(currentMart, Boolean.FALSE); MartTabSet.this.updateMartTitle(currentMart); } }.start(); } /** * Saves the mart to a user-specified file, by popping up a file-chooser. */ public void requestSaveMartAs() { // If nothing selected at present, refuse. if (this.getSelectedMartTab() == null) return; // Work out the current mart. final Mart currentMart = this.getSelectedMartTab().getMart(); // Show a file chooser. If the user didn't cancel it, process the // response. final String currentDir = Settings.getProperty("currentSaveDir"); this.xmlFileChooser.setCurrentDirectory(currentDir == null ? null : new File(currentDir)); if (this.xmlFileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) { Settings.setProperty("currentSaveDir", this.xmlFileChooser .getCurrentDirectory().getPath()); // Find out the file the user chose. final File saveAsFile = this.xmlFileChooser.getSelectedFile(); // Skip the rest if they cancelled the save box. if (saveAsFile == null) return; // Save it, and save the reference to the XML file for later. this.martXMLFile.put(currentMart, saveAsFile); this.requestSaveMart(); // Save XML filename in history of accessed files. final Properties history = new Properties(); history.setProperty("location", saveAsFile.getPath()); Settings.saveHistoryProperties(MartTabSet.class, this .suggestTabName(currentMart, false), history); } } /** * Change the name case for the selected mart. * * @param nameCase * the new case. */ public void requestChangeNameCase(final int nameCase) { new LongProcess() { public void run() { Transaction.start(false); MartTabSet.this.getSelectedMartTab().getMart() .setCase(nameCase); Transaction.end(); } }.start(); } /** * This represents a single mart XML file as a tab in the top-level tabbed * pane set. */ public class MartTab extends JPanel { private static final long serialVersionUID = 1; private JRadioButton datasetButton; private DataSetTabSet datasetTabSet; private JPanel displayArea; private Mart mart; private MartTabSet martTabSet; private JRadioButton schemaButton; private SchemaTabSet schemaTabSet; private String partitionViewSelection = null; private final ArrayList listeners = new ArrayList(); /** * Constructs a new tab in the tabbed pane that represents the given * mart. * * @param martTabSet * the tabbed pane set we are adding ourselves to. * @param mart * the mart we represent. */ public MartTab(final MartTabSet martTabSet, final Mart mart) { // Set up our layout. super(new BorderLayout()); // Remember which mart and tabset we are working with. this.martTabSet = martTabSet; this.mart = mart; // Create display part of the tab. The display area consists of // two cards - one for the schema editor, one for the dataset // editor. Buttons in another area switch between the cards. this.displayArea = new JPanel(new CardLayout()); // Create panel which contains the buttons. final JToolBar buttonsPanel = new JToolBar(Resources .get("martTabToolbarTitle")); // Add the Biomart logo to the buttons panel. final JLabel logo = new JLabel(new ImageIcon(Resources .getResourceAsURL("biomart-logo.gif"))); logo.setBorder(new EmptyBorder(4, 4, 4, 4)); buttonsPanel.add(logo); // Create the Run DDL button. final JButton runDDL = new JButton(Resources.get("runDDLButton"), new ImageIcon(Resources.getResourceAsURL("run.gif"))); runDDL.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { MartTab.this.martTabSet.requestRunDDL(); } }); buttonsPanel.add(runDDL); // Create the schema tabset. this.schemaTabSet = new SchemaTabSet(this); // Create the button that selects the window card. It reattaches // it every time in case it has been attached somewhere else // whilst we weren't looking. this.schemaButton = new JRadioButton(Resources .get("schemaEditorButtonName")); this.schemaButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { if (e.getSource() == MartTab.this.schemaButton) { final SchemaContext context = new SchemaContext( MartTab.this); MartTab.this.schemaTabSet.setDiagramContext(context); MartTab.this.displayArea.add(MartTab.this.schemaTabSet, "SCHEMA_EDITOR_CARD"); final CardLayout cards = (CardLayout) MartTab.this.displayArea .getLayout(); new LongProcess() { public void run() throws Exception { SwingUtilities.invokeAndWait(new Runnable() { public void run() { cards.show(MartTab.this.displayArea, "SCHEMA_EDITOR_CARD"); } }); } }.start(); } } }); buttonsPanel.add(this.schemaButton); // Create the dataset tabset. this.datasetTabSet = new DataSetTabSet(this); // Dataset card. this.displayArea.add(this.datasetTabSet, "DATASET_EDITOR_CARD"); // Create the button that selects the dataset card. this.datasetButton = new JRadioButton(Resources .get("datasetEditorButtonName")); this.datasetButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { if (e.getSource() == MartTab.this.datasetButton) { final CardLayout cards = (CardLayout) MartTab.this.displayArea .getLayout(); new LongProcess() { public void run() throws Exception { SwingUtilities.invokeAndWait(new Runnable() { public void run() { cards.show(MartTab.this.displayArea, "DATASET_EDITOR_CARD"); } }); } }.start(); } } }); buttonsPanel.add(this.datasetButton); // Make buttons mutually exclusive. final ButtonGroup buttons = new ButtonGroup(); buttons.add(this.schemaButton); buttons.add(this.datasetButton); // Drop-down menu to select current partition. final DefaultComboBoxModel partitionModel = new DefaultComboBoxModel(); final JComboBox partitions = new JComboBox(partitionModel); partitions.setBorder(new EmptyBorder(4, 4, 4, 4)); partitionModel.addElement(Resources.get("martTabAllPartitions")); partitions.setSelectedIndex(0); buttonsPanel.add(partitions); // On-click, before open, update contents to match // partitions of currently selected schema. Include an "All // partitions" option. If no schema currently selected, // show partitions from all. partitions.addPopupMenuListener(new PopupMenuListener() { public void popupMenuCanceled(PopupMenuEvent e) { } public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { } public void popupMenuWillBecomeVisible(PopupMenuEvent e) { int items = partitionModel.getSize(); final Object selection = partitionModel.getSelectedItem(); partitionModel.removeAllElements(); partitionModel.addElement(Resources .get("martTabAllPartitions")); for (final Iterator i = MartTab.this.getAllSchemaPrefixes() .iterator(); i.hasNext();) partitionModel.addElement(i.next()); partitionModel.setSelectedItem(selection); if (items != partitionModel.getSize()) { partitions.hidePopup(); partitions.showPopup(); } } }); // When drop-down changes, update local variable. partitions.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { if (partitions.getSelectedIndex() == 0) MartTab.this.setPartitionViewSelection(null); else MartTab.this .setPartitionViewSelection((String) partitions .getSelectedItem()); } }); // Add the buttons panel, and the display area containing the cards, // to the panel. this.add(buttonsPanel, BorderLayout.NORTH); this.add(this.displayArea, BorderLayout.CENTER); // Select the default button (which shows the schema card). this.selectSchemaEditor(); } /** * Return all possible schema prefixes, except the all-prefixes one. * * @return the collection. */ public Collection getAllSchemaPrefixes() { // Rebuild list from all schemas. final Set sortedPrefixes = new TreeSet(); for (final Iterator i = mart.getSchemas().values().iterator(); i .hasNext();) sortedPrefixes.addAll(((Schema) i.next()) .getReferencedPartitions()); return sortedPrefixes; } /** * Set which schema partition (if any) the user has selected. * * @param partitionViewSelection * the schema partition prefix. <tt>null</tt> if they have * selected All Partitions. */ public void setPartitionViewSelection( final String partitionViewSelection) { if (partitionViewSelection == this.partitionViewSelection || (this.partitionViewSelection != null && this.partitionViewSelection .equals(partitionViewSelection))) return; this.partitionViewSelection = partitionViewSelection; for (final Iterator i = this.listeners.iterator(); i.hasNext();) { final WeakReference ref = (WeakReference) i.next(); final PartitionViewSelectionListener listener = (PartitionViewSelectionListener) ref .get(); if (listener == null) i.remove(); else listener.partitionViewSelectionChanged(); } } /** * Add a listener to receive events when the partition view selection * dropdown changes. The listener will be stored with a * {@link WeakReference} and so will be dropped if it falls out of * scope. * * @param listener * the listener to register. */ public void addPartitionViewSelectionListener( final PartitionViewSelectionListener listener) { this.listeners.add(new WeakReference(listener)); } /** * Get which schema partition (if any) the user has selected. * * @return the schema partition prefix. <tt>null</tt> if they have * selected All Partitions. */ public String getPartitionViewSelection() { return this.partitionViewSelection; } /** * Obtain the tabbed pane set inside this one that represents the * datasets in this mart. * * @return the tabbed pane set showing the datasets in this mart. */ public DataSetTabSet getDataSetTabSet() { return this.datasetTabSet; } /** * Find out what mart we represent. * * @return the mart we represent. */ public Mart getMart() { return this.mart; } /** * Find out what tabbed pane set we belong to. * * @return the tabbed pane set we belong to. */ public MartTabSet getMartTabSet() { return this.martTabSet; } /** * Obtain the tabbed pane set inside this one that represents the * schemas in this mart. * * @return the tabbed pane set showing the schemas in this mart. */ public SchemaTabSet getSchemaTabSet() { return this.schemaTabSet; } /** * Fakes a click on the dataset editor radio button. */ public void selectDataSetEditor() { // May get called before button has been created. if (this.datasetButton != null && !this.datasetButton.isSelected()) this.datasetButton.doClick(); } /** * Fakes a click on the schema editor radio button. */ public void selectSchemaEditor() { // May get called before button has been created. if (this.schemaButton != null && !this.schemaButton.isSelected()) this.schemaButton.doClick(); } } /** * A listener that is called when the user changes the partition view * selection dropdown. */ public interface PartitionViewSelectionListener { /** * This method is called when the partition view selection dropdown is * changed. */ public void partitionViewSelectionChanged(); } }