/* * Copyright 2010-2015 Institut Pasteur. * * This file is part of Icy. * * Icy 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 3 of the License, or * (at your option) any later version. * * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>. */ package icy.gui.inspector; import java.awt.Color; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Semaphore; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.JSpinner; import javax.swing.SpinnerNumberModel; import javax.swing.SwingConstants; import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import icy.action.RoiActions; import icy.clipboard.Clipboard; import icy.clipboard.Clipboard.ClipboardListener; import icy.gui.component.AbstractRoisPanel; import icy.gui.component.IcyTextField; import icy.gui.component.IcyTextField.TextChangeListener; import icy.gui.component.SpecialValueSpinner; import icy.gui.component.button.ColorChooserButton; import icy.gui.component.button.ColorChooserButton.ColorChangeListener; import icy.gui.component.button.IcyButton; import icy.gui.component.model.SpecialValueSpinnerModel; import icy.main.Icy; import icy.math.MathUtil; import icy.roi.ROI; import icy.roi.ROIEvent; import icy.roi.edit.BoundsROIEdit; import icy.roi.edit.BoundsROIsEdit; import icy.roi.edit.PositionROIEdit; import icy.roi.edit.PositionROIsEdit; import icy.roi.edit.PropertyROIsEdit; import icy.sequence.Sequence; import icy.system.thread.ThreadUtil; import icy.type.point.Point5D; import icy.type.rectangle.Rectangle5D; import icy.util.StringUtil; import javax.swing.UIManager; /** * @author Stephane */ public class RoiControlPanel extends JPanel implements ColorChangeListener, TextChangeListener, ClipboardListener, ChangeListener, ActionListener { /** * */ private static final long serialVersionUID = 7403770406075917063L; // GUI JLabel posCFieldLabel; JLabel posTFieldLabel; JLabel posZFieldLabel; JLabel posZSpinnerLabel; JLabel posTSpinnerLabel; JLabel posCSpinnerLabel; IcyTextField posXField; IcyTextField posYField; IcyTextField posTField; IcyTextField posZField; IcyTextField sizeXField; IcyTextField sizeZField; IcyTextField sizeYField; IcyTextField sizeTField; IcyTextField posCField; IcyTextField sizeCField; SpecialValueSpinner posZSpinner; SpecialValueSpinner posTSpinner; SpecialValueSpinner posCSpinner; ColorChooserButton colorButton; JSlider alphaSlider; JSpinner strokeSpinner; JCheckBox displayNameCheckBox; JButton setAsDefaultBtn; // internals private final AbstractRoisPanel roisPanel; final Semaphore modifyingRoi; private List<ROI> modifiedRois; final Runnable roiActionsRefresher; final Runnable roiPropertiesRefresher; public RoiControlPanel(AbstractRoisPanel roisPanel) { super(); this.roisPanel = roisPanel; modifyingRoi = new Semaphore(1); modifiedRois = null; roiActionsRefresher = new Runnable() { @Override public void run() { refreshROIActionsInternal(); } }; roiPropertiesRefresher = new Runnable() { @Override public void run() { refreshROIPropertiesInternal(); } }; initialize(); colorButton.addColorChangeListener(this); strokeSpinner.addChangeListener(this); alphaSlider.addChangeListener(this); displayNameCheckBox.addActionListener(this); posXField.addTextChangeListener(this); posYField.addTextChangeListener(this); posZField.addTextChangeListener(this); posZSpinner.addChangeListener(this); posTField.addTextChangeListener(this); posTSpinner.addChangeListener(this); posCField.addTextChangeListener(this); posCSpinner.addChangeListener(this); sizeXField.addTextChangeListener(this); sizeYField.addTextChangeListener(this); sizeZField.addTextChangeListener(this); sizeTField.addTextChangeListener(this); sizeCField.addTextChangeListener(this); setAsDefaultBtn.addActionListener(this); Clipboard.addListener(this); refreshROIActionsInternal(); refreshROIPropertiesInternal(); } private void initialize() { setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); JPanel actionPanel = new JPanel(); actionPanel.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Properties", TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0))); add(actionPanel); GridBagLayout gbl_actionPanel = new GridBagLayout(); gbl_actionPanel.columnWidths = new int[] {0, 0, 0, 60, 0, 0}; gbl_actionPanel.rowHeights = new int[] {0, 0, 0}; gbl_actionPanel.columnWeights = new double[] {0.0, 1.0, 0.0, 1.0, 0.0, Double.MIN_VALUE}; gbl_actionPanel.rowWeights = new double[] {0.0, 0.0, Double.MIN_VALUE}; actionPanel.setLayout(gbl_actionPanel); final JLabel lblColor = new JLabel("Color"); GridBagConstraints gbc_lblColor = new GridBagConstraints(); gbc_lblColor.anchor = GridBagConstraints.WEST; gbc_lblColor.insets = new Insets(0, 0, 5, 5); gbc_lblColor.gridx = 0; gbc_lblColor.gridy = 0; actionPanel.add(lblColor, gbc_lblColor); colorButton = new ColorChooserButton(); GridBagConstraints gbc_colorButton = new GridBagConstraints(); gbc_colorButton.fill = GridBagConstraints.HORIZONTAL; gbc_colorButton.insets = new Insets(0, 0, 5, 5); gbc_colorButton.gridx = 1; gbc_colorButton.gridy = 0; actionPanel.add(colorButton, gbc_colorButton); colorButton.setToolTipText("ROI color"); JLabel lblContentOpacity = new JLabel("Opacity"); GridBagConstraints gbc_lblContentOpacity = new GridBagConstraints(); gbc_lblContentOpacity.anchor = GridBagConstraints.WEST; gbc_lblContentOpacity.insets = new Insets(0, 0, 5, 5); gbc_lblContentOpacity.gridx = 2; gbc_lblContentOpacity.gridy = 0; actionPanel.add(lblContentOpacity, gbc_lblContentOpacity); alphaSlider = new JSlider(); alphaSlider.setFocusable(false); GridBagConstraints gbc_alphaSlider = new GridBagConstraints(); gbc_alphaSlider.gridwidth = 2; gbc_alphaSlider.fill = GridBagConstraints.HORIZONTAL; gbc_alphaSlider.insets = new Insets(0, 0, 5, 0); gbc_alphaSlider.gridx = 3; gbc_alphaSlider.gridy = 0; actionPanel.add(alphaSlider, gbc_alphaSlider); alphaSlider.setPreferredSize(new Dimension(80, 20)); alphaSlider.setMaximumSize(new Dimension(32767, 20)); alphaSlider.setMinimumSize(new Dimension(36, 20)); alphaSlider.setToolTipText("ROI content opacity"); JLabel lblNewLabel = new JLabel("Stroke"); GridBagConstraints gbc_lblNewLabel = new GridBagConstraints(); gbc_lblNewLabel.anchor = GridBagConstraints.WEST; gbc_lblNewLabel.insets = new Insets(0, 0, 0, 5); gbc_lblNewLabel.gridx = 0; gbc_lblNewLabel.gridy = 1; actionPanel.add(lblNewLabel, gbc_lblNewLabel); strokeSpinner = new JSpinner(); strokeSpinner.setToolTipText("ROI stroke size (for visualization only)"); strokeSpinner.setModel(new SpinnerNumberModel(1.0, 1.0, 9.0, 1.0)); GridBagConstraints gbc_strokeSpinner = new GridBagConstraints(); gbc_strokeSpinner.fill = GridBagConstraints.HORIZONTAL; gbc_strokeSpinner.insets = new Insets(0, 0, 0, 5); gbc_strokeSpinner.gridx = 1; gbc_strokeSpinner.gridy = 1; actionPanel.add(strokeSpinner, gbc_strokeSpinner); displayNameCheckBox = new JCheckBox("Show name"); displayNameCheckBox.setToolTipText("Show the ROI name"); displayNameCheckBox.setMargin(new Insets(2, 0, 2, 2)); displayNameCheckBox.setIconTextGap(10); displayNameCheckBox.setHorizontalTextPosition(SwingConstants.LEADING); GridBagConstraints gbc_displayNameCheckBox = new GridBagConstraints(); gbc_displayNameCheckBox.anchor = GridBagConstraints.WEST; gbc_displayNameCheckBox.insets = new Insets(0, 0, 0, 5); gbc_displayNameCheckBox.gridwidth = 2; gbc_displayNameCheckBox.gridx = 2; gbc_displayNameCheckBox.gridy = 1; actionPanel.add(displayNameCheckBox, gbc_displayNameCheckBox); setAsDefaultBtn = new JButton("As default"); setAsDefaultBtn.setEnabled(false); setAsDefaultBtn.setMargin(new Insets(2, 2, 2, 2)); setAsDefaultBtn.setIconTextGap(0); setAsDefaultBtn .setToolTipText("Set the current color, opacity, stroke and show name values as the default settings"); GridBagConstraints gbc_setAsDefaultBtn = new GridBagConstraints(); gbc_setAsDefaultBtn.anchor = GridBagConstraints.EAST; gbc_setAsDefaultBtn.gridx = 4; gbc_setAsDefaultBtn.gridy = 1; actionPanel.add(setAsDefaultBtn, gbc_setAsDefaultBtn); JPanel positionAndSizePanel = new JPanel(); add(positionAndSizePanel); GridBagLayout gbl_positionAndSizePanel = new GridBagLayout(); gbl_positionAndSizePanel.columnWidths = new int[] {0, 0, 0}; gbl_positionAndSizePanel.rowHeights = new int[] {0, 0}; gbl_positionAndSizePanel.columnWeights = new double[] {1.0, 1.0, Double.MIN_VALUE}; gbl_positionAndSizePanel.rowWeights = new double[] {0.0, Double.MIN_VALUE}; positionAndSizePanel.setLayout(gbl_positionAndSizePanel); JPanel positionPanel = new JPanel(); positionPanel.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Position", TitledBorder.LEFT, TitledBorder.TOP, null, new Color(0, 0, 0))); GridBagConstraints gbc_positionPanel = new GridBagConstraints(); gbc_positionPanel.anchor = GridBagConstraints.NORTH; gbc_positionPanel.insets = new Insets(0, 0, 0, 5); gbc_positionPanel.fill = GridBagConstraints.HORIZONTAL; gbc_positionPanel.gridx = 0; gbc_positionPanel.gridy = 0; positionAndSizePanel.add(positionPanel, gbc_positionPanel); GridBagLayout gbl_positionPanel = new GridBagLayout(); gbl_positionPanel.columnWidths = new int[] {20, 0, 0}; gbl_positionPanel.rowHeights = new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0}; gbl_positionPanel.columnWeights = new double[] {0.0, 1.0, Double.MIN_VALUE}; gbl_positionPanel.rowWeights = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; positionPanel.setLayout(gbl_positionPanel); final JLabel lblX = new JLabel("X"); GridBagConstraints gbc_lblX = new GridBagConstraints(); gbc_lblX.insets = new Insets(0, 0, 5, 5); gbc_lblX.gridx = 0; gbc_lblX.gridy = 0; positionPanel.add(lblX, gbc_lblX); posXField = new IcyTextField(); GridBagConstraints gbc_posXField = new GridBagConstraints(); gbc_posXField.insets = new Insets(0, 0, 5, 0); gbc_posXField.fill = GridBagConstraints.HORIZONTAL; gbc_posXField.gridx = 1; gbc_posXField.gridy = 0; positionPanel.add(posXField, gbc_posXField); posXField.setToolTipText("X position of the ROI"); posXField.setColumns(8); final JLabel lblY = new JLabel("Y"); GridBagConstraints gbc_lblY = new GridBagConstraints(); gbc_lblY.insets = new Insets(0, 0, 5, 5); gbc_lblY.gridx = 0; gbc_lblY.gridy = 1; positionPanel.add(lblY, gbc_lblY); posYField = new IcyTextField(); GridBagConstraints gbc_posYField = new GridBagConstraints(); gbc_posYField.fill = GridBagConstraints.HORIZONTAL; gbc_posYField.insets = new Insets(0, 0, 5, 0); gbc_posYField.gridx = 1; gbc_posYField.gridy = 1; positionPanel.add(posYField, gbc_posYField); posYField.setToolTipText("Y position of the ROI"); posYField.setColumns(8); posZFieldLabel = new JLabel("Z"); GridBagConstraints gbc_posZFieldLabel = new GridBagConstraints(); gbc_posZFieldLabel.insets = new Insets(0, 0, 5, 5); gbc_posZFieldLabel.gridx = 0; gbc_posZFieldLabel.gridy = 2; positionPanel.add(posZFieldLabel, gbc_posZFieldLabel); posZField = new IcyTextField(); GridBagConstraints gbc_posZField = new GridBagConstraints(); gbc_posZField.fill = GridBagConstraints.HORIZONTAL; gbc_posZField.insets = new Insets(0, 0, 5, 0); gbc_posZField.gridx = 1; gbc_posZField.gridy = 2; positionPanel.add(posZField, gbc_posZField); posZField.setVisible(false); posZField.setToolTipText("Z position of the ROI"); posZField.setColumns(8); posZSpinnerLabel = new JLabel("Z"); GridBagConstraints gbc_posZSpinnerLabel = new GridBagConstraints(); gbc_posZSpinnerLabel.insets = new Insets(0, 0, 5, 5); gbc_posZSpinnerLabel.gridx = 0; gbc_posZSpinnerLabel.gridy = 3; positionPanel.add(posZSpinnerLabel, gbc_posZSpinnerLabel); posZSpinner = new SpecialValueSpinner(new SpecialValueSpinnerModel(-1, -1, 0, 1, -1, "ALL")); GridBagConstraints gbc_posZSpinner = new GridBagConstraints(); gbc_posZSpinner.fill = GridBagConstraints.HORIZONTAL; gbc_posZSpinner.insets = new Insets(0, 0, 5, 0); gbc_posZSpinner.gridx = 1; gbc_posZSpinner.gridy = 3; positionPanel.add(posZSpinner, gbc_posZSpinner); posZSpinner.setToolTipText("Attach the ROI to a specific Z slice (set to -1 for ALL)"); posTFieldLabel = new JLabel("T"); GridBagConstraints gbc_posTFieldLabel = new GridBagConstraints(); gbc_posTFieldLabel.insets = new Insets(0, 0, 5, 5); gbc_posTFieldLabel.gridx = 0; gbc_posTFieldLabel.gridy = 4; positionPanel.add(posTFieldLabel, gbc_posTFieldLabel); posTField = new IcyTextField(); GridBagConstraints gbc_posTField = new GridBagConstraints(); gbc_posTField.fill = GridBagConstraints.HORIZONTAL; gbc_posTField.insets = new Insets(0, 0, 5, 0); gbc_posTField.gridx = 1; gbc_posTField.gridy = 4; positionPanel.add(posTField, gbc_posTField); posTField.setVisible(false); posTField.setToolTipText("T position of the ROI"); posTField.setColumns(8); posTSpinnerLabel = new JLabel("T"); GridBagConstraints gbc_posTSpinnerLabel = new GridBagConstraints(); gbc_posTSpinnerLabel.insets = new Insets(0, 0, 5, 5); gbc_posTSpinnerLabel.gridx = 0; gbc_posTSpinnerLabel.gridy = 5; positionPanel.add(posTSpinnerLabel, gbc_posTSpinnerLabel); posTSpinner = new SpecialValueSpinner(new SpecialValueSpinnerModel(-1, -1, 0, 1, -1, "ALL")); GridBagConstraints gbc_posTSpinner = new GridBagConstraints(); gbc_posTSpinner.fill = GridBagConstraints.HORIZONTAL; gbc_posTSpinner.insets = new Insets(0, 0, 5, 0); gbc_posTSpinner.gridx = 1; gbc_posTSpinner.gridy = 5; positionPanel.add(posTSpinner, gbc_posTSpinner); posTSpinner.setToolTipText("Attach the ROI to a specific T frame (set to -1 for ALL)"); posCFieldLabel = new JLabel("C"); GridBagConstraints gbc_posCFieldLabel = new GridBagConstraints(); gbc_posCFieldLabel.insets = new Insets(0, 0, 5, 5); gbc_posCFieldLabel.gridx = 0; gbc_posCFieldLabel.gridy = 6; positionPanel.add(posCFieldLabel, gbc_posCFieldLabel); posCField = new IcyTextField(); GridBagConstraints gbc_posCField = new GridBagConstraints(); gbc_posCField.fill = GridBagConstraints.HORIZONTAL; gbc_posCField.insets = new Insets(0, 0, 5, 0); gbc_posCField.gridx = 1; gbc_posCField.gridy = 6; positionPanel.add(posCField, gbc_posCField); posCField.setVisible(false); posCField.setToolTipText("C position of the ROI"); posCField.setColumns(8); posCSpinnerLabel = new JLabel("C"); GridBagConstraints gbc_posCSpinnerLabel = new GridBagConstraints(); gbc_posCSpinnerLabel.insets = new Insets(0, 0, 0, 5); gbc_posCSpinnerLabel.gridx = 0; gbc_posCSpinnerLabel.gridy = 7; positionPanel.add(posCSpinnerLabel, gbc_posCSpinnerLabel); posCSpinner = new SpecialValueSpinner(new SpecialValueSpinnerModel(-1, -1, 0, 1, -1, "ALL")); GridBagConstraints gbc_posCSpinner = new GridBagConstraints(); gbc_posCSpinner.fill = GridBagConstraints.HORIZONTAL; gbc_posCSpinner.gridx = 1; gbc_posCSpinner.gridy = 7; positionPanel.add(posCSpinner, gbc_posCSpinner); posCSpinner.setToolTipText("Attach the ROI to a specific C channel (set to -1 for ALL)"); JPanel sizePanel = new JPanel(); sizePanel.setBorder(new TitledBorder(null, "Dimension", TitledBorder.LEADING, TitledBorder.TOP, null, null)); GridBagConstraints gbc_sizePanel = new GridBagConstraints(); gbc_sizePanel.anchor = GridBagConstraints.NORTH; gbc_sizePanel.fill = GridBagConstraints.HORIZONTAL; gbc_sizePanel.gridx = 1; gbc_sizePanel.gridy = 0; positionAndSizePanel.add(sizePanel, gbc_sizePanel); GridBagLayout gbl_sizePanel = new GridBagLayout(); gbl_sizePanel.columnWidths = new int[] {20, 0, 0}; gbl_sizePanel.rowHeights = new int[] {0, 0, 0, 0, 0, 0}; gbl_sizePanel.columnWeights = new double[] {0.0, 1.0, Double.MIN_VALUE}; gbl_sizePanel.rowWeights = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; sizePanel.setLayout(gbl_sizePanel); final JLabel lblNewLabel_2 = new JLabel("X"); GridBagConstraints gbc_lblNewLabel_2 = new GridBagConstraints(); gbc_lblNewLabel_2.insets = new Insets(0, 0, 5, 5); gbc_lblNewLabel_2.gridx = 0; gbc_lblNewLabel_2.gridy = 0; sizePanel.add(lblNewLabel_2, gbc_lblNewLabel_2); sizeXField = new IcyTextField(); GridBagConstraints gbc_sizeXField = new GridBagConstraints(); gbc_sizeXField.insets = new Insets(0, 0, 5, 0); gbc_sizeXField.fill = GridBagConstraints.HORIZONTAL; gbc_sizeXField.gridx = 1; gbc_sizeXField.gridy = 0; sizePanel.add(sizeXField, gbc_sizeXField); sizeXField.setToolTipText("Size of dimension X for the ROI"); sizeXField.setColumns(8); final JLabel lblY_1 = new JLabel("Y"); GridBagConstraints gbc_lblY_1 = new GridBagConstraints(); gbc_lblY_1.insets = new Insets(0, 0, 5, 5); gbc_lblY_1.gridx = 0; gbc_lblY_1.gridy = 1; sizePanel.add(lblY_1, gbc_lblY_1); sizeYField = new IcyTextField(); GridBagConstraints gbc_sizeYField = new GridBagConstraints(); gbc_sizeYField.fill = GridBagConstraints.HORIZONTAL; gbc_sizeYField.insets = new Insets(0, 0, 5, 0); gbc_sizeYField.gridx = 1; gbc_sizeYField.gridy = 1; sizePanel.add(sizeYField, gbc_sizeYField); sizeYField.setToolTipText("Size of dimension Y for the ROI"); sizeYField.setColumns(8); JLabel sizeZFieldLabel = new JLabel("Z"); GridBagConstraints gbc_sizeZFieldLabel = new GridBagConstraints(); gbc_sizeZFieldLabel.insets = new Insets(0, 0, 5, 5); gbc_sizeZFieldLabel.gridx = 0; gbc_sizeZFieldLabel.gridy = 2; sizePanel.add(sizeZFieldLabel, gbc_sizeZFieldLabel); sizeZField = new IcyTextField(); GridBagConstraints gbc_sizeZField = new GridBagConstraints(); gbc_sizeZField.fill = GridBagConstraints.HORIZONTAL; gbc_sizeZField.insets = new Insets(0, 0, 5, 0); gbc_sizeZField.gridx = 1; gbc_sizeZField.gridy = 2; sizePanel.add(sizeZField, gbc_sizeZField); sizeZField.setToolTipText("Size of dimension Z for the ROI"); sizeZField.setColumns(8); JLabel sizeTFieldLabel = new JLabel("T"); GridBagConstraints gbc_sizeTFieldLabel = new GridBagConstraints(); gbc_sizeTFieldLabel.insets = new Insets(0, 0, 5, 5); gbc_sizeTFieldLabel.gridx = 0; gbc_sizeTFieldLabel.gridy = 3; sizePanel.add(sizeTFieldLabel, gbc_sizeTFieldLabel); sizeTField = new IcyTextField(); GridBagConstraints gbc_sizeTField = new GridBagConstraints(); gbc_sizeTField.fill = GridBagConstraints.HORIZONTAL; gbc_sizeTField.insets = new Insets(0, 0, 5, 0); gbc_sizeTField.gridx = 1; gbc_sizeTField.gridy = 3; sizePanel.add(sizeTField, gbc_sizeTField); sizeTField.setToolTipText("Size of dimension T for the ROI"); sizeTField.setColumns(8); JLabel sizeCFieldLabel = new JLabel("C"); GridBagConstraints gbc_sizeCFieldLabel = new GridBagConstraints(); gbc_sizeCFieldLabel.insets = new Insets(0, 0, 0, 5); gbc_sizeCFieldLabel.gridx = 0; gbc_sizeCFieldLabel.gridy = 4; sizePanel.add(sizeCFieldLabel, gbc_sizeCFieldLabel); sizeCField = new IcyTextField(); GridBagConstraints gbc_sizeCField = new GridBagConstraints(); gbc_sizeCField.fill = GridBagConstraints.HORIZONTAL; gbc_sizeCField.gridx = 1; gbc_sizeCField.gridy = 4; sizePanel.add(sizeCField, gbc_sizeCField); sizeCField.setToolTipText("Size of dimension C for the ROI"); sizeCField.setColumns(8); } public void selectionChanged() { refreshROIActionsAndProperties(); } /** * Get the visible ROI in the ROI control panel */ List<ROI> getVisibleRois() { return roisPanel.getVisibleRois(); } /** * Get all selected ROIs */ List<ROI> getSelectedRois() { return getSelectedRois(true); } /** * Get the selected ROI in the ROI control panel. * * @param wantReadOnly * If <code>true</code> the returned list will also contains ROI in Read-Only state */ List<ROI> getSelectedRois(boolean wantReadOnly) { final List<ROI> selected = roisPanel.getSelectedRois(); if (wantReadOnly) return selected; final List<ROI> result = new ArrayList<ROI>(selected.size()); for (ROI roi : selected) if (!roi.isReadOnly()) result.add(roi); return result; } static double formatPosition(double pos, double size) { // special case of infinite dimension if (size == Double.POSITIVE_INFINITY) return -1d; return MathUtil.roundSignificant(pos, 5, true); } static String getPositionAsString(double pos, double size) { // special case of infinite dimension if (size == Double.POSITIVE_INFINITY) return "all"; return StringUtil.toString(MathUtil.roundSignificant(pos, 5, true)); } static double formatSize(double value) { return MathUtil.roundSignificant(value, 5, true); } static String getSizeAsString(double value) { // special case of infinite dimension if (value == Double.POSITIVE_INFINITY) return MathUtil.INFINITE_STRING; return StringUtil.toString(formatSize(value)); } /** * Refresh the ROI actions state. */ public void refreshROIActions() { ThreadUtil.runSingle(roiActionsRefresher); } /** * Refresh the ROI actions state (internal) */ void refreshROIActionsInternal() { final Sequence sequence = Icy.getMainInterface().getActiveSequence(); final List<ROI> selectedRois = getSelectedRois(); final ROI roi = (selectedRois.size() > 0) ? selectedRois.get(0) : null; boolean canSetPos = false; boolean canSetBnd = false; boolean readOnly = true; // set read only flag for (ROI r : selectedRois) { readOnly &= r.isReadOnly(); canSetPos |= r.canSetPosition(); canSetBnd |= r.canSetBounds(); } final boolean hasSequence = (sequence != null); final boolean hasSelected = (roi != null); final boolean canSetPosition = canSetPos; final boolean canSetBounds = canSetBnd; final boolean editable = !readOnly; final int dim = (roi != null) ? roi.getDimension() : 0; // avoid retaining ROIS when sequence is closed if (!hasSequence) modifiedRois = null; // wait a bit to avoid eating too much time with refresh ThreadUtil.sleep(1); ThreadUtil.invokeNow(new Runnable() { @Override public void run() { // modifyingRoi.acquireUninterruptibly(); if (modifyingRoi.tryAcquire()) { try { if (sequence != null) { ((SpecialValueSpinnerModel) posZSpinner.getModel()) .setMaximum(Integer.valueOf(sequence.getSizeZ() - 1)); ((SpecialValueSpinnerModel) posTSpinner.getModel()) .setMaximum(Integer.valueOf(sequence.getSizeT() - 1)); ((SpecialValueSpinnerModel) posCSpinner.getModel()) .setMaximum(Integer.valueOf(sequence.getSizeC() - 1)); } else { ((SpecialValueSpinnerModel) posZSpinner.getModel()).setMaximum(Integer.valueOf(0)); ((SpecialValueSpinnerModel) posTSpinner.getModel()).setMaximum(Integer.valueOf(0)); ((SpecialValueSpinnerModel) posCSpinner.getModel()).setMaximum(Integer.valueOf(0)); } } finally { modifyingRoi.release(); } } posXField.setEnabled(canSetPosition && editable); posYField.setEnabled(canSetPosition && editable); posZField.setEnabled(canSetPosition && editable); posTField.setEnabled(canSetPosition && editable); posCField.setEnabled(canSetPosition && editable); posZSpinner.setEnabled(canSetPosition && editable); posTSpinner.setEnabled(canSetPosition && editable); posCSpinner.setEnabled(canSetPosition && editable); sizeXField.setEnabled(canSetBounds && editable); sizeYField.setEnabled(canSetBounds && editable && (dim > 1)); sizeZField.setEnabled(canSetBounds && editable && (dim > 2)); sizeTField.setEnabled(canSetBounds && editable && (dim > 3)); sizeCField.setEnabled(canSetBounds && editable && (dim > 4)); if (dim > 2) { posZField.setVisible(true); posZFieldLabel.setVisible(true); posZSpinner.setVisible(false); posZSpinnerLabel.setVisible(false); } else { posZField.setVisible(false); posZFieldLabel.setVisible(false); posZSpinner.setVisible(true); posZSpinnerLabel.setVisible(true); } if (dim > 3) { posTField.setVisible(true); posTFieldLabel.setVisible(true); posTSpinner.setVisible(false); posTSpinnerLabel.setVisible(false); } else { posTField.setVisible(false); posTFieldLabel.setVisible(false); posTSpinner.setVisible(true); posTSpinnerLabel.setVisible(true); } if (dim > 4) { posCField.setVisible(true); posCFieldLabel.setVisible(true); posCSpinner.setVisible(false); posCSpinnerLabel.setVisible(false); } else { posCField.setVisible(false); posCFieldLabel.setVisible(false); posCSpinner.setVisible(true); posCSpinnerLabel.setVisible(true); } colorButton.setEnabled(hasSelected && editable); strokeSpinner.setEnabled(hasSelected && editable); alphaSlider.setEnabled(hasSelected); displayNameCheckBox.setEnabled(hasSelected); setAsDefaultBtn.setEnabled(hasSelected); } }); } /** * Refresh ROI properties */ public void refreshROIProperties() { ThreadUtil.runSingle(roiPropertiesRefresher); } /** * Refresh ROI properties (internal) */ void refreshROIPropertiesInternal() { final List<ROI> rois = getSelectedRois(); final ROI roi = (rois.size() > 0) ? rois.get(0) : null; final Rectangle5D bounds = (roi != null) ? roi.getBounds5D() : null; // wait a bit to avoid eating too much time with refresh ThreadUtil.sleep(1); ThreadUtil.invokeNow(new Runnable() { @Override public void run() { // modifyingRoi.acquireUninterruptibly(); if (!modifyingRoi.tryAcquire()) return; try { if (roi != null) { colorButton.setColor(roi.getColor()); strokeSpinner.setValue(Double.valueOf(roi.getStroke())); alphaSlider.setValue((int) (roi.getOpacity() * 100)); displayNameCheckBox.setSelected(roi.getShowName()); } else { // no ROI selected colorButton.setColor(Color.gray); strokeSpinner.setValue(Double.valueOf(1d)); alphaSlider.setValue(0); displayNameCheckBox.setSelected(false); } if (bounds != null) { posXField.setText(getPositionAsString(bounds.getX(), bounds.getSizeX())); posYField.setText(getPositionAsString(bounds.getY(), bounds.getSizeY())); posZSpinner.setValue(Integer.valueOf((int) formatPosition(bounds.getZ(), bounds.getSizeZ()))); posZField.setText(getPositionAsString(bounds.getZ(), bounds.getSizeZ())); posTSpinner.setValue(Integer.valueOf((int) formatPosition(bounds.getT(), bounds.getSizeT()))); posTField.setText(getPositionAsString(bounds.getT(), bounds.getSizeT())); posCSpinner.setValue(Integer.valueOf((int) formatPosition(bounds.getC(), bounds.getSizeC()))); posCField.setText(getPositionAsString(bounds.getC(), bounds.getSizeC())); sizeXField.setText(getSizeAsString(bounds.getSizeX())); sizeYField.setText(getSizeAsString(bounds.getSizeY())); sizeZField.setText(getSizeAsString(bounds.getSizeZ())); sizeTField.setText(getSizeAsString(bounds.getSizeT())); sizeCField.setText(getSizeAsString(bounds.getSizeC())); } else { posXField.setText(""); posYField.setText(""); posZField.setText(""); posTField.setText(""); posCField.setText(""); posZSpinner.setValue(Integer.valueOf(0)); posTSpinner.setValue(Integer.valueOf(0)); posCSpinner.setValue(Integer.valueOf(0)); sizeXField.setText(""); sizeYField.setText(""); sizeZField.setText(""); sizeTField.setText(""); sizeCField.setText(""); } } finally { modifyingRoi.release(); } } }); } /** * Refresh ROI actions and properties */ public void refreshROIActionsAndProperties() { ThreadUtil.runSingle(roiActionsRefresher); ThreadUtil.runSingle(roiPropertiesRefresher); } @Override public void textChanged(IcyTextField source, boolean validate) { // source not anymore enable --> cancel validation if (!source.isEnabled()) return; // keep trace of modified ROI and wait for validation if (!validate) { modifiedRois = getSelectedRois(); return; } // at this point the text is validated... final List<ROI> rois = modifiedRois; // nothing to edit ? if ((rois == null) || rois.isEmpty()) return; // can get semaphore ? if (!modifyingRoi.tryAcquire()) return; try { // position fields ? if ((source == posXField) || (source == posYField) || (source == posZField) || (source == posTField) || (source == posCField)) { final double value = StringUtil.parseDouble(source.getText(), Double.NaN); // correct value ? if (!Double.isNaN(value)) { final List<ROI> roiToModify = new ArrayList<ROI>(); // get the ROI we were modifying for (ROI roi : rois) { // can't edit read only ROI (should not arrive) if (roi.isReadOnly()) continue; // roi support position change ? if (roi.canSetPosition()) roiToModify.add(roi); } // we have effectively some ROI to modify ? if (!roiToModify.isEmpty()) { Point5D positionSet = null; // single change ? if (roiToModify.size() == 1) { final ROI roi = roiToModify.get(0); // get current ROI position final Point5D savePosition = roi.getPosition5D(); final Point5D position = (Point5D) savePosition.clone(); if (source == posXField) position.setX(value); else if (source == posYField) position.setY(value); else if (source == posZField) position.setZ(value); else if (source == posTField) position.setT(value); else position.setC(value); // set new position roi.setPosition5D(position); // keep trace of accepted ROI position positionSet = roi.getPosition5D(); // add position change to undo manager Icy.getMainInterface().getUndoManager() .addEdit(new PositionROIEdit(roi, savePosition, false)); } else { final List<Point5D> savePositions = new ArrayList<Point5D>(); final List<Point5D> newPositions = new ArrayList<Point5D>(); // save previous positions for (ROI roi : roiToModify) savePositions.add(roi.getPosition5D()); for (ROI roi : roiToModify) { // get current ROI position final Point5D position = roi.getPosition5D(); if (source == posXField) position.setX(value); else if (source == posYField) position.setY(value); else if (source == posZField) position.setZ(value); else if (source == posTField) position.setT(value); else position.setC(value); // set new position roi.setPosition5D(position); // keep trace of first accepted ROI position if (positionSet == null) positionSet = roi.getPosition5D(); // save new position newPositions.add(position); } // add position change to undo manager Icy.getMainInterface().getUndoManager() .addEdit(new PositionROIsEdit(roiToModify, savePositions, newPositions, false)); } if (positionSet != null) { double p; // fix field value if needed if (source == posXField) p = positionSet.getX(); else if (source == posYField) p = positionSet.getY(); else if (source == posZField) p = positionSet.getZ(); else if (source == posTField) p = positionSet.getT(); else p = positionSet.getC(); // change infinite by -1 if (p == Double.NEGATIVE_INFINITY) p = -1d; source.setText(Double.toString(p)); } } } } // size fields ? else if ((source == sizeXField) || (source == sizeYField) || (source == sizeZField) || (source == sizeTField) || (source == sizeCField)) { final double value = StringUtil.parseDouble(source.getText(), Double.NaN); // correct value ? if (!Double.isNaN(value)) { final List<ROI> roiToModify = new ArrayList<ROI>(); // get the ROI we were modifying for (ROI roi : rois) { // can't edit read only ROI (should not arrive) if (roi.isReadOnly()) continue; // roi support position change ? if (roi.canSetPosition()) roiToModify.add(roi); } // we have effectively some ROI to modify ? if (!roiToModify.isEmpty()) { Rectangle5D boundsSet = null; if (roiToModify.size() == 1) { final ROI roi = roiToModify.get(0); // get current ROI size final Rectangle5D saveBounds = roi.getBounds5D(); final Rectangle5D bounds = (Rectangle5D) saveBounds.clone(); if (source == sizeXField) bounds.setSizeX(value); else if (source == sizeYField) bounds.setSizeY(value); else if (source == sizeZField) bounds.setSizeZ(value); else if (source == sizeTField) bounds.setSizeT(value); else bounds.setSizeC(value); roi.setBounds5D(bounds); // keep trace of first accepted ROI bounds boundsSet = roi.getBounds5D(); // add position change to undo manager Icy.getMainInterface().getUndoManager().addEdit(new BoundsROIEdit(roi, saveBounds, false)); } else { final List<Rectangle5D> saveBoundsList = new ArrayList<Rectangle5D>(); final List<Rectangle5D> newBoundsList = new ArrayList<Rectangle5D>(); // save previous size for (ROI roi : roiToModify) saveBoundsList.add(roi.getBounds5D()); for (ROI roi : roiToModify) { // get current ROI size Rectangle5D bounds = roi.getBounds5D(); if (source == sizeXField) bounds.setSizeX(value); else if (source == sizeYField) bounds.setSizeY(value); else if (source == sizeZField) bounds.setSizeZ(value); else if (source == sizeTField) bounds.setSizeT(value); else bounds.setSizeC(value); roi.setBounds5D(bounds); // keep trace of first accepted ROI bounds if (boundsSet == null) boundsSet = roi.getBounds5D(); // save new size newBoundsList.add(bounds); } // add position change to undo manager Icy.getMainInterface().getUndoManager() .addEdit(new BoundsROIsEdit(roiToModify, saveBoundsList, newBoundsList, false)); } if (boundsSet != null) { final double p; // fix field value if needed if (source == sizeXField) p = boundsSet.getSizeX(); else if (source == sizeYField) p = boundsSet.getSizeY(); else if (source == sizeZField) p = boundsSet.getSizeZ(); else if (source == sizeTField) p = boundsSet.getSizeT(); else p = boundsSet.getSizeC(); source.setText(Double.toString(p)); } } } } } finally { modifyingRoi.release(); } } @Override public void colorChanged(ColorChooserButton source) { // source not anymore enable --> cancel change if (!source.isEnabled()) return; final Sequence sequence = Icy.getMainInterface().getActiveSequence(); if (sequence == null) return; if (!modifyingRoi.tryAcquire()) return; final List<ROI> rois = getSelectedRois(false); final List<Object> oldValues = new ArrayList<Object>(); final Color color = source.getColor(); sequence.beginUpdate(); try { // set new color for (ROI roi : rois) { // save previous color oldValues.add(roi.getColor()); roi.setColor(color); } } finally { sequence.endUpdate(); modifyingRoi.release(); } // add color change to undo manager sequence.addUndoableEdit(new PropertyROIsEdit(rois, ROI.PROPERTY_COLOR, oldValues, color)); } @Override public void stateChanged(ChangeEvent e) { if (!(e.getSource() instanceof JComponent)) return; final JComponent source = (JComponent) e.getSource(); // source not anymore enable --> cancel change if (!source.isEnabled()) return; final Sequence sequence = Icy.getMainInterface().getActiveSequence(); if (sequence == null) return; if (!modifyingRoi.tryAcquire()) return; try { if (source == strokeSpinner) { final List<ROI> rois = getSelectedRois(false); final List<Object> oldValues = new ArrayList<Object>(); final double stroke = ((Double) strokeSpinner.getValue()).doubleValue(); sequence.beginUpdate(); try { for (ROI roi : rois) { // save previous stroke oldValues.add(Double.valueOf(roi.getStroke())); roi.setStroke(stroke); } } finally { sequence.endUpdate(); } // add stroke change to undo manager sequence.addUndoableEdit( new PropertyROIsEdit(rois, ROI.PROPERTY_STROKE, oldValues, Double.valueOf(stroke))); } else if (source == alphaSlider) { final List<ROI> rois = getSelectedRois(true); final List<Object> oldValues = new ArrayList<Object>(); final float opacity = alphaSlider.getValue() / 100f; sequence.beginUpdate(); try { for (ROI roi : rois) { // save previous opacity oldValues.add(Float.valueOf(roi.getOpacity())); roi.setOpacity(opacity); } } finally { sequence.endUpdate(); } // add opacity change to undo manager sequence.addUndoableEdit( new PropertyROIsEdit(rois, ROI.PROPERTY_OPACITY, oldValues, Float.valueOf(opacity))); } else if ((source == posZSpinner) || (source == posTSpinner) || (source == posCSpinner)) { final List<ROI> rois = getSelectedRois(); // nothing to edit if ((rois == null) || rois.isEmpty()) return; final SpecialValueSpinner spinner = (SpecialValueSpinner) source; final double value = ((Integer) spinner.getValue()).intValue(); // correct value ? if (!Double.isNaN(value)) { final List<ROI> roiToModify = new ArrayList<ROI>(); // get the ROI we were modifying for (ROI roi : rois) { // can't edit read only ROI (should not arrive) if (roi.isReadOnly()) continue; // roi support position change ? if (roi.canSetPosition()) roiToModify.add(roi); } // we have effectively some ROI to modify ? if (!roiToModify.isEmpty()) { Point5D positionSet = null; // single change ? if (roiToModify.size() == 1) { final ROI roi = roiToModify.get(0); // get current ROI position final Point5D savePosition = roi.getPosition5D(); final Point5D position = (Point5D) savePosition.clone(); if (source == posZSpinner) position.setZ(value); else if (source == posTSpinner) position.setT(value); else position.setC(value); // set new position roi.setPosition5D(position); // keep trace of accepted ROI position positionSet = roi.getPosition5D(); // add position change to undo manager Icy.getMainInterface().getUndoManager() .addEdit(new PositionROIEdit(roi, savePosition, false)); } else { final List<Point5D> savePositions = new ArrayList<Point5D>(); final List<Point5D> newPositions = new ArrayList<Point5D>(); // save previous positions for (ROI roi : roiToModify) savePositions.add(roi.getPosition5D()); for (ROI roi : roiToModify) { // get current ROI position final Point5D position = roi.getPosition5D(); if (source == posZSpinner) position.setZ(value); else if (source == posTSpinner) position.setT(value); else position.setC(value); // set new position roi.setPosition5D(position); // keep trace of first accepted ROI position if (positionSet == null) positionSet = roi.getPosition5D(); // save new position newPositions.add(position); } // add position change to undo manager Icy.getMainInterface().getUndoManager() .addEdit(new PositionROIsEdit(roiToModify, savePositions, newPositions, false)); } if (positionSet != null) { double p; // fix field value if needed if (source == posZSpinner) p = positionSet.getZ(); else if (source == posTSpinner) p = positionSet.getT(); else p = positionSet.getC(); // change infinite by -1 if (p == Double.NEGATIVE_INFINITY) p = -1d; spinner.setValue(Integer.valueOf((int) p)); } } } } } finally { modifyingRoi.release(); } } @Override public void actionPerformed(ActionEvent e) { if (!(e.getSource() instanceof JComponent)) return; final JComponent source = (JComponent) e.getSource(); // source not anymore enable --> cancel change if (!source.isEnabled()) return; final Sequence sequence = Icy.getMainInterface().getActiveSequence(); if (sequence == null) return; if (!modifyingRoi.tryAcquire()) return; try { if (source == displayNameCheckBox) { sequence.beginUpdate(); try { final boolean display = displayNameCheckBox.isSelected(); for (ROI roi : getSelectedRois(false)) roi.setShowName(display); } finally { sequence.endUpdate(); } } else if (source == setAsDefaultBtn) { final boolean display = displayNameCheckBox.isSelected(); final Color color = colorButton.getColor(); final double stroke = ((Double) strokeSpinner.getValue()).doubleValue(); final float opacity = alphaSlider.getValue() / 100f; // set default ROI Overlay properties ROI.setDefaultColor(color); ROI.setDefaultShowName(display); ROI.setDefaultOpacity(opacity); ROI.setDefaultStroke(stroke); } } finally { modifyingRoi.release(); } } @Override public void clipboardChanged() { refreshROIActions(); } // one of the selected ROI changed public void roiChanged(ROIEvent event) { switch (event.getType()) { case ROI_CHANGED: // refresh the properties refreshROIProperties(); break; case FOCUS_CHANGED: // nothing to do here break; case PROPERTY_CHANGED: final String propertyName = event.getPropertyName(); if (ROI.PROPERTY_READONLY.equals(propertyName)) refreshROIActions(); break; case SELECTION_CHANGED: // handle externally with the setSelectedROI() method break; } } }