/* *------------------------------------------------------------------------------ * 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.Color; import java.awt.Dimension; 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.text.DecimalFormat; import java.util.ArrayList; import java.util.Calendar; 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.Icon; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import org.apache.commons.lang.time.DateUtils; import org.jdesktop.swingx.JXTaskPane; import omero.model.AcquisitionMode; import omero.model.ContrastMethod; import omero.model.Illumination; import omero.model.LengthI; import omero.model.PlaneInfo; import ome.formats.model.UnitsFactory; import ome.model.units.BigResult; 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.env.data.model.EnumerationObject; import org.openmicroscopy.shoola.util.ui.ColourIcon; import org.openmicroscopy.shoola.util.ui.JLabelButton; import org.openmicroscopy.shoola.util.ui.NumericalTextField; import org.openmicroscopy.shoola.util.ui.OMEComboBox; import org.openmicroscopy.shoola.util.ui.OMETextArea; import org.openmicroscopy.shoola.util.ui.UIUtilities; import omero.gateway.model.ChannelAcquisitionData; import omero.gateway.model.ChannelData; import omero.gateway.model.DataObject; import omero.gateway.model.LightSourceData; /** * Displays the metadata related to the channel. * * @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 * @since 3.0-Beta4 */ class ChannelAcquisitionComponent extends JPanel implements PropertyChangeListener { /** Action ID to show or hide the unset general data. */ private static final int GENERAL = 0; /** Format used for displaying time in ms */ private static final DecimalFormat msFormat = new DecimalFormat("# ms"); /** Format used for displaying time in s */ private static final DecimalFormat sFormat = new DecimalFormat("0.0 s"); /** Reference to the parent of this component. */ private AcquisitionDataUI parent; /** The channel data. */ private ChannelData channel; /** The component displaying the illumination's options. */ private OMEComboBox illuminationBox; /** The component displaying the contrast Method options. */ private OMEComboBox contrastMethodBox; /** The component displaying the contrast Method options. */ private OMEComboBox modeBox; /** The fields displaying the metadata. */ private Map<String, DataComponent> fieldsGeneral; /** The UI component hosting the lightSource metadata. */ private LightSourceComponent lightPane; /** The UI component hosting the detector metadata. */ private DetectorComponent detectorPane; /** The UI component hosting the filter set or <code>null</code>. */ private FilterGroupComponent filterSetPane; /** The UI component hosting the light path or <code>null</code>. */ private FilterGroupComponent lightPathPane; /** Button to show or hides the unset fields of the detector. */ private JLabelButton unsetGeneral; /** Flag indicating the unset fields for the general are displayed. */ private boolean unsetGeneralShown; /** The UI component hosting the general metadata. */ private JPanel generalPane; /** Flag indicating if the components have been initialized. */ private boolean init; /** Reference to the Model. */ private EditorModel model; /** The component hosting the exposure time. */ private JXTaskPane exposureTask; /** The icon displaying the color associated to the channel. */ private ColourIcon icon; /** Resets the various boxes with enumerations. */ private void resetBoxes() { List<EnumerationObject> l; l = model.getChannelEnumerations(Editor.ILLUMINATION_TYPE); EnumerationObject[] array = new EnumerationObject[l.size()+1]; Iterator<EnumerationObject> j = l.iterator(); int i = 0; while (j.hasNext()) { array[i] = j.next(); i++; } array[i] = new EnumerationObject(AnnotationUI.NO_SET_TEXT); illuminationBox = EditorUtil.createComboBox(array); l = model.getChannelEnumerations(Editor.CONTRAST_METHOD); array = new EnumerationObject[l.size()+1]; j = l.iterator(); i = 0; while (j.hasNext()) { array[i] = j.next(); i++; } array[i] = new EnumerationObject(AnnotationUI.NO_SET_TEXT); contrastMethodBox = EditorUtil.createComboBox(array); l = model.getChannelEnumerations(Editor.MODE); array = new EnumerationObject[l.size()+1]; j = l.iterator(); i = 0; while (j.hasNext()) { array[i] = j.next(); i++; } array[i] = new EnumerationObject(AnnotationUI.NO_SET_TEXT); modeBox = EditorUtil.createComboBox(array); l = model.getChannelEnumerations(Editor.BINNING); array = new EnumerationObject[l.size()+1]; j = l.iterator(); i = 0; while (j.hasNext()) { array[i] = j.next(); i++; } array[i] = new EnumerationObject(AnnotationUI.NO_SET_TEXT); } /** Initializes the components */ private void initComponents() { resetBoxes(); fieldsGeneral = new LinkedHashMap<String, DataComponent>(); detectorPane = new DetectorComponent(parent, model); lightPane = new LightSourceComponent(parent, model); unsetGeneral = null; unsetGeneralShown = false; generalPane = new JPanel(); generalPane.setBorder(BorderFactory.createTitledBorder("Info")); generalPane.setBackground(UIUtilities.BACKGROUND_COLOR); generalPane.setLayout(new GridBagLayout()); exposureTask = EditorUtil.createTaskPane("Exposure Time"); exposureTask.addPropertyChangeListener(this); } /** Shows or hides the unset fields. */ private void displayUnsetGeneralFields() { unsetGeneralShown = !unsetGeneralShown; String s = AcquisitionDataUI.SHOW_UNSET; if (unsetGeneralShown) s = AcquisitionDataUI.HIDE_UNSET; unsetGeneral.setText(s); parent.layoutFields(generalPane, unsetGeneral, fieldsGeneral, unsetGeneralShown); } /** * Transforms the detector metadata. * * @param details The value to transform. */ private void transformGeneralSource(Map<String, Object> details) { DataComponent comp; JLabel label; JComponent area; String key; Object value; label = new JLabel(); Font font = label.getFont(); int sizeLabel = font.getSize()-2; Object selected; List notSet = (List) details.get(EditorUtil.NOT_SET); details.remove(EditorUtil.NOT_SET); if (notSet.size() > 0 && unsetGeneral == null) { unsetGeneral = parent.formatUnsetFieldsControl(); unsetGeneral.setActionID(GENERAL); unsetGeneral.addPropertyChangeListener(this); } Set entrySet = details.entrySet(); Entry entry; Iterator i = entrySet.iterator(); boolean set; while (i.hasNext()) { entry = (Entry) i.next(); key = (String) entry.getKey(); set = !notSet.contains(key); value = entry.getValue(); label = UIUtilities.setTextFont(key, Font.BOLD, sizeLabel); label.setBackground(UIUtilities.BACKGROUND_COLOR); if (EditorUtil.ILLUMINATION.equals(key)) { selected = model.getChannelEnumerationSelected( Editor.ILLUMINATION_TYPE, (String) value); if (selected != null) illuminationBox.setSelectedItem(selected); else { set = false; illuminationBox.setSelectedIndex( illuminationBox.getItemCount()-1); } illuminationBox.setEditedColor(UIUtilities.EDITED_COLOR); area = illuminationBox;//parent.replaceCombobox(illuminationBox); } else if (EditorUtil.CONTRAST_METHOD.equals(key)) { selected = model.getChannelEnumerationSelected( Editor.ILLUMINATION_TYPE, (String) value); if (selected != null) contrastMethodBox.setSelectedItem(selected); else { set = false; contrastMethodBox.setSelectedIndex( contrastMethodBox.getItemCount()-1); } contrastMethodBox.setEditedColor(UIUtilities.EDITED_COLOR); area = contrastMethodBox;//parent.replaceCombobox(contrastMethodBox); } else if (EditorUtil.MODE.equals(key)) { selected = model.getChannelEnumerationSelected(Editor.MODE, (String) value); if (selected != null) modeBox.setSelectedItem(selected); else { set = false; modeBox.setSelectedIndex(modeBox.getItemCount()-1); } modeBox.setEditedColor(UIUtilities.EDITED_COLOR); area = modeBox;//parent.replaceCombobox(modeBox); } else { if (value instanceof Number) { area = UIUtilities.createComponent(NumericalTextField.class, null); String v = ""; if (value instanceof Double) { v = ""+UIUtilities.roundTwoDecimals( ((Number) value).doubleValue()); ((NumericalTextField) area).setNumberType(Double.class); } else if (value instanceof Float) { v = ""+UIUtilities.roundTwoDecimals( ((Number) value).doubleValue()); ((NumericalTextField) area).setNumberType(Float.class); } else v = ""+value; ((NumericalTextField) area).setText(v); ((NumericalTextField) area).setEditedColor( UIUtilities.EDITED_COLOR); } else { area = UIUtilities.createComponent(OMETextArea.class, null); if (value == null || value.equals("")) { set = false; value = AnnotationUI.DEFAULT_TEXT; } ((OMETextArea) area).setEditable(false); ((OMETextArea) area).setText((String) value); ((OMETextArea) area).setEditedColor( UIUtilities.EDITED_COLOR); } } area.setEnabled(!set); comp = new DataComponent(label, area); comp.setEnabled(false); comp.setSetField(!notSet.contains(key)); fieldsGeneral.put(key, comp); } } /** Builds and lays out the UI. */ private void buildGUI() { setBackground(UIUtilities.BACKGROUND_COLOR); parent.layoutFields(generalPane, unsetGeneral, fieldsGeneral, unsetGeneralShown); setLayout(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.BOTH; constraints.anchor = GridBagConstraints.WEST; constraints.insets = new Insets(0, 2, 2, 0); constraints.weightx = 1.0; constraints.gridy = 0; if (generalPane.isVisible()) { add(generalPane, constraints); ++constraints.gridy; } if (detectorPane.isVisible()) { add(detectorPane, constraints); ++constraints.gridy; } if (lightPane.isVisible()) { add(lightPane, constraints); ++constraints.gridy; } if (lightPathPane != null) { add(lightPathPane, constraints); ++constraints.gridy; } if (filterSetPane != null) { add(filterSetPane, constraints); ++constraints.gridy; } constraints.fill = GridBagConstraints.HORIZONTAL; add(exposureTask, constraints); parent.attachListener(fieldsGeneral); } /** * Creates a new instance. * * @param parent Reference to the Parent. Mustn't be <code>null</code>. * @param model Reference to the Model. Mustn't be <code>null</code>. * @param channel The channel to display. Mustn't be <code>null</code>. */ ChannelAcquisitionComponent(AcquisitionDataUI parent, EditorModel model, ChannelData channel) { if (model == null) throw new IllegalArgumentException("No model."); if (parent == null) throw new IllegalArgumentException("No parent."); if (channel == null) throw new IllegalArgumentException("No channel."); this.model = model; this.channel = channel; this.parent = parent; initComponents(); } /** * Returns the index of the channel hosted by this component. * * @return See above. */ int getChannelIndex() { return channel.getIndex(); } /** * Sets the color associated to that channel. * * @param color The associated color. */ void setChannelColor(Color color) { if (color == null) return; if (icon == null) { icon = new ColourIcon(color); icon.paintLineBorder(true); } else icon.setColour(color); } /** * Returns the color icon associated to the channel or <code>null</code>. * * @return */ Icon getIcon() { return icon; } /** * Displays the acquisition data for the passed channel. * * @param index The index of the channel. */ void setChannelAcquisitionData(int index) { if (channel.getIndex() != index) return; if (!init) { init = true; resetBoxes(); removeAll(); fieldsGeneral.clear(); ChannelAcquisitionData data = model.getChannelAcquisitionData( channel.getIndex()); Map<String, Object> details = EditorUtil.transformChannelData(channel); List notSet = (List) details.get(EditorUtil.NOT_SET); generalPane.setVisible(false); if (notSet.size() != EditorUtil.MAX_FIELDS_CHANNEL) { transformGeneralSource(details); generalPane.setVisible(true); } //if no detector info: don't display. details = EditorUtil.transformDetectorAndSettings(data); notSet = (List) details.get(EditorUtil.NOT_SET); detectorPane.setVisible(false); if (notSet.size() != EditorUtil.MAX_FIELDS_DETECTOR_AND_SETTINGS) { detectorPane.displayDetector(details); detectorPane.setVisible(true); } details = EditorUtil.transformLightSourceAndSetting(data); String kind = (String) details.get(EditorUtil.LIGHT_TYPE); details.remove(EditorUtil.LIGHT_TYPE); notSet = (List) details.get(EditorUtil.NOT_SET); lightPane.setVisible(false); int n = EditorUtil.MAX_FIELDS_LIGHT_AND_SETTINGS; if (LightSourceData.LASER.equals(kind)) n = EditorUtil.MAX_FIELDS_LASER_AND_SETTINGS; if (notSet.size() != n) { lightPane.displayLightSource(kind, details); lightPane.setVisible(true); } DataObject set = data.getFilterSet(); if (set != null) filterSetPane = new FilterGroupComponent(parent, model, set); set = data.getLightPath(); if (set != null) lightPathPane = new FilterGroupComponent(parent, model, set); buildGUI(); //load the exposure time exposureTask.setCollapsed(false); } } /** * Formats time into a more human readable format * * @param tInS * The time in seconds * @return See above. */ private String getReadableTime(double tInS) { if (tInS == 0.0) return "0 s"; Calendar date = Calendar.getInstance(); date = DateUtils.truncate(date, Calendar.YEAR); date.add(Calendar.MILLISECOND, (int) (tInS * 1000)); int d, h, m, s; if (tInS > (23 * 60 * 60)) { date = DateUtils.round(date, Calendar.MINUTE); d = date.get(Calendar.DAY_OF_YEAR) - 1; h = date.get(Calendar.HOUR_OF_DAY); m = date.get(Calendar.MINUTE); return d + " d " + h + " h " + (m > 0 ? m + " min " : ""); } else if (tInS > (59 * 60)) { date = DateUtils.round(date, Calendar.MINUTE); h = date.get(Calendar.HOUR_OF_DAY); m = date.get(Calendar.MINUTE); return h + " h " + m + " min"; } else if (tInS > 59) { date = DateUtils.round(date, Calendar.SECOND); m = date.get(Calendar.MINUTE); s = date.get(Calendar.SECOND); return m + " min " + s + " s"; } else if (tInS > 0.9) return sFormat.format(tInS); return msFormat.format(tInS * 1000); } /** * Sets the plane info for the specified channel. * * @param index The index of the channel. */ void setPlaneInfo(int index) { if (channel.getIndex() != index) return; Collection result = model.getChannelPlaneInfo(index); String[][] values = new String[2][result.size()+1]; String[] names = new String[result.size()+1]; int i = 0; Iterator j = result.iterator(); PlaneInfo info; Map<String, Object> details; List<String> notSet; names[0] = "t"; values[0][i] = "Delta T"; values[1][i] = "Exposure"; i++; while (j.hasNext()) { info = (PlaneInfo) j.next(); details = EditorUtil.transformPlaneInfo(info); notSet = (List<String>) details.get(EditorUtil.NOT_SET); if (!notSet.contains(EditorUtil.DELTA_T)) { if(details.get(EditorUtil.DELTA_T) instanceof BigResult) { MetadataViewerAgent.logBigResultExeption(this, details.get(EditorUtil.DELTA_T) , EditorUtil.DELTA_T); values[0][i] = "N/A"; } else { double tInS = ((Double)details.get(EditorUtil.DELTA_T)); values[0][i] = getReadableTime(tInS); } } else values[0][i] = "--"; if (!notSet.contains(EditorUtil.EXPOSURE_TIME)) { if(details.get(EditorUtil.EXPOSURE_TIME) instanceof BigResult) { MetadataViewerAgent.logBigResultExeption(this, details.get(EditorUtil.EXPOSURE_TIME) , EditorUtil.EXPOSURE_TIME); values[1][i] = "N/A"; } else { double tInS = ((Double)details.get(EditorUtil.EXPOSURE_TIME)); values[1][i] = getReadableTime(tInS); } } else values[1][i] = "--"; names[i] = "t="+i; i++; } if (i > 1) { JTable table = new JTable(values, names); table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); table.setShowGrid(true); table.setGridColor(Color.LIGHT_GRAY); JScrollPane pane = new JScrollPane(table); Dimension d = table.getPreferredSize(); Dimension de = exposureTask.getPreferredSize(); pane.setPreferredSize(new Dimension(de.width-10, 4*d.height)); exposureTask.add(pane); exposureTask.setVisible(true); } else { exposureTask.setVisible(false); } } /** * Returns <code>true</code> if data to save, <code>false</code> * otherwise. * * @return See above. */ boolean hasDataToSave() { boolean b = parent.hasDataToSave(fieldsGeneral); if (b) return true; b = detectorPane.hasDataToSave(); if (b) return true; b = lightPane.hasDataToSave(); if (b) return true; if (lightPathPane != null) { b = lightPathPane.hasDataToSave(); if (b) return true; } if (filterSetPane != null) { b = filterSetPane.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; String key; DataComponent comp; Object value; EnumerationObject enumObject; Number number; Iterator i; Entry entry; if (channel.isDirty()) { i = fieldsGeneral.entrySet().iterator(); while (i.hasNext()) { entry = (Entry) i.next(); key = (String) entry.getKey(); comp = (DataComponent) entry.getValue(); if (comp.isDirty()) { value = comp.getAreaValue(); if (EditorUtil.NAME.equals(key)) { channel.setName((String) value); } else if (EditorUtil.PIN_HOLE_SIZE.equals(key)) { number = UIUtilities.extractNumber((String) value, Float.class); if (number != null) channel.setPinholeSize(new LengthI((Float) number, UnitsFactory.Channel_PinholeSize)); } else if (EditorUtil.ND_FILTER.equals(key)) { number = UIUtilities.extractNumber((String) value, Float.class); if (number != null) channel.setNDFilter((Float) number); } else if (EditorUtil.POCKEL_CELL.equals(key)) { number = UIUtilities.extractNumber((String) value, Integer.class); if (number != null) channel.setPockelCell((Integer) value); } else if (EditorUtil.EMISSION.equals(key)) { number = UIUtilities.extractNumber((String) value, Double.class); if (number != null) channel.setEmissionWavelength(new LengthI((Double) number, UnitsFactory.Channel_EmissionWavelength)); } else if (EditorUtil.EXCITATION.equals(key)) { number = UIUtilities.extractNumber((String) value, Double.class); if (number != null) channel.setExcitationWavelength(new LengthI((Double) number, UnitsFactory.Channel_ExcitationWavelength)); } else if (EditorUtil.ILLUMINATION.equals(key)) { enumObject = (EnumerationObject) value; if (enumObject.getObject() instanceof Illumination) channel.setIllumination( (Illumination) enumObject.getObject()); } else if (EditorUtil.MODE.equals(key)) { enumObject = (EnumerationObject) value; if (enumObject.getObject() instanceof AcquisitionMode) channel.setMode( (AcquisitionMode) enumObject.getObject()); } else if (EditorUtil.CONTRAST_METHOD.equals(key)) { enumObject = (EnumerationObject) value; if (enumObject.getObject() instanceof ContrastMethod) channel.setContrastMethod( (ContrastMethod) enumObject.getObject()); } } } data.add(channel); } return data; } /** * Reacts to property fired by the <code>JLabelButton</code>. * @see PropertyChangeListener#propertyChange(PropertyChangeEvent) */ public void propertyChange(PropertyChangeEvent evt) { String name = evt.getPropertyName(); if (JLabelButton.SELECTED_PROPERTY.equals(name)) { displayUnsetGeneralFields(); } else if (UIUtilities.COLLAPSED_PROPERTY_JXTASKPANE.equals(name)) { parent.loadPlaneInfo(channel.getIndex()); } } }