/* *------------------------------------------------------------------------------ * Copyright (C) 2006-2014 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.rnd; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSeparator; import org.apache.commons.collections.CollectionUtils; import org.openmicroscopy.shoola.agents.util.ViewedByItem; import org.openmicroscopy.shoola.env.rnd.RndProxyDef; import org.openmicroscopy.shoola.util.ui.UIUtilities; import org.openmicroscopy.shoola.util.ui.WrapLayout; import org.openmicroscopy.shoola.util.ui.slider.TextualTwoKnobsSlider; import org.openmicroscopy.shoola.util.ui.slider.TwoKnobsSlider; import omero.gateway.model.ChannelData; /** * Component hosting the diagram and the controls to select the pixels intensity * interval and the codomain interval. * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author Andrea Falconi      * <a href="mailto:a.falconi@dundee.ac.uk">a.falconi@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 GraphicsPane extends JPanel implements PropertyChangeListener { /** * Factor used to determine the percentage of the range added * (resp. removed) to (resp. from) the maximum (resp. the minimum). */ static final double RATIO = 0.2; /** The title of the viewedby taskpane */ static final String VIEWEDBY_TITLE = "User Settings:"; /** Slider to select a sub-interval of [0, 255]. */ private TwoKnobsSlider codomainSlider; /** Slider to select the pixels intensity interval. */ private TextualTwoKnobsSlider domainSlider; /** The label displaying the global max. */ private JLabel maxLabel; /** The label displaying the global minimum. */ private JLabel minLabel; /** The component displaying the plane histogram. */ private GraphicsPaneUI uiDelegate; /** Reference to the Model.*/ protected RendererModel model; /** Reference to the Control.*/ protected RendererControl controller; /** Flag indicating to paint a vertical line. */ private boolean paintVertical; /** Flag indicating to paint a vertical line. */ private boolean paintHorizontal; /** The equation the horizontal line. */ private int horizontalLine = -1; /** The equation of the vertical line. */ private int verticalLine = -1; /** Checkbox for switching between greyscale and rgb mode */ private JCheckBox greyScale; /** Hosts the sliders controlling the pixels intensity values. */ private List<ChannelSlider> sliders; /** The component displaying the controls. */ private PreviewControlBar controlsBar; /** The lower control pane */ private PreviewControlBar2 controlsBar2; /** The Tasks pane, only visible if already viewed by others. */ private JPanel viewedBy; /** The preview tool bar. */ private PreviewToolBar previewToolBar; /** The items shown in the 'saved by' taskpane */ private List<ViewedByItem> viewedByItems; /** The selected item.*/ private RndProxyDef selectedDef; /** * Formats the specified value. * * @param value The value to format. * @return See above. */ private String formatValue(double value) { if (model.isIntegerPixelData()) return ""+(int) value; else return UIUtilities.formatToDecimal(value); } /** Initializes the domain slider. */ private void initDomainSlider() { int s = (int) model.getWindowStart(); int e = (int) model.getWindowEnd(); int absMin = (int) model.getLowestValue(); int absMax = (int) model.getHighestValue(); int min = (int) model.getGlobalMin(); int max = (int) model.getGlobalMax(); double range = (max-min)*RATIO; int lowestBound = (int) (min-range); if (lowestBound < absMin) lowestBound = absMin; int highestBound = (int) (max+range); if (highestBound > absMax) highestBound = absMax; domainSlider.setValues(max, min, highestBound, lowestBound, max, min, s, e); if (model.getMaxC() > Renderer.MAX_CHANNELS) domainSlider.setInterval(min, max); } /** Initializes the components. */ private void initComponents() { viewedBy = new JPanel(); Font font = viewedBy.getFont(); viewedBy.setFont(font.deriveFont(font.getSize2D()-2)); viewedBy.setBackground(UIUtilities.BACKGROUND_COLOR); viewedBy.setLayout(new BorderLayout()); controlsBar = new PreviewControlBar(controller, model); controlsBar2 = new PreviewControlBar2(controller); uiDelegate = new GraphicsPaneUI(this, model); codomainSlider = new TwoKnobsSlider(RendererModel.CD_START, RendererModel.CD_END, model.getCodomainStart(), model.getCodomainEnd()); codomainSlider.setBackground(UIUtilities.BACKGROUND_COLOR); codomainSlider.setPaintLabels(false); codomainSlider.setPaintEndLabels(false); codomainSlider.setPaintTicks(false); codomainSlider.setColourGradients(Color.BLACK, Color.WHITE); codomainSlider.addPropertyChangeListener(this); domainSlider = new TextualTwoKnobsSlider(); domainSlider.setBackground(UIUtilities.BACKGROUND_COLOR); initDomainSlider(); domainSlider.getSlider().setPaintLabels(false); domainSlider.getSlider().setPaintEndLabels(false); domainSlider.getSlider().setPaintTicks(false); domainSlider.addPropertyChangeListener(this); maxLabel = new JLabel(formatValue(model.getGlobalMax())); minLabel = new JLabel(formatValue(model.getGlobalMin())); maxLabel.setBackground(UIUtilities.BACKGROUND_COLOR); minLabel.setBackground(UIUtilities.BACKGROUND_COLOR); greyScale = new JCheckBox("Grayscale"); greyScale.setSelected(model.isGreyScale()); greyScale.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { model.setGreyscale(greyScale.isSelected()); } }); sliders = new ArrayList<ChannelSlider>(); if (model.getModuloT() != null || !model.isLifetimeImage()) { List<ChannelData> channels = model.getChannelData(); Iterator<ChannelData> i = channels.iterator(); ChannelSlider slider; while (i.hasNext()) { slider = new ChannelSlider(this, model, controller, i.next()); sliders.add(slider); } } previewToolBar = new PreviewToolBar(controller, model); } /** Builds and lays out the GUI. */ private void buildGUI() { setBackground(UIUtilities.BACKGROUND_COLOR); setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.gridy = 0; c.anchor = GridBagConstraints.NORTHWEST; c.weightx = 1; c.weighty = 1; c.fill = GridBagConstraints.BOTH; if (model.isGeneralIndex()) { add(buildGeneralPane(), c); } else { c.weightx = 0; add(buildPane(), c); c.weightx = 1; c.gridx++; add(buildGeneralPane(), c); } } /** * Builds hosting the various sliders * * @return See above. */ private JPanel buildGeneralPane() { JPanel content = new JPanel(); content.setBackground(UIUtilities.BACKGROUND_COLOR); content.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); content.setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.NORTHWEST; c.fill = GridBagConstraints.HORIZONTAL; c.insets = new Insets(1, 2, 1, 2); c.gridy = 0; c.gridx = 0; c.weightx = 1; c.weighty = 0; if (model.isGeneralIndex()) { content.add(previewToolBar, c); c.gridy++; content.add(new JSeparator(), c); c.gridy++; } content.add(controlsBar, c); c.gridy++; content.add(new JSeparator(), c); c.gridy++; content.add(greyScale, c); c.gridy++; Iterator<ChannelSlider> i = sliders.iterator(); while (i.hasNext()) { content.add(i.next(), c); c.gridy++; } c.insets = new Insets(3, 1, 3, 1); content.add(controlsBar2, c); c.gridy++; c.insets = new Insets(0, 0, 0, 0); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1; c.weighty = 0; content.add(new JSeparator(), c); c.gridy++; content.add(new JLabel(VIEWEDBY_TITLE), c); c.gridy++; c.fill = GridBagConstraints.BOTH; c.weighty = 1; content.add(viewedBy, c); return content; } /** * Builds and lays out the slider. * * @return See above. */ private JPanel buildPane() { JPanel p = new JPanel(); p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS)); p.setBackground(UIUtilities.BACKGROUND_COLOR); p.add(codomainSlider); return p; } /** * Creates a new instance. * * @param model Reference to the model. Mustn't be <code>null</code>. * @param controller Reference to the control. Mustn't be <code>null</code>. */ GraphicsPane(RendererModel model, RendererControl controller) { if (model == null) throw new NullPointerException("No model."); if (controller == null) throw new NullPointerException("No controller."); this.model = model; this.controller = controller; initComponents(); buildGUI(); } /** Sets the value of the selected plane.*/ void setSelectedPlane() { if (previewToolBar != null) previewToolBar.setSelectedPlane(); } /** Updates the controls when a new channel is selected. */ void setSelectedChannel() { Iterator<ChannelSlider> i = sliders.iterator(); ChannelSlider slider; while (i.hasNext()) { slider = i.next(); slider.setSelectedChannel(); } } /** Sets the pixels intensity interval. */ void setInputInterval() { double s, e; Iterator<ChannelSlider> i = sliders.iterator(); ChannelSlider slider; while (i.hasNext()) { slider = i.next(); s = model.getWindowStart(slider.getIndex()); e = model.getWindowEnd(slider.getIndex()); slider.setInterval(s, e); } } /** * Modifies the input range of the channel sliders. * * @param absolute Pass <code>true</code> to set it to the absolute value, * <code>false</code> to the minimum and maximum. */ void setInputRange(boolean booleanValue) { if (CollectionUtils.isEmpty(sliders)) return; Iterator<ChannelSlider> i = sliders.iterator(); while (i.hasNext()) i.next().setInputRange(booleanValue); } /** Sets the value of the codomain interval. */ void setCodomainInterval() { codomainSlider.setInterval(model.getCodomainStart(), model.getCodomainEnd()); onCurveChange(); } /** * Updates the UI when a new curve is selected i.e. when a new family * is selected or when a new gamma value is selected. */ void onCurveChange() { uiDelegate.invalidate(); uiDelegate.repaint(); } /** * Returns <code>true</code> if a vertical or horizontal line has * to be painted, <code>false</code> otherwise. * * @return See above. */ boolean isPaintLine() { return paintVertical() || paintHorizontal(); } /** * Returns <code>true</code> if the life update is selected, * <code>false</code> otherwise. * * @return See above. */ boolean isLiveUpdate() { return previewToolBar!=null ? previewToolBar.isLiveUpdate() : false; } /** * Returns <code>true</code> if a vertical line has * to be painted, <code>false</code> otherwise. * * @return See above. */ boolean paintVertical() { return paintVertical; } /** * Returns <code>true</code> if a horizontal line has * to be painted, <code>false</code> otherwise. * * @return See above. */ boolean paintHorizontal() { return paintHorizontal; } /** * Returns the equation of the horizontal line or <code>-1</code> * if no horizontal line defined. * * @return See above. */ int getHorizontalLine() { return horizontalLine; } /** * Returns the equation of the vertical line or <code>-1</code> * if no vertical line defined. * * @return See above. */ int getVerticalLine() { return verticalLine; } /** * Returns the value of the partial minimum. * * @return See above. */ int getPartialMinimum() { return (int)domainSlider.getSlider().getPartialMinimum(); } /** * Returns the value of the partial maximum. * * @return See above. */ int getPartialMaximum() { return (int)domainSlider.getSlider().getPartialMaximum(); } /** * Sets the enabled flag of the UI components. * * @param b The value to set. */ void onStateChange(boolean b) { if (codomainSlider != null) codomainSlider.setEnabled(b); if (domainSlider != null) domainSlider.setEnabled(b); } /** Toggles between color model and Greyscale. */ void setColorModelChanged() { if (CollectionUtils.isEmpty(sliders)) return; Iterator<ChannelSlider> i = sliders.iterator(); while (i.hasNext()) { i.next().setColorModelChanged(); } } /** * Sets the color of the passed channel. * * @param index The index of the channel. */ void setChannelColor(int index) { if (CollectionUtils.isNotEmpty(sliders)) { Iterator<ChannelSlider> i = sliders.iterator(); ChannelSlider slider; while (i.hasNext()) { slider = i.next(); if (slider.getIndex() == index) { slider.setChannelColor(); break; } } } repaint(); } /** * Resets the display of the selected thumbanils under User Settings. * @param activeRndDef The rendering setting which is currently used */ void resetViewedBy(RndProxyDef activeRndDef) { displayViewedBy(viewedByItems, activeRndDef); } /** * Builds and lays out the images as seen by other experimenters. * * @param results The thumbnails to lay out. * @param activeRndDef The rendering setting which is currently used */ void displayViewedBy(List<ViewedByItem> results, RndProxyDef activeRndDef) { if (results == null) { viewedBy.removeAll(); return; } this.viewedByItems = results; Collections.sort(this.viewedByItems, new ViewedByItemComparator()); JPanel p = new JPanel(); p.setLayout(new WrapLayout(WrapLayout.LEFT)); p.setBackground(UIUtilities.BACKGROUND_COLOR); Iterator<ViewedByItem> i = viewedByItems.iterator(); ViewedByItem item; while (i.hasNext()) { item = i.next(); item.addPropertyChangeListener(this); p.add(createViewedByPanel(item)); } if (activeRndDef != null) { highlight(activeRndDef); } p.setSize(viewedBy.getSize()); viewedBy.removeAll(); viewedBy.add(p, BorderLayout.CENTER); viewedBy.validate(); } /** * Wraps the ViewedByItem in a JPanel with empty border acting as inset * @param item The ViewedByItem */ private JPanel createViewedByPanel(ViewedByItem item) { JPanel viewedByPanel = new JPanel(); viewedByPanel.setBackground(UIUtilities.BACKGROUND_COLOR); viewedByPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); viewedByPanel.add(item); return viewedByPanel; } /** * Draws a border around the ViewedByItem which represents * the given RndProxyDef * @param def The RndProxyDef to highlight */ void highlight(RndProxyDef def) { selectedDef = def; for(ViewedByItem item : viewedByItems) { JPanel p = (JPanel) item.getParent(); if (item.getRndDef().getDataID() == def.getDataID()) { p.setBorder(BorderFactory.createLineBorder( UIUtilities.STEELBLUE, 2)); } else { p.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); } } } /** * Returns the selected rendering settings if any. * * @return See above. */ RndProxyDef getSelectedDef() { return selectedDef; } /** * Returns the slider used to set the codomain interval. * * @return See above. */ JComponent getCodomainSlider() { return codomainSlider; } /** * Reacts to property changes fired by the {@link TwoKnobsSlider}s. * @see PropertyChangeListener#propertyChange(PropertyChangeEvent) */ public void propertyChange(PropertyChangeEvent evt) { String name = evt.getPropertyName(); Object source = evt.getSource(); if (!previewToolBar.isLiveUpdate()) { if (TwoKnobsSlider.KNOB_RELEASED_PROPERTY.equals(name)) { paintHorizontal = false; paintVertical = false; if (source.equals(domainSlider)) { controller.setInputInterval(domainSlider.getStartValue(), domainSlider.getEndValue()); onCurveChange(); } else if (source.equals(codomainSlider)) { int s = codomainSlider.getStartValueAsInt(); int e = codomainSlider.getEndValueAsInt(); controller.setCodomainInterval(s, e); onCurveChange(); } } else if (TwoKnobsSlider.LEFT_MOVED_PROPERTY.equals(name)){ if (source.equals(domainSlider)) { verticalLine = (int) (domainSlider.getStartValue()); paintHorizontal = false; paintVertical = true; onCurveChange(); } else if (source.equals(codomainSlider)) { horizontalLine = codomainSlider.getEndValueAsInt(); paintHorizontal = true; paintVertical = false; onCurveChange(); } } else if (TwoKnobsSlider.RIGHT_MOVED_PROPERTY.equals(name)) { if (source.equals(domainSlider)) { verticalLine = (int) (domainSlider.getEndValue()); horizontalLine = -1; paintHorizontal = false; paintVertical = true; onCurveChange(); } else if (source.equals(codomainSlider)) { horizontalLine = codomainSlider.getStartValueAsInt(); verticalLine = -1; paintHorizontal = true; paintVertical = false; onCurveChange(); } } } else { paintHorizontal = false; paintVertical = false; if (TwoKnobsSlider.LEFT_MOVED_PROPERTY.equals(name) || TwoKnobsSlider.RIGHT_MOVED_PROPERTY.equals(name)) { if (source.equals(domainSlider)) { controller.setInputInterval(domainSlider.getStartValue(), domainSlider.getEndValue()); onCurveChange(); } else if (source.equals(codomainSlider)) { int s = codomainSlider.getStartValueAsInt(); int e = codomainSlider.getEndValueAsInt(); controller.setCodomainInterval(s, e); onCurveChange(); } } else if (TwoKnobsSlider.KNOB_RELEASED_PROPERTY.equals(name)) { if (source.equals(domainSlider)) { controller.setInputInterval(domainSlider.getStartValue(), domainSlider.getEndValue()); } else if (source.equals(codomainSlider)) { int s = codomainSlider.getStartValueAsInt(); int e = codomainSlider.getEndValueAsInt(); controller.setCodomainInterval(s, e); onCurveChange(); } } } if(ViewedByItem.VIEWED_BY_PROPERTY.equals(name)) { RndProxyDef def = (RndProxyDef)evt.getNewValue(); highlight(def); } } /** * Checks/Unchecks the greyscale checkbox when the color model has changed * @param b Pass <code>true</code> if color model is greyscale */ void updateGreyScale(boolean b) { greyScale.setSelected(b); } /** * Comparator which sorts the ViewedByItems by its * experimenter's last name */ class ViewedByItemComparator implements Comparator<ViewedByItem> { @Override public int compare(ViewedByItem o1, ViewedByItem o2) { String name1 = o1.getExperimenter().getLastName() != null ? o1.getExperimenter().getLastName() : ""; String name2 = o2.getExperimenter().getLastName() != null ? o2.getExperimenter().getLastName() : ""; return name1.compareToIgnoreCase(name2); } } }