/*
* 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;
}
}
}