// // FancySSCell.java // /* VisAD system for interactive analysis and visualization of numerical data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and Tommy Jasmin. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library 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 visad.ss; import java.awt.*; import java.io.*; import java.net.*; import java.rmi.*; import javax.swing.*; import javax.swing.border.*; import visad.*; import visad.data.*; import visad.formula.FormulaManager; import visad.util.*; /** * FancySSCell is an extension of BasicSSCell with extra options, such * as a file loader dialog and a dialog to set up ScalarMaps. * It provides an example of GUI extensions to BasicSSCell. */ public class FancySSCell extends BasicSSCell implements SSCellListener { // --- CONSTANTS --- /** * Dark red. */ public static final Color DARK_RED = new Color(0.5f, 0f, 0f); /** * Dark green. */ public static final Color DARK_GREEN = new Color(0f, 0.5f, 0f); /** * Dark blue. */ public static final Color DARK_BLUE = new Color(0f, 0f, 0.5f); /** * Dark yellow. */ public static final Color DARK_YELLOW = new Color(0.5f, 0.5f, 0f); /** * Dark purple. */ public static final Color DARK_PURPLE = new Color(0.5f, 0f, 0.5f); /** * Dark cyan. */ public static final Color DARK_CYAN = new Color(0f, 0.5f, 0.5f); /** * Border for cell with no data. */ public static final Border B_EMPTY = new LineBorder(Color.gray, 3); /** * Border for selected cell. */ public static final Border B_HIGHLIGHT = new LineBorder(Color.yellow, 3); /** * Border for cell with data from an unknown source. */ public static final Border B_UNKNOWN = new LineBorder(DARK_PURPLE, 3); /** * Border for cell with data set directly. */ public static final Border B_DIRECT = new LineBorder(DARK_CYAN, 3); /** * Border for cell with file or URL. */ public static final Border B_URL = new LineBorder(DARK_GREEN, 3); /** * Border for cell with formula. */ public static final Border B_FORMULA = new LineBorder(DARK_RED, 3); /** * Border for cell with RMI address. */ public static final Border B_RMI = new LineBorder(DARK_BLUE, 3); /** * Border for cell with data from a remote source. */ public static final Border B_REMOTE = new LineBorder(DARK_YELLOW, 3); /** * Border for cell with multiple data objects. */ public static final Border B_MULTI = new CompoundBorder( new CompoundBorder(new LineBorder(DARK_RED), new LineBorder(DARK_GREEN)), new LineBorder(DARK_BLUE)); // --- FIELDS --- /** * File chooser for loading and saving data. This variable is static so * that the directory is remembered between each load or save command. */ protected static JFileChooser FileBox = Util.getVisADFileChooser(); /** * Parent frame. */ protected Frame Parent; /** * Associated JFrame, for use with VisAD Controls. */ protected JFrame WidgetFrame; /** * Whether this cell is selected. */ protected boolean Selected = false; /** * Whether this cell should auto-switch to 3-D. */ protected boolean AutoSwitch = true; /** * Whether this cell should auto-detect mappings for data. */ protected boolean AutoDetect = true; /** * Whether this cell should auto-display its widget frame. */ protected boolean AutoShowControls = true; /** * Lock object for mapping auto-detection notification. */ private Object MapLock = new Object(); /** * Counter for mapping auto-detection notification. */ private int MapCount = 0; // --- CONSTRUCTORS --- /** * Constructs a new FancySSCell with the given name. */ public FancySSCell(String name) throws VisADException, RemoteException { this(name, null, null, false, null, null); } /** * Constructs a new FancySSCell with the given name and parent Frame. */ public FancySSCell(String name, Frame parent) throws VisADException, RemoteException { this(name, null, null, false, null, parent); } /** * Constructs a new FancySSCell with the given name, formula manager, * and parent Frame. */ public FancySSCell(String name, FormulaManager fman, Frame parent) throws VisADException, RemoteException { this(name, fman, null, false, null, parent); } /** * Constructs a new FancySSCell with the given name, remote server, * and parent Frame. */ public FancySSCell(String name, RemoteServer rs, Frame parent) throws VisADException, RemoteException { this(name, null, rs, false, null, parent); } /** * Constructs a new FancySSCell with the given name, save string, and * parent Frame. */ public FancySSCell(String name, String save, Frame parent) throws VisADException, RemoteException { this(name, null, null, false, save, parent); } /** * Constructs a new FancySSCell with the given name, formula manager, * remote server, save string, and parent Frame. */ public FancySSCell(String name, FormulaManager fman, RemoteServer rs, String save, Frame parent) throws VisADException, RemoteException { this(name, fman, rs, false, save, parent); } /** * Constructs a new, possibly slaved, FancySSCell with the given name, * formula manager, remote server, save string, and parent Frame. */ public FancySSCell(String name, FormulaManager fman, RemoteServer rs, boolean slave, String save, Frame parent) throws VisADException, RemoteException { super(name, fman, rs, slave, save); Parent = parent; WidgetFrame = new JFrame("Controls (" + Name + ")"); Util.invoke(false, DEBUG, new Runnable() { public void run() { setHighlighted(false); } }); addSSCellListener(this); } // --- DATA MANAGEMENT --- /** * Removes the Data object corresponding to the * given variable name from this cell. */ public void removeData(String varName) throws VisADException, RemoteException { super.removeData(varName); if (CellData.size() == 0) clearWidgetFrame(); } /** * Imports a data object from the given source of unknown type, * in a separate thread. */ public void loadDataSource(String source) { loadDataSource(source, UNKNOWN_SOURCE); } /** * Imports a data object from the given source of the specified type, * in a separate thread. */ public void loadDataSource(String source, int type) { final String fsource = source; final int ftype = type; final BasicSSCell cell = this; Runnable load = new Runnable() { public void run() { try { cell.addDataSource(fsource, ftype); if (!cell.hasData()) { JOptionPane.showMessageDialog(Parent, "Unable to import data", "Error importing data", JOptionPane.ERROR_MESSAGE); } } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); JOptionPane.showMessageDialog(Parent, exc.getMessage(), "Error importing data", JOptionPane.ERROR_MESSAGE); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); JOptionPane.showMessageDialog(Parent, exc.getMessage(), "Error importing data", JOptionPane.ERROR_MESSAGE); } } }; Thread t = new Thread(load); t.start(); } /** * Imports data from a file selected by the user. */ public void loadDataDialog() { // get file name from file dialog FileBox.setDialogType(JFileChooser.OPEN_DIALOG); if (FileBox.showOpenDialog(Parent) != JFileChooser.APPROVE_OPTION) return; // make sure file exists File f = FileBox.getSelectedFile(); if (!f.exists()) { JOptionPane.showMessageDialog(Parent, f.getName() + " does not exist", "Cannot load file", JOptionPane.ERROR_MESSAGE); return; } // load file loadDataSource(f.getAbsolutePath(), URL_SOURCE); } /** * Pops up a dialog box for user to select file where data will be saved. */ private File getSaveFile() { if (!hasData()) { JOptionPane.showMessageDialog(Parent, "This cell is empty.", "Nothing to save", JOptionPane.ERROR_MESSAGE); return null; } // get file name from file dialog FileBox.setDialogType(JFileChooser.SAVE_DIALOG); if (FileBox.showSaveDialog(Parent) != JFileChooser.APPROVE_OPTION) { return null; } return FileBox.getSelectedFile(); } /** * Saves a Data object to a file selected by the user, * using the given data form. */ public void saveDataDialog(String varName, Form saveForm) { // get file where data should be saved final File file = getSaveFile(); if (file == null) return; // start new thread to save the file final BasicSSCell cell = this; final String fname = varName; final Form form = saveForm; Runnable saveFile = new Runnable() { public void run() { String msg = "Could not save the dataset to the file " + "\"" + file.getName() + "\" in " + form.getName() + " format. "; try { cell.saveData(fname, file.getAbsolutePath(), form); } catch (BadFormException exc) { if (DEBUG) exc.printStackTrace(); msg = msg + "An error occurred: " + exc.getMessage(); JOptionPane.showMessageDialog(Parent, msg, "Error saving data", JOptionPane.ERROR_MESSAGE); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); msg = msg + "A remote error occurred: " + exc.getMessage(); JOptionPane.showMessageDialog(Parent, msg, "Error saving data", JOptionPane.ERROR_MESSAGE); } catch (IOException exc) { if (DEBUG) exc.printStackTrace(); msg = msg + "An I/O error occurred: " + exc.getMessage(); JOptionPane.showMessageDialog(Parent, msg, "Error saving data", JOptionPane.ERROR_MESSAGE); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); msg = msg + "An error occurred: " + exc.getMessage(); JOptionPane.showMessageDialog(Parent, msg, "Error saving data", JOptionPane.ERROR_MESSAGE); } } }; Thread t = new Thread(saveFile); t.start(); } /** * Blocks until mapping auto-detection is complete. */ public void waitForMaps() { synchronized (MapLock) { while (MapCount > 0) { try { MapLock.wait(); } catch (InterruptedException exc) { if (DEBUG && DEBUG_LEVEL >= 3) exc.printStackTrace(); } } } } /** * Does the work of adding the given DataReference, * from the given source of the specified type. * * @return The newly created SSCellData object. */ protected SSCellData addReferenceImpl(int id, DataReferenceImpl ref, ConstantMap[] cmaps, String source, int type, boolean notify, boolean checkErrors) throws VisADException, RemoteException { synchronized (MapLock) { MapCount++; } return super.addReferenceImpl(id, ref, cmaps, source, type, notify, checkErrors); } // --- DISPLAY MANAGEMENT --- /** * Whether the mapping dialog is currently being displayed. */ private boolean mapDialogUp = false; /** * Asks user to confirm clearing the cell if any other cell depends on it. */ public boolean confirmClear() { if (othersDepend()) { int ans = JOptionPane.showConfirmDialog(null, "Other cells depend on " + "this cell. Are you sure you want to clear it?", "Warning", JOptionPane.YES_NO_OPTION); if (ans != JOptionPane.YES_OPTION) return false; } return true; } /** * Clears the cell if no other cell depends on it; otherwise, ask the * user "Are you sure?" return true if the cell was cleared. */ public boolean smartClear() throws VisADException, RemoteException { if (confirmClear()) { clearWidgetFrame(); clearCell(); return true; } else return false; } /** * Permanently destroy this cell, asking user for confirmation first * if other cells depend on it; return true if the cell was destroyed. */ public boolean smartDestroy() throws VisADException, RemoteException { if (confirmClear()) { clearWidgetFrame(); destroyCell(); return true; } else return false; } /** * Switches to 3-D mode if necessary and available. */ public void setMapsAuto(ScalarMap[] maps) throws VisADException, RemoteException { if (AutoSwitch && maps != null) { int need = 0; for (int i=0; i<maps.length; i++) { DisplayRealType drt = maps[i].getDisplayScalar(); if (drt.equals(Display.ZAxis) || drt.equals(Display.Latitude)) { need = 2; } if (drt.equals(Display.Alpha) || drt.equals(Display.RGBA)) { if (need < 1) need = 1; } } // switch to Java3D mode if needed if (need == 2) setDimension(JAVA3D_3D); else if (need == 1 && Dim != JAVA3D_3D) setDimension(JAVA3D_2D); } setMaps(maps); } /** * Sets the ScalarMaps for this cell and creates needed control widgets. */ public void setMaps(ScalarMap[] maps) throws VisADException, RemoteException { super.setMaps(maps); if (WidgetFrame.isVisible() || AutoShowControls) showWidgetFrame(); } /** * Lets the user specify mappings between this cell's data and display. */ public void addMapDialog() { if (mapDialogUp) return; mapDialogUp = true; try { // check whether this cell has data if (getDataCount() == 0) { JOptionPane.showMessageDialog(Parent, "This cell has no data", "FancySSCell error", JOptionPane.ERROR_MESSAGE); return; } // get mappings from mapping dialog MappingDialog mapDialog = new MappingDialog(Parent, getData(), getMaps(), Dim != JAVA2D_2D || AutoSwitch, Dim == JAVA3D_3D || AutoSwitch); mapDialog.display(); // make sure user did not cancel the operation if (!mapDialog.okPressed()) return; // set up new mappings try { setMapsAuto(mapDialog.getMaps()); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); JOptionPane.showMessageDialog(Parent, "This combination of mappings is not valid: " + exc.getMessage(), "Cannot assign mappings", JOptionPane.ERROR_MESSAGE); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); JOptionPane.showMessageDialog(Parent, "This combination of mappings is not valid: " + exc.getMessage(), "Cannot assign mappings", JOptionPane.ERROR_MESSAGE); } } finally { mapDialogUp = false; } } /** * Guesses a good set of mappings for this cell's data and applies them. */ protected void autoDetectMappings() throws VisADException, RemoteException { if (AutoDetect) { boolean allow3d = (Dim != JAVA2D_2D || AutoSwitch); // guess mappings for Data objects int len = getDataCount(); Data[] data = getData(); MathType[] types = new MathType[len]; for (int i=0; i<len; i++) { Data d = data[i]; types[i] = (d == null ? null : d.getType()); } ScalarMap[] maps = DataUtility.guessMaps(types, allow3d); // apply the mappings setMapsAuto(maps); } // notify waitForMaps() method synchronized (MapLock) { MapCount--; MapLock.notifyAll(); } } // --- GUI MANAGEMENT --- /** * Shows the widgets for altering controls (if there are any). */ public synchronized void showWidgetFrame() { if (VDisplay == null || CellData.size() == 0) return; Util.invoke(false, DEBUG, new Runnable() { public void run() { Container jc = VDisplay.getWidgetPanel(); if (jc != null && jc.getComponentCount() > 0) { WidgetFrame.setContentPane(jc); WidgetFrame.pack(); WidgetFrame.setVisible(true); } } }); } /** * Hides the widgets for altering controls. */ public void hideWidgetFrame() { Util.invoke(false, DEBUG, new Runnable() { public void run() { WidgetFrame.setVisible(false); } }); } /** * Specifies whether the FancySSCell has a border. */ public void setBorderEnabled(boolean value) { if (value) setSelected(Selected); else setBorder(null); } /** * Specifies whether the FancySSCell has a highlighted border. */ public void setSelected(boolean value) { if (Selected == value) return; Selected = value; Util.invoke(false, DEBUG, new Runnable() { public void run() { setHighlighted(Selected); if (!Selected) hideWidgetFrame(); else if (AutoShowControls) showWidgetFrame(); refresh(); } }); } /** * Specifies whether this FancySSCell should auto-switch to 3-D. */ public synchronized void setAutoSwitch(boolean value) { AutoSwitch = value; } /** * Specifies whether this FancySSCell should auto-detect its mappings. */ public synchronized void setAutoDetect(boolean value) { AutoDetect = value; } /** * Specifies whether this FancySSCell should auto-display its widget frame. */ public synchronized void setAutoShowControls(boolean value) { AutoShowControls = value; } /** * Removes all widgets for altering controls and hide widget frame. */ private void clearWidgetFrame() { Util.invoke(false, DEBUG, new Runnable() { public void run() { WidgetFrame.setVisible(false); JPanel pane = new JPanel(); pane.add(new JLabel("No controls"), "CENTER"); WidgetFrame.setContentPane(pane); } }); } /** * Sets whether this cell is highlighted. */ private void setHighlighted(boolean hl) { if (hl) setBorder(B_HIGHLIGHT); else { int dataCount = getDataCount(); if (dataCount == 0) { // no datasets setBorder(B_EMPTY); } else if (dataCount == 1) { // single dataset int type = getDataSourceType(getFirstVariableName()); if (type == DIRECT_SOURCE) setBorder(B_DIRECT); else if (type == URL_SOURCE) setBorder(B_URL); else if (type == FORMULA_SOURCE) setBorder(B_FORMULA); else if (type == RMI_SOURCE) setBorder(B_RMI); else if (type == REMOTE_SOURCE) setBorder(B_REMOTE); else setBorder(B_UNKNOWN); } else { // multiple datasets setBorder(B_MULTI); } } } // --- EVENT HANDLING --- /** * Re-detects mappings when this cell's data changes. */ public void ssCellChanged(SSCellChangeEvent e) { int type = e.getChangeType(); if (type == SSCellChangeEvent.DATA_CHANGE) { // refresh border color Util.invoke(false, DEBUG, new Runnable() { public void run() { setHighlighted(Selected); } }); if (!IsRemote) { // attempt to auto-detect mappings for new data Data value = null; try { value = (Data) fm.getThing(e.getVariableName()); } catch (ClassCastException exc) { if (DEBUG) exc.printStackTrace(); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } try { if (value != null) autoDetectMappings(); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); } } } else if (type == SSCellChangeEvent.DISPLAY_CHANGE) { if (IsRemote) { // reconstruct controls for cloned display if (AutoShowControls) showWidgetFrame(); } } } // --- MISCELLANEOUS --- /** * Captures display image and saves to a file selected by the user, * in JPEG format. */ public void captureDialog() { // get file where captured image should be saved final File f = getSaveFile(); if (f == null) return; // start new thread to capture image and save it to the file final BasicSSCell cell = this; Runnable captureImage = new Runnable() { public void run() { String msg = "Could not save image snapshot to file \"" + f.getName() + "\" in JPEG format. "; try { cell.captureImage(f); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); msg = msg + "An error occurred: " + exc.getMessage(); JOptionPane.showMessageDialog(Parent, msg, "Error saving data", JOptionPane.ERROR_MESSAGE); } catch (IOException exc) { if (DEBUG) exc.printStackTrace(); msg = msg + "An I/O error occurred: " + exc.getMessage(); JOptionPane.showMessageDialog(Parent, msg, "Error saving data", JOptionPane.ERROR_MESSAGE); } } }; Thread t = new Thread(captureImage); t.start(); } // --- ACCESSORS --- /** * Returns whether this FancySSCell auto-switches to 3-D. */ public boolean getAutoSwitch() { return AutoSwitch; } /** * Returns whether this FancySSCell auto-detects its mappings. */ public boolean getAutoDetect() { return AutoDetect; } /** * Returns whether this FancySSCell auto-displays its widget frame. */ public boolean getAutoShowControls() { return AutoShowControls; } /** * Returns whether the cell has any associated controls. */ public boolean hasControls() { if (VDisplay == null || CellData.size() == 0) return false; Container jc = VDisplay.getWidgetPanel(); if (jc == null) return false; return (jc.getComponentCount() > 0); } // --- DEPRECATED --- /** * @deprecated Use loadDataSource(String, RMI_SOURCE) instead. */ public void loadDataRMI(String s) { loadDataSource(s, RMI_SOURCE); } /** * @deprecated Use loadDataSource(String, URL_SOURCE) instead. */ public synchronized void loadDataString(String s) { loadDataSource(s, URL_SOURCE); } /** * @deprecated Use loadDataSource(String, URL_SOURCE) instead. */ public void loadDataURL(URL u) { loadDataSource(u.toString(), URL_SOURCE); } /** * @deprecated Use saveDataDialog(String, Form) instead. */ public void saveDataDialog(boolean netcdf) { try { Form f; if (netcdf) f = new visad.data.netcdf.Plain(); else f = new visad.data.visad.VisADForm(); saveDataDialog(getFirstVariableName(), f); } catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); } } /** * @deprecated Use saveDataDialog(String, Form) instead. */ public void saveDataDialog(Form saveForm) { saveDataDialog(getFirstVariableName(), saveForm); } }