/* *------------------------------------------------------------------------------ * Copyright (C) 2006-2015 University of Dundee. All rights reserved. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.agents.metadata.editor; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import org.jdesktop.swingx.JXTaskPane; import org.openmicroscopy.shoola.agents.metadata.MetadataViewerAgent; import org.openmicroscopy.shoola.agents.util.DataComponent; import org.openmicroscopy.shoola.agents.util.EditorUtil; import org.openmicroscopy.shoola.util.ui.JLabelButton; import org.openmicroscopy.shoola.util.ui.UIUtilities; import omero.gateway.model.ChannelData; import omero.gateway.model.FileAnnotationData; /** * Component displaying the acquisition information. * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author Donald MacDonald      * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a> * @version 3.0 * <small> * (<b>Internal version:</b> $Revision: $Date: $) * </small> * @since 3.0-Beta4 */ class AcquisitionDataUI extends JPanel implements PropertyChangeListener { /** Text to show the unset fields. */ static final String SHOW_UNSET = "Show unset fields"; /** Text to hide the unset fields. */ static final String HIDE_UNSET = "Hide unset fields"; /** Indicates if a boolean has been set to <code>true</code>. */ static final String BOOLEAN_YES = "Yes"; /** Indicates if a boolean has been set to <code>false</code>. */ static final String BOOLEAN_NO = "No"; /** The collection of enumerations supported corresponding to unset value.*/ static final List<String> UNSET_ENUM; /** The default text of the channel. */ private static final String DEFAULT_CHANNEL_TEXT = "";//"Channel "; static { UNSET_ENUM = new ArrayList<String>(); UNSET_ENUM.add("Other"); UNSET_ENUM.add("Unknown"); } /** Reference to the controller. */ private EditorControl controller; /** Reference to the Model. */ private EditorModel model; /** Reference to the Model. */ private EditorUI view; /** The component hosting the image acquisition data. */ private ImageAcquisitionComponent imageAcquisition; /** * The component hosting information about the instrument used to capture * the image. */ private InstrumentComponent instrument; /** The collection of channel acquisition data. */ private Map<JXTaskPane, ChannelData> channelAcquisitionPanes; /** The component hosting the image info. */ private JXTaskPane imagePane; /** The component hosting the instrument info. */ private JXTaskPane instrumentPane; /** The component hosting the original metadata. */ private JXTaskPane originalMetadataPane; /** Collection of components hosting the channel. */ private List<ChannelAcquisitionComponent> channelComps; /** The UI component hosting the <code>JXTaskPane</code>s. */ private JPanel container; /** The constraints used to lay out the components in the container. */ private GridBagConstraints constraints; /** Component displaying the original metadata. */ private OriginalMetadataComponent originalMetadata; /** The component hosting the companion files. */ private JXTaskPane companionFilesPane; /** Flag indicating to build the UI once. */ private boolean init; /** Components displaying the companion files. */ private JPanel docPane; /** Initializes the UI components. */ private void initComponents() { container = new JPanel(); channelComps = new ArrayList<ChannelAcquisitionComponent>(); channelAcquisitionPanes = new LinkedHashMap<JXTaskPane, ChannelData>(); imageAcquisition = new ImageAcquisitionComponent(this, model); imageAcquisition.addPropertyChangeListener(this); imagePane = EditorUtil.createTaskPane("Image"); imagePane.add(imageAcquisition); imagePane.addPropertyChangeListener( UIUtilities.COLLAPSED_PROPERTY_JXTASKPANE, this); instrument = new InstrumentComponent(this, model); instrumentPane = EditorUtil.createTaskPane("Microscope"); instrumentPane.add(instrument); instrumentPane.addPropertyChangeListener( UIUtilities.COLLAPSED_PROPERTY_JXTASKPANE, this); originalMetadataPane = EditorUtil.createTaskPane("Original Metadata"); originalMetadataPane.addPropertyChangeListener( UIUtilities.COLLAPSED_PROPERTY_JXTASKPANE, this); originalMetadata = new OriginalMetadataComponent(model); originalMetadataPane.add(originalMetadata); companionFilesPane = EditorUtil.createTaskPane("Companion Files"); docPane = new JPanel(); docPane.setLayout(new BoxLayout(docPane, BoxLayout.Y_AXIS)); docPane.setBackground(UIUtilities.BACKGROUND_COLOR); companionFilesPane.add(docPane); } /** Builds and lays out the components. */ private void buildGUI() { container.setBackground(UIUtilities.BACKGROUND_COLOR); container.setLayout(new GridBagLayout()); setBackground(UIUtilities.BACKGROUND_COLOR); setLayout(new BorderLayout(0, 0)); constraints = new GridBagConstraints(); constraints.gridy = 0; constraints.fill = GridBagConstraints.BOTH; constraints.anchor = GridBagConstraints.WEST; constraints.insets = new Insets(0, 2, 2, 0); constraints.weightx = 1.0; container.add(companionFilesPane, constraints); constraints.gridy++; container.add(originalMetadataPane, constraints); constraints.gridy++; container.add(instrumentPane, constraints); constraints.gridy++; container.add(imagePane, constraints); add(container, BorderLayout.NORTH); } /** Lays out the channels. */ private void layoutUI() { if (!init) { buildGUI(); init = true; } int n = container.getComponentCount(); if (n == 0) return; Component[] comps = container.getComponents(); for (int i = 0; i < comps.length; i++) { if (comps[i] != imagePane && comps[i] != instrumentPane && comps[i] != originalMetadataPane && comps[i] != companionFilesPane) container.remove(comps[i]); } constraints.gridy = 4; Iterator<JXTaskPane> i = channelAcquisitionPanes.keySet().iterator(); while (i.hasNext()) { ++constraints.gridy; container.add(i.next(), constraints); } revalidate(); repaint(); } /** * Creates a new instance. * * @param view Reference to the View. Mustn't be <code>null</code>. * @param model Reference to the Model. * Mustn't be <code>null</code>. * @param controller Reference to the Control. * Mustn't be <code>null</code>. */ AcquisitionDataUI(EditorUI view, EditorModel model, EditorControl controller) { if (model == null) throw new IllegalArgumentException("No model."); if (controller == null) throw new IllegalArgumentException("No control."); if (view == null) throw new IllegalArgumentException("No view."); this.model = model; this.controller = controller; this.view = view; initComponents(); init = false; //buildGUI(); } /** * Attaches listener to each item of the map. * * @param map The map to handle. */ void attachListener(Map<String, DataComponent> map) { Iterator i = map.entrySet().iterator(); Entry entry; while (i.hasNext()) { entry = (Entry) i.next(); ((DataComponent) entry.getValue()).attachListener(controller); } } /** * Returns <code>true</code> if one of the components has been modified, * <code>false</code> otherwise. * * @param map The map to handle. * @return See above. */ boolean hasDataToSave(Map<String, DataComponent> map) { if (map == null) return false; Iterator i = map.entrySet().iterator(); DataComponent comp; Entry entry; while (i.hasNext()) { entry = (Entry) i.next(); comp = (DataComponent) entry.getValue(); if (comp.isDirty()) return true; } return false; } /** * Formats the button used to hide or show the unset fields * for acquisition metadata. * * @return See above. */ JLabelButton formatUnsetFieldsControl() { JLabelButton button = new JLabelButton(AcquisitionDataUI.SHOW_UNSET); Font font = button.getFont(); int sizeLabel = font.getSize()-2; //UIUtilities.unifiedButtonLookAndFeel(button); button.setFont(font.deriveFont(Font.ITALIC, sizeLabel)); button.setBackground(UIUtilities.BACKGROUND_COLOR); button.setForeground(UIUtilities.HYPERLINK_COLOR); return button; } /** * Formats the component. * * @param comp The component to format. * @param title The title to add to the border. */ void format(JComponent comp, String title) { if (comp == null) return; if (title == null) title = ""; comp.setBorder(BorderFactory.createTitledBorder(title)); comp.setBackground(UIUtilities.BACKGROUND_COLOR); comp.setLayout(new GridBagLayout()); } /** * Replaces the combox by a label, due to a painting problem since we * switched to openGL. * * @param box The box to change. * @return See above. */ JComponent replaceCombobox(JComboBox box) { JLabel l = new JLabel(); l.setBackground(UIUtilities.BACKGROUND_COLOR); if (box != null) { l.setFont(box.getFont()); l.setText(box.getSelectedItem().toString()); } return l; } /** * Lays out the passed component. * * @param pane The main component. * @param button The button to show or hide the unset fields. * @param fields The fields to lay out. * @param shown Pass <code>true</code> to show the unset fields, * <code>false</code> to hide them. */ void layoutFields(JPanel pane, JComponent button, Map<String, DataComponent> fields, boolean shown) { pane.removeAll(); GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.WEST; c.insets = new Insets(0, 2, 2, 0); DataComponent comp; Set set = fields.entrySet(); Entry entry; Iterator i = set.iterator(); c.gridy = 0; while (i.hasNext()) { c.gridx = 0; entry = (Entry) i.next(); comp = (DataComponent) entry.getValue(); if (comp.isSetField() || shown) { ++c.gridy; c.gridwidth = GridBagConstraints.RELATIVE; //next-to-last c.fill = GridBagConstraints.NONE; //reset to default c.weightx = 0.0; pane.add(comp.getLabel(), c); c.gridx++; pane.add(Box.createHorizontalStrut(5), c); c.gridx++; c.gridwidth = GridBagConstraints.REMAINDER; //end row c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1.0; pane.add(comp.getArea(), c); } } if (c.gridy != 0) ++c.gridy; c.gridx = 0; c.gridwidth = GridBagConstraints.REMAINDER; //end row c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1.0; if (button != null) pane.add(button, c); } /** * Returns the boolean value corresponding to the passed string * or <code>null</code>. * * @param value The value to convert. * @return See above. */ Boolean convertToBoolean(String value) { if (value == null) return null; if (BOOLEAN_YES.equals(value)) return Boolean.valueOf(true); else if (BOOLEAN_NO.equals(value)) return Boolean.valueOf(false); return null; } /** Sets the data when data are loaded, builds the UI. */ void setChannelData() { Map channels = model.getChannelData(); channelAcquisitionPanes.clear(); channelComps.clear(); if (channels != null) { ChannelData channel; Iterator i = channels.entrySet().iterator(); ChannelAcquisitionComponent comp; JXTaskPane p; Entry entry; while (i.hasNext()) { entry = (Entry) i.next(); channel = (ChannelData) entry.getKey(); comp = new ChannelAcquisitionComponent(this, model, channel); comp.setChannelColor((Color) entry.getValue()); p = EditorUtil.createTaskPane(DEFAULT_CHANNEL_TEXT+ channel.getChannelLabeling()); p.setIcon(comp.getIcon()); p.add(comp); p.addPropertyChangeListener( UIUtilities.COLLAPSED_PROPERTY_JXTASKPANE, this); channelAcquisitionPanes.put(p, channel); channelComps.add(comp); } } layoutUI(); } /** * Indicates that the color of the passed channel has changed. * * @param index The index of the channel. */ void onChannelColorChanged(int index) { ChannelAcquisitionComponent comp; Iterator<ChannelAcquisitionComponent> i = channelComps.iterator(); ChannelData data; while (i.hasNext()) { comp = i.next(); if (comp.getChannelIndex() == index) { comp.setChannelColor(model.getChannelColor(index)); comp.repaint(); break; } } } /** Sets the metadata. */ void setImageAcquisitionData() { imageAcquisition.setImageAcquisitionData(); revalidate(); } /** Sets the instrument and its components. */ void setInstrumentData() { instrument.setInstrumentData(); revalidate(); } /** * Displays the acquisition data for the passed channel. * * @param index The index of the channel. */ void setChannelAcquisitionData(int index) { Iterator<ChannelAcquisitionComponent> i = channelComps.iterator(); while (i.hasNext()) i.next().setChannelAcquisitionData(index); revalidate(); } /** * Sets the plane info for the specified channel. * * @param index The index of the channel. */ void setPlaneInfo(int index) { Iterator<ChannelAcquisitionComponent> i = channelComps.iterator(); while (i.hasNext()) i.next().setPlaneInfo(index); } /** * Updates display when the new root node is set. * Loads the acquisition data if the passed parameter is <code>true</code> * and the {@link #imagePane} is expanded. * * @param load Pass <code>true</code> to load the image data, * <code>false</code> otherwise. */ void setRootObject(boolean load) { instrument.setRootObject(); imageAcquisition.setRootObject(); channelAcquisitionPanes.clear(); originalMetadata.clear(); layoutUI(); repaint(); if (!imagePane.isCollapsed() && load) controller.loadImageAcquisitionData(); if (!instrumentPane.isCollapsed() && load) controller.loadInstrumentData(); originalMetadataPane.setCollapsed(true); if (MetadataViewerAgent.isBinaryAvailable()) { originalMetadataPane.setVisible(true); } else originalMetadataPane.setVisible(false); } /** * Returns <code>true</code> if data to save, <code>false</code> * otherwise. * * @return See above. */ boolean hasDataToSave() { return false; //For Beta4.0.0,metadata not editable. /* boolean b = imageAcquisition.hasDataToSave(); if (b) return b; Iterator<ChannelAcquisitionComponent> i = channelComps.iterator(); while (i.hasNext()) { b = i.next().hasDataToSave(); if (b) return true; } return false; */ } /** * Prepares the data to save. * * @return See above. */ List<Object> prepareDataToSave() { List<Object> data = new ArrayList<Object>(); if (!hasDataToSave()) return data; Object object = imageAcquisition.prepareDataToSave(); if (object != null) data.add(object); Iterator<ChannelAcquisitionComponent> i = channelComps.iterator(); List<Object> objects; while (i.hasNext()) { objects = i.next().prepareDataToSave(); if (objects.size() > 0) data.addAll(objects); } return data; } /** * Loads the planes for the specified channel. * * @param index The index of the channel. */ void loadPlaneInfo(int index) { if (model.getChannelPlaneInfo(index) == null) { model.firePlaneInfoLoading(index, 0); view.setStatus(true); } } /** Lays out the companion files if any. */ void layoutCompanionFiles() { boolean b = model.hasOriginalMetadata() && MetadataViewerAgent.isBinaryAvailable(); originalMetadataPane.setVisible(b); if (b) originalMetadataPane.setCollapsed(true); Collection list = model.getCompanionFiles(); if (list == null || list.size() == 0) { companionFilesPane.setVisible(false); revalidate(); repaint(); return; } companionFilesPane.setVisible(true); Iterator i = list.iterator(); FileAnnotationData file; DocComponent doc; docPane.removeAll(); while (i.hasNext()) { docPane.add(new DocComponent(i.next(), model, false, false)); } revalidate(); repaint(); } /** Refreshes the view. */ void refresh() { /* instrument.setRootObject(); imageAcquisition.setRootObject(); originalMetadata.clear(); layoutUI(); repaint(); originalMetadataPane.setCollapsed(true); instrumentPane.setCollapsed(true); imagePane.setCollapsed(true); */ } /** * Loads the acquisition data. * @see PropertyChangeListener#propertyChange(PropertyChangeEvent) */ public void propertyChange(PropertyChangeEvent evt) { String name = evt.getPropertyName(); if (!UIUtilities.COLLAPSED_PROPERTY_JXTASKPANE.equals(name)) return; Object src = evt.getSource(); if (src == imagePane) { controller.loadImageAcquisitionData(); } else if (src == instrumentPane) { controller.loadInstrumentData(); } else if (src == originalMetadataPane) { if (MetadataViewerAgent.isBinaryAvailable()) { if (model.hasOriginalMetadata() && model.getImage() != null) { if (!originalMetadata.isMetadataLoaded()) model.loadOriginalMetadata(originalMetadata); } else originalMetadataPane.setCollapsed(true); } } else { ChannelData channel = channelAcquisitionPanes.get(src); controller.loadChannelAcquisitionData(channel); } } }