001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.gui.inspector;
020
021import java.awt.Color;
022import java.awt.Dimension;
023import java.awt.GridBagConstraints;
024import java.awt.GridBagLayout;
025import java.awt.Insets;
026import java.awt.event.ActionEvent;
027import java.awt.event.ActionListener;
028import java.lang.ref.Reference;
029import java.lang.ref.WeakReference;
030import java.util.ArrayList;
031import java.util.List;
032import java.util.concurrent.Semaphore;
033
034import javax.swing.BoxLayout;
035import javax.swing.JButton;
036import javax.swing.JCheckBox;
037import javax.swing.JComponent;
038import javax.swing.JLabel;
039import javax.swing.JPanel;
040import javax.swing.JSlider;
041import javax.swing.JSpinner;
042import javax.swing.SpinnerNumberModel;
043import javax.swing.SwingConstants;
044import javax.swing.UIManager;
045import javax.swing.border.TitledBorder;
046import javax.swing.event.ChangeEvent;
047import javax.swing.event.ChangeListener;
048
049import icy.clipboard.Clipboard;
050import icy.clipboard.Clipboard.ClipboardListener;
051import icy.gui.component.AbstractRoisPanel;
052import icy.gui.component.IcyTextField;
053import icy.gui.component.IcyTextField.TextChangeListener;
054import icy.gui.component.SpecialValueSpinner;
055import icy.gui.component.button.ColorChooserButton;
056import icy.gui.component.button.ColorChooserButton.ColorChangeListener;
057import icy.gui.component.model.SpecialValueSpinnerModel;
058import icy.main.Icy;
059import icy.math.MathUtil;
060import icy.roi.ROI;
061import icy.roi.ROIEvent;
062import icy.roi.edit.BoundsROIEdit;
063import icy.roi.edit.BoundsROIsEdit;
064import icy.roi.edit.PositionROIEdit;
065import icy.roi.edit.PositionROIsEdit;
066import icy.roi.edit.PropertyROIsEdit;
067import icy.sequence.Sequence;
068import icy.system.thread.ThreadUtil;
069import icy.type.point.Point5D;
070import icy.type.rectangle.Rectangle5D;
071import icy.util.StringUtil;
072
073/**
074 * @author Stephane
075 */
076public class RoiControlPanel extends JPanel
077        implements ColorChangeListener, TextChangeListener, ClipboardListener, ChangeListener, ActionListener
078{
079    /**
080     * 
081     */
082    private static final long serialVersionUID = 7403770406075917063L;
083
084    // GUI
085    JLabel posCFieldLabel;
086    JLabel posTFieldLabel;
087    JLabel posZFieldLabel;
088    JLabel posZSpinnerLabel;
089    JLabel posTSpinnerLabel;
090    JLabel posCSpinnerLabel;
091    IcyTextField posXField;
092    IcyTextField posYField;
093    IcyTextField posTField;
094    IcyTextField posZField;
095    IcyTextField sizeXField;
096    IcyTextField sizeZField;
097    IcyTextField sizeYField;
098    IcyTextField sizeTField;
099    IcyTextField posCField;
100    IcyTextField sizeCField;
101    SpecialValueSpinner posZSpinner;
102    SpecialValueSpinner posTSpinner;
103    SpecialValueSpinner posCSpinner;
104    ColorChooserButton colorButton;
105    JSlider alphaSlider;
106    JSpinner strokeSpinner;
107    JCheckBox displayNameCheckBox;
108    JButton setAsDefaultBtn;
109
110    // internals
111    private final AbstractRoisPanel roisPanel;
112    final Semaphore modifyingRoi;
113    private final List<Reference<ROI>> modifiedRois;
114    final Runnable roiActionsRefresher;
115    final Runnable roiPropertiesRefresher;
116
117    public RoiControlPanel(AbstractRoisPanel roisPanel)
118    {
119        super();
120
121        this.roisPanel = roisPanel;
122
123        modifyingRoi = new Semaphore(1);
124        modifiedRois = new ArrayList<Reference<ROI>>();
125
126        roiActionsRefresher = new Runnable()
127        {
128            @Override
129            public void run()
130            {
131                refreshROIActionsInternal();
132            }
133        };
134        roiPropertiesRefresher = new Runnable()
135        {
136            @Override
137            public void run()
138            {
139                refreshROIPropertiesInternal();
140            }
141        };
142
143        initialize();
144
145        colorButton.addColorChangeListener(this);
146        strokeSpinner.addChangeListener(this);
147        alphaSlider.addChangeListener(this);
148        displayNameCheckBox.addActionListener(this);
149
150        posXField.addTextChangeListener(this);
151        posYField.addTextChangeListener(this);
152        posZField.addTextChangeListener(this);
153        posZSpinner.addChangeListener(this);
154        posTField.addTextChangeListener(this);
155        posTSpinner.addChangeListener(this);
156        posCField.addTextChangeListener(this);
157        posCSpinner.addChangeListener(this);
158        sizeXField.addTextChangeListener(this);
159        sizeYField.addTextChangeListener(this);
160        sizeZField.addTextChangeListener(this);
161        sizeTField.addTextChangeListener(this);
162        sizeCField.addTextChangeListener(this);
163
164        setAsDefaultBtn.addActionListener(this);
165
166        Clipboard.addListener(this);
167
168        refreshROIActionsInternal();
169        refreshROIPropertiesInternal();
170    }
171
172    private void initialize()
173    {
174        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
175
176        JPanel actionPanel = new JPanel();
177        actionPanel.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Properties",
178                TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0)));
179        add(actionPanel);
180        GridBagLayout gbl_actionPanel = new GridBagLayout();
181        gbl_actionPanel.columnWidths = new int[] {0, 0, 0, 60, 0, 0};
182        gbl_actionPanel.rowHeights = new int[] {0, 0, 0};
183        gbl_actionPanel.columnWeights = new double[] {0.0, 1.0, 0.0, 1.0, 0.0, Double.MIN_VALUE};
184        gbl_actionPanel.rowWeights = new double[] {0.0, 0.0, Double.MIN_VALUE};
185        actionPanel.setLayout(gbl_actionPanel);
186
187        final JLabel lblColor = new JLabel("Color");
188        GridBagConstraints gbc_lblColor = new GridBagConstraints();
189        gbc_lblColor.anchor = GridBagConstraints.WEST;
190        gbc_lblColor.insets = new Insets(0, 0, 5, 5);
191        gbc_lblColor.gridx = 0;
192        gbc_lblColor.gridy = 0;
193        actionPanel.add(lblColor, gbc_lblColor);
194
195        colorButton = new ColorChooserButton();
196        GridBagConstraints gbc_colorButton = new GridBagConstraints();
197        gbc_colorButton.fill = GridBagConstraints.HORIZONTAL;
198        gbc_colorButton.insets = new Insets(0, 0, 5, 5);
199        gbc_colorButton.gridx = 1;
200        gbc_colorButton.gridy = 0;
201        actionPanel.add(colorButton, gbc_colorButton);
202        colorButton.setToolTipText("ROI color");
203
204        JLabel lblContentOpacity = new JLabel("Opacity");
205        GridBagConstraints gbc_lblContentOpacity = new GridBagConstraints();
206        gbc_lblContentOpacity.anchor = GridBagConstraints.WEST;
207        gbc_lblContentOpacity.insets = new Insets(0, 0, 5, 5);
208        gbc_lblContentOpacity.gridx = 2;
209        gbc_lblContentOpacity.gridy = 0;
210        actionPanel.add(lblContentOpacity, gbc_lblContentOpacity);
211
212        alphaSlider = new JSlider();
213        alphaSlider.setFocusable(false);
214        GridBagConstraints gbc_alphaSlider = new GridBagConstraints();
215        gbc_alphaSlider.gridwidth = 2;
216        gbc_alphaSlider.fill = GridBagConstraints.HORIZONTAL;
217        gbc_alphaSlider.insets = new Insets(0, 0, 5, 0);
218        gbc_alphaSlider.gridx = 3;
219        gbc_alphaSlider.gridy = 0;
220        actionPanel.add(alphaSlider, gbc_alphaSlider);
221        alphaSlider.setPreferredSize(new Dimension(80, 20));
222        alphaSlider.setMaximumSize(new Dimension(32767, 20));
223        alphaSlider.setMinimumSize(new Dimension(36, 20));
224        alphaSlider.setToolTipText("ROI content opacity");
225
226        JLabel lblNewLabel = new JLabel("Stroke");
227        GridBagConstraints gbc_lblNewLabel = new GridBagConstraints();
228        gbc_lblNewLabel.anchor = GridBagConstraints.WEST;
229        gbc_lblNewLabel.insets = new Insets(0, 0, 0, 5);
230        gbc_lblNewLabel.gridx = 0;
231        gbc_lblNewLabel.gridy = 1;
232        actionPanel.add(lblNewLabel, gbc_lblNewLabel);
233
234        strokeSpinner = new JSpinner();
235        strokeSpinner.setToolTipText("ROI stroke size (for visualization only)");
236        strokeSpinner.setModel(new SpinnerNumberModel(1.0, 1.0, 9.0, 1.0));
237        GridBagConstraints gbc_strokeSpinner = new GridBagConstraints();
238        gbc_strokeSpinner.fill = GridBagConstraints.HORIZONTAL;
239        gbc_strokeSpinner.insets = new Insets(0, 0, 0, 5);
240        gbc_strokeSpinner.gridx = 1;
241        gbc_strokeSpinner.gridy = 1;
242        actionPanel.add(strokeSpinner, gbc_strokeSpinner);
243
244        displayNameCheckBox = new JCheckBox("Show name");
245        displayNameCheckBox.setToolTipText("Show the ROI name");
246        displayNameCheckBox.setMargin(new Insets(2, 0, 2, 2));
247        displayNameCheckBox.setIconTextGap(10);
248        displayNameCheckBox.setHorizontalTextPosition(SwingConstants.LEADING);
249        GridBagConstraints gbc_displayNameCheckBox = new GridBagConstraints();
250        gbc_displayNameCheckBox.anchor = GridBagConstraints.WEST;
251        gbc_displayNameCheckBox.insets = new Insets(0, 0, 0, 5);
252        gbc_displayNameCheckBox.gridwidth = 2;
253        gbc_displayNameCheckBox.gridx = 2;
254        gbc_displayNameCheckBox.gridy = 1;
255        actionPanel.add(displayNameCheckBox, gbc_displayNameCheckBox);
256
257        setAsDefaultBtn = new JButton("As default");
258        setAsDefaultBtn.setEnabled(false);
259        setAsDefaultBtn.setMargin(new Insets(2, 2, 2, 2));
260        setAsDefaultBtn.setIconTextGap(0);
261        setAsDefaultBtn
262                .setToolTipText("Set the current color, opacity, stroke and show name values as the default settings");
263        GridBagConstraints gbc_setAsDefaultBtn = new GridBagConstraints();
264        gbc_setAsDefaultBtn.anchor = GridBagConstraints.EAST;
265        gbc_setAsDefaultBtn.gridx = 4;
266        gbc_setAsDefaultBtn.gridy = 1;
267        actionPanel.add(setAsDefaultBtn, gbc_setAsDefaultBtn);
268
269        JPanel positionAndSizePanel = new JPanel();
270        add(positionAndSizePanel);
271        GridBagLayout gbl_positionAndSizePanel = new GridBagLayout();
272        gbl_positionAndSizePanel.columnWidths = new int[] {0, 0, 0};
273        gbl_positionAndSizePanel.rowHeights = new int[] {0, 0};
274        gbl_positionAndSizePanel.columnWeights = new double[] {1.0, 1.0, Double.MIN_VALUE};
275        gbl_positionAndSizePanel.rowWeights = new double[] {0.0, Double.MIN_VALUE};
276        positionAndSizePanel.setLayout(gbl_positionAndSizePanel);
277
278        JPanel positionPanel = new JPanel();
279        positionPanel.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Position",
280                TitledBorder.LEFT, TitledBorder.TOP, null, new Color(0, 0, 0)));
281        GridBagConstraints gbc_positionPanel = new GridBagConstraints();
282        gbc_positionPanel.anchor = GridBagConstraints.NORTH;
283        gbc_positionPanel.insets = new Insets(0, 0, 0, 5);
284        gbc_positionPanel.fill = GridBagConstraints.HORIZONTAL;
285        gbc_positionPanel.gridx = 0;
286        gbc_positionPanel.gridy = 0;
287        positionAndSizePanel.add(positionPanel, gbc_positionPanel);
288        GridBagLayout gbl_positionPanel = new GridBagLayout();
289        gbl_positionPanel.columnWidths = new int[] {20, 0, 0};
290        gbl_positionPanel.rowHeights = new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0};
291        gbl_positionPanel.columnWeights = new double[] {0.0, 1.0, Double.MIN_VALUE};
292        gbl_positionPanel.rowWeights = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
293        positionPanel.setLayout(gbl_positionPanel);
294
295        final JLabel lblX = new JLabel("X");
296        GridBagConstraints gbc_lblX = new GridBagConstraints();
297        gbc_lblX.insets = new Insets(0, 0, 5, 5);
298        gbc_lblX.gridx = 0;
299        gbc_lblX.gridy = 0;
300        positionPanel.add(lblX, gbc_lblX);
301
302        posXField = new IcyTextField();
303        GridBagConstraints gbc_posXField = new GridBagConstraints();
304        gbc_posXField.insets = new Insets(0, 0, 5, 0);
305        gbc_posXField.fill = GridBagConstraints.HORIZONTAL;
306        gbc_posXField.gridx = 1;
307        gbc_posXField.gridy = 0;
308        positionPanel.add(posXField, gbc_posXField);
309        posXField.setToolTipText("X position of the ROI");
310        posXField.setColumns(8);
311
312        final JLabel lblY = new JLabel("Y");
313        GridBagConstraints gbc_lblY = new GridBagConstraints();
314        gbc_lblY.insets = new Insets(0, 0, 5, 5);
315        gbc_lblY.gridx = 0;
316        gbc_lblY.gridy = 1;
317        positionPanel.add(lblY, gbc_lblY);
318
319        posYField = new IcyTextField();
320        GridBagConstraints gbc_posYField = new GridBagConstraints();
321        gbc_posYField.fill = GridBagConstraints.HORIZONTAL;
322        gbc_posYField.insets = new Insets(0, 0, 5, 0);
323        gbc_posYField.gridx = 1;
324        gbc_posYField.gridy = 1;
325        positionPanel.add(posYField, gbc_posYField);
326        posYField.setToolTipText("Y position of the ROI");
327        posYField.setColumns(8);
328
329        posZFieldLabel = new JLabel("Z");
330        GridBagConstraints gbc_posZFieldLabel = new GridBagConstraints();
331        gbc_posZFieldLabel.insets = new Insets(0, 0, 5, 5);
332        gbc_posZFieldLabel.gridx = 0;
333        gbc_posZFieldLabel.gridy = 2;
334        positionPanel.add(posZFieldLabel, gbc_posZFieldLabel);
335
336        posZField = new IcyTextField();
337        GridBagConstraints gbc_posZField = new GridBagConstraints();
338        gbc_posZField.fill = GridBagConstraints.HORIZONTAL;
339        gbc_posZField.insets = new Insets(0, 0, 5, 0);
340        gbc_posZField.gridx = 1;
341        gbc_posZField.gridy = 2;
342        positionPanel.add(posZField, gbc_posZField);
343        posZField.setVisible(false);
344        posZField.setToolTipText("Z position of the ROI");
345        posZField.setColumns(8);
346
347        posZSpinnerLabel = new JLabel("Z");
348        GridBagConstraints gbc_posZSpinnerLabel = new GridBagConstraints();
349        gbc_posZSpinnerLabel.insets = new Insets(0, 0, 5, 5);
350        gbc_posZSpinnerLabel.gridx = 0;
351        gbc_posZSpinnerLabel.gridy = 3;
352        positionPanel.add(posZSpinnerLabel, gbc_posZSpinnerLabel);
353
354        posZSpinner = new SpecialValueSpinner(new SpecialValueSpinnerModel(-1, -1, 0, 1, -1, "ALL"));
355        GridBagConstraints gbc_posZSpinner = new GridBagConstraints();
356        gbc_posZSpinner.fill = GridBagConstraints.HORIZONTAL;
357        gbc_posZSpinner.insets = new Insets(0, 0, 5, 0);
358        gbc_posZSpinner.gridx = 1;
359        gbc_posZSpinner.gridy = 3;
360        positionPanel.add(posZSpinner, gbc_posZSpinner);
361        posZSpinner.setToolTipText("Attach the ROI to a specific Z slice (set to -1 for ALL)");
362
363        posTFieldLabel = new JLabel("T");
364        GridBagConstraints gbc_posTFieldLabel = new GridBagConstraints();
365        gbc_posTFieldLabel.insets = new Insets(0, 0, 5, 5);
366        gbc_posTFieldLabel.gridx = 0;
367        gbc_posTFieldLabel.gridy = 4;
368        positionPanel.add(posTFieldLabel, gbc_posTFieldLabel);
369
370        posTField = new IcyTextField();
371        GridBagConstraints gbc_posTField = new GridBagConstraints();
372        gbc_posTField.fill = GridBagConstraints.HORIZONTAL;
373        gbc_posTField.insets = new Insets(0, 0, 5, 0);
374        gbc_posTField.gridx = 1;
375        gbc_posTField.gridy = 4;
376        positionPanel.add(posTField, gbc_posTField);
377        posTField.setVisible(false);
378        posTField.setToolTipText("T position of the ROI");
379        posTField.setColumns(8);
380
381        posTSpinnerLabel = new JLabel("T");
382        GridBagConstraints gbc_posTSpinnerLabel = new GridBagConstraints();
383        gbc_posTSpinnerLabel.insets = new Insets(0, 0, 5, 5);
384        gbc_posTSpinnerLabel.gridx = 0;
385        gbc_posTSpinnerLabel.gridy = 5;
386        positionPanel.add(posTSpinnerLabel, gbc_posTSpinnerLabel);
387
388        posTSpinner = new SpecialValueSpinner(new SpecialValueSpinnerModel(-1, -1, 0, 1, -1, "ALL"));
389        GridBagConstraints gbc_posTSpinner = new GridBagConstraints();
390        gbc_posTSpinner.fill = GridBagConstraints.HORIZONTAL;
391        gbc_posTSpinner.insets = new Insets(0, 0, 5, 0);
392        gbc_posTSpinner.gridx = 1;
393        gbc_posTSpinner.gridy = 5;
394        positionPanel.add(posTSpinner, gbc_posTSpinner);
395        posTSpinner.setToolTipText("Attach the ROI to a specific T frame (set to -1 for ALL)");
396
397        posCFieldLabel = new JLabel("C");
398        GridBagConstraints gbc_posCFieldLabel = new GridBagConstraints();
399        gbc_posCFieldLabel.insets = new Insets(0, 0, 5, 5);
400        gbc_posCFieldLabel.gridx = 0;
401        gbc_posCFieldLabel.gridy = 6;
402        positionPanel.add(posCFieldLabel, gbc_posCFieldLabel);
403
404        posCField = new IcyTextField();
405        GridBagConstraints gbc_posCField = new GridBagConstraints();
406        gbc_posCField.fill = GridBagConstraints.HORIZONTAL;
407        gbc_posCField.insets = new Insets(0, 0, 5, 0);
408        gbc_posCField.gridx = 1;
409        gbc_posCField.gridy = 6;
410        positionPanel.add(posCField, gbc_posCField);
411        posCField.setVisible(false);
412        posCField.setToolTipText("C position of the ROI");
413        posCField.setColumns(8);
414
415        posCSpinnerLabel = new JLabel("C");
416        GridBagConstraints gbc_posCSpinnerLabel = new GridBagConstraints();
417        gbc_posCSpinnerLabel.insets = new Insets(0, 0, 0, 5);
418        gbc_posCSpinnerLabel.gridx = 0;
419        gbc_posCSpinnerLabel.gridy = 7;
420        positionPanel.add(posCSpinnerLabel, gbc_posCSpinnerLabel);
421
422        posCSpinner = new SpecialValueSpinner(new SpecialValueSpinnerModel(-1, -1, 0, 1, -1, "ALL"));
423        GridBagConstraints gbc_posCSpinner = new GridBagConstraints();
424        gbc_posCSpinner.fill = GridBagConstraints.HORIZONTAL;
425        gbc_posCSpinner.gridx = 1;
426        gbc_posCSpinner.gridy = 7;
427        positionPanel.add(posCSpinner, gbc_posCSpinner);
428        posCSpinner.setToolTipText("Attach the ROI to a specific C channel (set to -1 for ALL)");
429
430        JPanel sizePanel = new JPanel();
431        sizePanel.setBorder(new TitledBorder(null, "Dimension", TitledBorder.LEADING, TitledBorder.TOP, null, null));
432        GridBagConstraints gbc_sizePanel = new GridBagConstraints();
433        gbc_sizePanel.anchor = GridBagConstraints.NORTH;
434        gbc_sizePanel.fill = GridBagConstraints.HORIZONTAL;
435        gbc_sizePanel.gridx = 1;
436        gbc_sizePanel.gridy = 0;
437        positionAndSizePanel.add(sizePanel, gbc_sizePanel);
438
439        GridBagLayout gbl_sizePanel = new GridBagLayout();
440        gbl_sizePanel.columnWidths = new int[] {20, 0, 0};
441        gbl_sizePanel.rowHeights = new int[] {0, 0, 0, 0, 0, 0};
442        gbl_sizePanel.columnWeights = new double[] {0.0, 1.0, Double.MIN_VALUE};
443        gbl_sizePanel.rowWeights = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
444        sizePanel.setLayout(gbl_sizePanel);
445
446        final JLabel lblNewLabel_2 = new JLabel("X");
447        GridBagConstraints gbc_lblNewLabel_2 = new GridBagConstraints();
448        gbc_lblNewLabel_2.insets = new Insets(0, 0, 5, 5);
449        gbc_lblNewLabel_2.gridx = 0;
450        gbc_lblNewLabel_2.gridy = 0;
451        sizePanel.add(lblNewLabel_2, gbc_lblNewLabel_2);
452
453        sizeXField = new IcyTextField();
454        GridBagConstraints gbc_sizeXField = new GridBagConstraints();
455        gbc_sizeXField.insets = new Insets(0, 0, 5, 0);
456        gbc_sizeXField.fill = GridBagConstraints.HORIZONTAL;
457        gbc_sizeXField.gridx = 1;
458        gbc_sizeXField.gridy = 0;
459        sizePanel.add(sizeXField, gbc_sizeXField);
460        sizeXField.setToolTipText("Size of dimension X for the ROI");
461        sizeXField.setColumns(8);
462
463        final JLabel lblY_1 = new JLabel("Y");
464        GridBagConstraints gbc_lblY_1 = new GridBagConstraints();
465        gbc_lblY_1.insets = new Insets(0, 0, 5, 5);
466        gbc_lblY_1.gridx = 0;
467        gbc_lblY_1.gridy = 1;
468        sizePanel.add(lblY_1, gbc_lblY_1);
469
470        sizeYField = new IcyTextField();
471        GridBagConstraints gbc_sizeYField = new GridBagConstraints();
472        gbc_sizeYField.fill = GridBagConstraints.HORIZONTAL;
473        gbc_sizeYField.insets = new Insets(0, 0, 5, 0);
474        gbc_sizeYField.gridx = 1;
475        gbc_sizeYField.gridy = 1;
476        sizePanel.add(sizeYField, gbc_sizeYField);
477        sizeYField.setToolTipText("Size of dimension Y for the ROI");
478        sizeYField.setColumns(8);
479
480        JLabel sizeZFieldLabel = new JLabel("Z");
481        GridBagConstraints gbc_sizeZFieldLabel = new GridBagConstraints();
482        gbc_sizeZFieldLabel.insets = new Insets(0, 0, 5, 5);
483        gbc_sizeZFieldLabel.gridx = 0;
484        gbc_sizeZFieldLabel.gridy = 2;
485        sizePanel.add(sizeZFieldLabel, gbc_sizeZFieldLabel);
486
487        sizeZField = new IcyTextField();
488        GridBagConstraints gbc_sizeZField = new GridBagConstraints();
489        gbc_sizeZField.fill = GridBagConstraints.HORIZONTAL;
490        gbc_sizeZField.insets = new Insets(0, 0, 5, 0);
491        gbc_sizeZField.gridx = 1;
492        gbc_sizeZField.gridy = 2;
493        sizePanel.add(sizeZField, gbc_sizeZField);
494        sizeZField.setToolTipText("Size of dimension Z for the ROI");
495        sizeZField.setColumns(8);
496
497        JLabel sizeTFieldLabel = new JLabel("T");
498        GridBagConstraints gbc_sizeTFieldLabel = new GridBagConstraints();
499        gbc_sizeTFieldLabel.insets = new Insets(0, 0, 5, 5);
500        gbc_sizeTFieldLabel.gridx = 0;
501        gbc_sizeTFieldLabel.gridy = 3;
502        sizePanel.add(sizeTFieldLabel, gbc_sizeTFieldLabel);
503
504        sizeTField = new IcyTextField();
505        GridBagConstraints gbc_sizeTField = new GridBagConstraints();
506        gbc_sizeTField.fill = GridBagConstraints.HORIZONTAL;
507        gbc_sizeTField.insets = new Insets(0, 0, 5, 0);
508        gbc_sizeTField.gridx = 1;
509        gbc_sizeTField.gridy = 3;
510        sizePanel.add(sizeTField, gbc_sizeTField);
511        sizeTField.setToolTipText("Size of dimension T for the ROI");
512        sizeTField.setColumns(8);
513
514        JLabel sizeCFieldLabel = new JLabel("C");
515        GridBagConstraints gbc_sizeCFieldLabel = new GridBagConstraints();
516        gbc_sizeCFieldLabel.insets = new Insets(0, 0, 0, 5);
517        gbc_sizeCFieldLabel.gridx = 0;
518        gbc_sizeCFieldLabel.gridy = 4;
519        sizePanel.add(sizeCFieldLabel, gbc_sizeCFieldLabel);
520
521        sizeCField = new IcyTextField();
522        GridBagConstraints gbc_sizeCField = new GridBagConstraints();
523        gbc_sizeCField.fill = GridBagConstraints.HORIZONTAL;
524        gbc_sizeCField.gridx = 1;
525        gbc_sizeCField.gridy = 4;
526        sizePanel.add(sizeCField, gbc_sizeCField);
527        sizeCField.setToolTipText("Size of dimension C for the ROI");
528        sizeCField.setColumns(8);
529    }
530
531    public void selectionChanged()
532    {
533        refreshROIActionsAndProperties();
534    }
535
536    /**
537     * Get the visible ROI in the ROI control panel
538     */
539    List<ROI> getVisibleRois()
540    {
541        return roisPanel.getVisibleRois();
542    }
543
544    /**
545     * Get all selected ROIs
546     */
547    List<ROI> getSelectedRois()
548    {
549        return getSelectedRois(true);
550    }
551
552    /**
553     * Get the selected ROI in the ROI control panel.
554     * 
555     * @param wantReadOnly
556     *        If <code>true</code> the returned list will also contains ROI in Read-Only state
557     */
558    List<ROI> getSelectedRois(boolean wantReadOnly)
559    {
560        final List<ROI> selected = roisPanel.getSelectedRois();
561
562        if (wantReadOnly)
563            return selected;
564
565        final List<ROI> result = new ArrayList<ROI>(selected.size());
566
567        for (ROI roi : selected)
568            if (!roi.isReadOnly())
569                result.add(roi);
570
571        return result;
572    }
573
574    static double formatPosition(double pos, double size)
575    {
576        // special case of infinite dimension
577        if (size == Double.POSITIVE_INFINITY)
578            return -1d;
579
580        return MathUtil.roundSignificant(pos, 5, true);
581    }
582
583    static String getPositionAsString(double pos, double size)
584    {
585        // special case of infinite dimension
586        if (size == Double.POSITIVE_INFINITY)
587            return "all";
588
589        return StringUtil.toString(MathUtil.roundSignificant(pos, 5, true));
590    }
591
592    static double formatSize(double value)
593    {
594        return MathUtil.roundSignificant(value, 5, true);
595    }
596
597    static String getSizeAsString(double value)
598    {
599        // special case of infinite dimension
600        if (value == Double.POSITIVE_INFINITY)
601            return MathUtil.INFINITE_STRING;
602
603        return StringUtil.toString(formatSize(value));
604    }
605
606    /**
607     * Refresh the ROI actions state.
608     */
609    public void refreshROIActions()
610    {
611        ThreadUtil.runSingle(roiActionsRefresher);
612    }
613
614    /**
615     * Refresh the ROI actions state (internal)
616     */
617    void refreshROIActionsInternal()
618    {
619        final Sequence sequence = Icy.getMainInterface().getActiveSequence();
620        final List<ROI> selectedRois = getSelectedRois();
621        final ROI roi = (selectedRois.size() > 0) ? selectedRois.get(0) : null;
622
623        boolean canSetPos = false;
624        boolean canSetBnd = false;
625        boolean readOnly = true;
626        // set read only flag
627        for (ROI r : selectedRois)
628        {
629            readOnly &= r.isReadOnly();
630            canSetPos |= r.canSetPosition();
631            canSetBnd |= r.canSetBounds();
632        }
633
634        final boolean hasSelected = (roi != null);
635        final boolean canSetPosition = canSetPos;
636        final boolean canSetBounds = canSetBnd;
637        final boolean editable = !readOnly;
638        final int dim = (roi != null) ? roi.getDimension() : 0;
639
640        // wait a bit to avoid eating too much time with refresh
641        ThreadUtil.sleep(1);
642
643        ThreadUtil.invokeNow(new Runnable()
644        {
645            @Override
646            public void run()
647            {
648                // modifyingRoi.acquireUninterruptibly();
649                if (modifyingRoi.tryAcquire())
650                {
651                    try
652                    {
653                        if (sequence != null)
654                        {
655                            ((SpecialValueSpinnerModel) posZSpinner.getModel())
656                                    .setMaximum(Integer.valueOf(sequence.getSizeZ() - 1));
657                            ((SpecialValueSpinnerModel) posTSpinner.getModel())
658                                    .setMaximum(Integer.valueOf(sequence.getSizeT() - 1));
659                            ((SpecialValueSpinnerModel) posCSpinner.getModel())
660                                    .setMaximum(Integer.valueOf(sequence.getSizeC() - 1));
661                        }
662                        else
663                        {
664                            ((SpecialValueSpinnerModel) posZSpinner.getModel()).setMaximum(Integer.valueOf(0));
665                            ((SpecialValueSpinnerModel) posTSpinner.getModel()).setMaximum(Integer.valueOf(0));
666                            ((SpecialValueSpinnerModel) posCSpinner.getModel()).setMaximum(Integer.valueOf(0));
667                        }
668                    }
669                    finally
670                    {
671                        modifyingRoi.release();
672                    }
673                }
674
675                posXField.setEnabled(canSetPosition && editable);
676                posYField.setEnabled(canSetPosition && editable);
677                posZField.setEnabled(canSetPosition && editable);
678                posTField.setEnabled(canSetPosition && editable);
679                posCField.setEnabled(canSetPosition && editable);
680                posZSpinner.setEnabled(canSetPosition && editable);
681                posTSpinner.setEnabled(canSetPosition && editable);
682                posCSpinner.setEnabled(canSetPosition && editable);
683                sizeXField.setEnabled(canSetBounds && editable);
684                sizeYField.setEnabled(canSetBounds && editable && (dim > 1));
685                sizeZField.setEnabled(canSetBounds && editable && (dim > 2));
686                sizeTField.setEnabled(canSetBounds && editable && (dim > 3));
687                sizeCField.setEnabled(canSetBounds && editable && (dim > 4));
688
689                if (dim > 2)
690                {
691                    posZField.setVisible(true);
692                    posZFieldLabel.setVisible(true);
693                    posZSpinner.setVisible(false);
694                    posZSpinnerLabel.setVisible(false);
695                }
696                else
697                {
698                    posZField.setVisible(false);
699                    posZFieldLabel.setVisible(false);
700                    posZSpinner.setVisible(true);
701                    posZSpinnerLabel.setVisible(true);
702                }
703
704                if (dim > 3)
705                {
706                    posTField.setVisible(true);
707                    posTFieldLabel.setVisible(true);
708                    posTSpinner.setVisible(false);
709                    posTSpinnerLabel.setVisible(false);
710                }
711                else
712                {
713                    posTField.setVisible(false);
714                    posTFieldLabel.setVisible(false);
715                    posTSpinner.setVisible(true);
716                    posTSpinnerLabel.setVisible(true);
717                }
718
719                if (dim > 4)
720                {
721                    posCField.setVisible(true);
722                    posCFieldLabel.setVisible(true);
723                    posCSpinner.setVisible(false);
724                    posCSpinnerLabel.setVisible(false);
725                }
726                else
727                {
728                    posCField.setVisible(false);
729                    posCFieldLabel.setVisible(false);
730                    posCSpinner.setVisible(true);
731                    posCSpinnerLabel.setVisible(true);
732                }
733
734                colorButton.setEnabled(hasSelected && editable);
735                strokeSpinner.setEnabled(hasSelected && editable);
736                alphaSlider.setEnabled(hasSelected);
737                displayNameCheckBox.setEnabled(hasSelected);
738
739                setAsDefaultBtn.setEnabled(hasSelected);
740            }
741        });
742    }
743
744    /**
745     * Refresh ROI properties
746     */
747    public void refreshROIProperties()
748    {
749        ThreadUtil.runSingle(roiPropertiesRefresher);
750    }
751
752    /**
753     * Refresh ROI properties (internal)
754     */
755    void refreshROIPropertiesInternal()
756    {
757        final List<ROI> rois = getSelectedRois();
758        final ROI roi = (rois.size() > 0) ? rois.get(0) : null;
759        final Rectangle5D bounds = (roi != null) ? roi.getBounds5D() : null;
760
761        // wait a bit to avoid eating too much time with refresh
762        ThreadUtil.sleep(1);
763
764        ThreadUtil.invokeNow(new Runnable()
765        {
766            @Override
767            public void run()
768            {
769                // modifyingRoi.acquireUninterruptibly();
770                if (!modifyingRoi.tryAcquire())
771                    return;
772
773                try
774                {
775                    if (roi != null)
776                    {
777                        colorButton.setColor(roi.getColor());
778                        strokeSpinner.setValue(Double.valueOf(roi.getStroke()));
779                        alphaSlider.setValue((int) (roi.getOpacity() * 100));
780                        displayNameCheckBox.setSelected(roi.getShowName());
781                    }
782                    else
783                    {
784                        // no ROI selected
785                        colorButton.setColor(Color.gray);
786                        strokeSpinner.setValue(Double.valueOf(1d));
787                        alphaSlider.setValue(0);
788                        displayNameCheckBox.setSelected(false);
789                    }
790
791                    if (bounds != null)
792                    {
793                        posXField.setText(getPositionAsString(bounds.getX(), bounds.getSizeX()));
794                        posYField.setText(getPositionAsString(bounds.getY(), bounds.getSizeY()));
795                        posZSpinner.setValue(Integer.valueOf((int) formatPosition(bounds.getZ(), bounds.getSizeZ())));
796                        posZField.setText(getPositionAsString(bounds.getZ(), bounds.getSizeZ()));
797                        posTSpinner.setValue(Integer.valueOf((int) formatPosition(bounds.getT(), bounds.getSizeT())));
798                        posTField.setText(getPositionAsString(bounds.getT(), bounds.getSizeT()));
799                        posCSpinner.setValue(Integer.valueOf((int) formatPosition(bounds.getC(), bounds.getSizeC())));
800                        posCField.setText(getPositionAsString(bounds.getC(), bounds.getSizeC()));
801
802                        sizeXField.setText(getSizeAsString(bounds.getSizeX()));
803                        sizeYField.setText(getSizeAsString(bounds.getSizeY()));
804                        sizeZField.setText(getSizeAsString(bounds.getSizeZ()));
805                        sizeTField.setText(getSizeAsString(bounds.getSizeT()));
806                        sizeCField.setText(getSizeAsString(bounds.getSizeC()));
807                    }
808                    else
809                    {
810                        posXField.setText("");
811                        posYField.setText("");
812                        posZField.setText("");
813                        posTField.setText("");
814                        posCField.setText("");
815                        posZSpinner.setValue(Integer.valueOf(0));
816                        posTSpinner.setValue(Integer.valueOf(0));
817                        posCSpinner.setValue(Integer.valueOf(0));
818
819                        sizeXField.setText("");
820                        sizeYField.setText("");
821                        sizeZField.setText("");
822                        sizeTField.setText("");
823                        sizeCField.setText("");
824                    }
825                }
826                finally
827                {
828                    modifyingRoi.release();
829                }
830            }
831        });
832    }
833
834    /**
835     * Refresh ROI actions and properties
836     */
837    public void refreshROIActionsAndProperties()
838    {
839        ThreadUtil.runSingle(roiActionsRefresher);
840        ThreadUtil.runSingle(roiPropertiesRefresher);
841    }
842
843    @Override
844    public void textChanged(IcyTextField source, boolean validate)
845    {
846        // source not anymore enable --> cancel validation
847        if (!source.isEnabled())
848            return;
849
850        // keep trace of modified ROI and wait for validation
851        if (!validate)
852        {
853            modifiedRois.clear();
854            // better to use weak reference here to not retain ROI
855            for (ROI roi : getSelectedRois())
856                modifiedRois.add(new WeakReference<ROI>(roi));
857
858            return;
859        }
860
861        // at this point the text is validated so we can recover modified ROIs...
862        final List<ROI> rois = new ArrayList<ROI>();
863        for (Reference<ROI> ref : modifiedRois)
864        {
865            final ROI roi = ref.get();
866            if (roi != null)
867                rois.add(roi);
868        }
869
870        // nothing to edit ?
871        if (rois.isEmpty())
872            return;
873
874        // can get semaphore ?
875        if (!modifyingRoi.tryAcquire())
876            return;
877
878        try
879        {
880            // position fields ?
881            if ((source == posXField) || (source == posYField) || (source == posZField) || (source == posTField)
882                    || (source == posCField))
883            {
884                final double value = StringUtil.parseDouble(source.getText(), Double.NaN);
885
886                // correct value ?
887                if (!Double.isNaN(value))
888                {
889                    final List<ROI> roiToModify = new ArrayList<ROI>();
890
891                    // get the ROI we were modifying
892                    for (ROI roi : rois)
893                    {
894                        // can't edit read only ROI (should not arrive)
895                        if (roi.isReadOnly())
896                            continue;
897
898                        // roi support position change ?
899                        if (roi.canSetPosition())
900                            roiToModify.add(roi);
901                    }
902
903                    // we have effectively some ROI to modify ?
904                    if (!roiToModify.isEmpty())
905                    {
906                        Point5D positionSet = null;
907
908                        // single change ?
909                        if (roiToModify.size() == 1)
910                        {
911                            final ROI roi = roiToModify.get(0);
912
913                            // get current ROI position
914                            final Point5D savePosition = roi.getPosition5D();
915                            final Point5D position = (Point5D) savePosition.clone();
916
917                            if (source == posXField)
918                                position.setX(value);
919                            else if (source == posYField)
920                                position.setY(value);
921                            else if (source == posZField)
922                                position.setZ(value);
923                            else if (source == posTField)
924                                position.setT(value);
925                            else
926                                position.setC(value);
927
928                            // set new position
929                            roi.setPosition5D(position);
930                            // keep trace of accepted ROI position
931                            positionSet = roi.getPosition5D();
932
933                            // add position change to undo manager
934                            Icy.getMainInterface().getUndoManager()
935                                    .addEdit(new PositionROIEdit(roi, savePosition, false));
936                        }
937                        else
938                        {
939                            final List<Point5D> savePositions = new ArrayList<Point5D>();
940                            final List<Point5D> newPositions = new ArrayList<Point5D>();
941
942                            // save previous positions
943                            for (ROI roi : roiToModify)
944                                savePositions.add(roi.getPosition5D());
945
946                            for (ROI roi : roiToModify)
947                            {
948                                // get current ROI position
949                                final Point5D position = roi.getPosition5D();
950
951                                if (source == posXField)
952                                    position.setX(value);
953                                else if (source == posYField)
954                                    position.setY(value);
955                                else if (source == posZField)
956                                    position.setZ(value);
957                                else if (source == posTField)
958                                    position.setT(value);
959                                else
960                                    position.setC(value);
961
962                                // set new position
963                                roi.setPosition5D(position);
964                                // keep trace of first accepted ROI position
965                                if (positionSet == null)
966                                    positionSet = roi.getPosition5D();
967
968                                // save new position
969                                newPositions.add(position);
970                            }
971
972                            // add position change to undo manager
973                            Icy.getMainInterface().getUndoManager()
974                                    .addEdit(new PositionROIsEdit(roiToModify, savePositions, newPositions, false));
975                        }
976
977                        if (positionSet != null)
978                        {
979                            double p;
980
981                            // fix field value if needed
982                            if (source == posXField)
983                                p = positionSet.getX();
984                            else if (source == posYField)
985                                p = positionSet.getY();
986                            else if (source == posZField)
987                                p = positionSet.getZ();
988                            else if (source == posTField)
989                                p = positionSet.getT();
990                            else
991                                p = positionSet.getC();
992
993                            // change infinite by -1
994                            if (p == Double.NEGATIVE_INFINITY)
995                                p = -1d;
996
997                            source.setText(Double.toString(p));
998                        }
999                    }
1000                }
1001            }
1002            // size fields ?
1003            else if ((source == sizeXField) || (source == sizeYField) || (source == sizeZField)
1004                    || (source == sizeTField) || (source == sizeCField))
1005            {
1006                final double value = StringUtil.parseDouble(source.getText(), Double.NaN);
1007
1008                // correct value ?
1009                if (!Double.isNaN(value))
1010                {
1011                    final List<ROI> roiToModify = new ArrayList<ROI>();
1012
1013                    // get the ROI we were modifying
1014                    for (ROI roi : rois)
1015                    {
1016                        // can't edit read only ROI (should not arrive)
1017                        if (roi.isReadOnly())
1018                            continue;
1019
1020                        // roi support position change ?
1021                        if (roi.canSetPosition())
1022                            roiToModify.add(roi);
1023                    }
1024
1025                    // we have effectively some ROI to modify ?
1026                    if (!roiToModify.isEmpty())
1027                    {
1028                        Rectangle5D boundsSet = null;
1029
1030                        if (roiToModify.size() == 1)
1031                        {
1032                            final ROI roi = roiToModify.get(0);
1033
1034                            // get current ROI size
1035                            final Rectangle5D saveBounds = roi.getBounds5D();
1036                            final Rectangle5D bounds = (Rectangle5D) saveBounds.clone();
1037
1038                            if (source == sizeXField)
1039                                bounds.setSizeX(value);
1040                            else if (source == sizeYField)
1041                                bounds.setSizeY(value);
1042                            else if (source == sizeZField)
1043                                bounds.setSizeZ(value);
1044                            else if (source == sizeTField)
1045                                bounds.setSizeT(value);
1046                            else
1047                                bounds.setSizeC(value);
1048
1049                            roi.setBounds5D(bounds);
1050                            // keep trace of first accepted ROI bounds
1051                            boundsSet = roi.getBounds5D();
1052
1053                            // add position change to undo manager
1054                            Icy.getMainInterface().getUndoManager().addEdit(new BoundsROIEdit(roi, saveBounds, false));
1055                        }
1056                        else
1057                        {
1058                            final List<Rectangle5D> saveBoundsList = new ArrayList<Rectangle5D>();
1059                            final List<Rectangle5D> newBoundsList = new ArrayList<Rectangle5D>();
1060
1061                            // save previous size
1062                            for (ROI roi : roiToModify)
1063                                saveBoundsList.add(roi.getBounds5D());
1064
1065                            for (ROI roi : roiToModify)
1066                            {
1067                                // get current ROI size
1068                                Rectangle5D bounds = roi.getBounds5D();
1069
1070                                if (source == sizeXField)
1071                                    bounds.setSizeX(value);
1072                                else if (source == sizeYField)
1073                                    bounds.setSizeY(value);
1074                                else if (source == sizeZField)
1075                                    bounds.setSizeZ(value);
1076                                else if (source == sizeTField)
1077                                    bounds.setSizeT(value);
1078                                else
1079                                    bounds.setSizeC(value);
1080
1081                                roi.setBounds5D(bounds);
1082                                // keep trace of first accepted ROI bounds
1083                                if (boundsSet == null)
1084                                    boundsSet = roi.getBounds5D();
1085
1086                                // save new size
1087                                newBoundsList.add(bounds);
1088                            }
1089
1090                            // add position change to undo manager
1091                            Icy.getMainInterface().getUndoManager()
1092                                    .addEdit(new BoundsROIsEdit(roiToModify, saveBoundsList, newBoundsList, false));
1093                        }
1094
1095                        if (boundsSet != null)
1096                        {
1097                            final double p;
1098
1099                            // fix field value if needed
1100                            if (source == sizeXField)
1101                                p = boundsSet.getSizeX();
1102                            else if (source == sizeYField)
1103                                p = boundsSet.getSizeY();
1104                            else if (source == sizeZField)
1105                                p = boundsSet.getSizeZ();
1106                            else if (source == sizeTField)
1107                                p = boundsSet.getSizeT();
1108                            else
1109                                p = boundsSet.getSizeC();
1110
1111                            source.setText(Double.toString(p));
1112                        }
1113                    }
1114                }
1115            }
1116        }
1117        finally
1118        {
1119            modifyingRoi.release();
1120        }
1121    }
1122
1123    @Override
1124    public void colorChanged(ColorChooserButton source)
1125    {
1126        // source not anymore enable --> cancel change
1127        if (!source.isEnabled())
1128            return;
1129
1130        final Sequence sequence = Icy.getMainInterface().getActiveSequence();
1131        if (sequence == null)
1132            return;
1133
1134        if (!modifyingRoi.tryAcquire())
1135            return;
1136
1137        final List<ROI> rois = getSelectedRois(false);
1138        final List<Object> oldValues = new ArrayList<Object>();
1139        final Color color = source.getColor();
1140
1141        sequence.beginUpdate();
1142        try
1143        {
1144            // set new color
1145            for (ROI roi : rois)
1146            {
1147                // save previous color
1148                oldValues.add(roi.getColor());
1149                roi.setColor(color);
1150            }
1151        }
1152        finally
1153        {
1154            sequence.endUpdate();
1155            modifyingRoi.release();
1156        }
1157
1158        // add color change to undo manager
1159        sequence.addUndoableEdit(new PropertyROIsEdit(rois, ROI.PROPERTY_COLOR, oldValues, color));
1160    }
1161
1162    @Override
1163    public void stateChanged(ChangeEvent e)
1164    {
1165        if (!(e.getSource() instanceof JComponent))
1166            return;
1167
1168        final JComponent source = (JComponent) e.getSource();
1169
1170        // source not anymore enable --> cancel change
1171        if (!source.isEnabled())
1172            return;
1173
1174        final Sequence sequence = Icy.getMainInterface().getActiveSequence();
1175        if (sequence == null)
1176            return;
1177
1178        if (!modifyingRoi.tryAcquire())
1179            return;
1180
1181        try
1182        {
1183            if (source == strokeSpinner)
1184            {
1185                final List<ROI> rois = getSelectedRois(false);
1186                final List<Object> oldValues = new ArrayList<Object>();
1187                final double stroke = ((Double) strokeSpinner.getValue()).doubleValue();
1188
1189                sequence.beginUpdate();
1190                try
1191                {
1192                    for (ROI roi : rois)
1193                    {
1194                        // save previous stroke
1195                        oldValues.add(Double.valueOf(roi.getStroke()));
1196                        roi.setStroke(stroke);
1197                    }
1198                }
1199                finally
1200                {
1201                    sequence.endUpdate();
1202                }
1203
1204                // add stroke change to undo manager
1205                sequence.addUndoableEdit(
1206                        new PropertyROIsEdit(rois, ROI.PROPERTY_STROKE, oldValues, Double.valueOf(stroke)));
1207            }
1208            else if (source == alphaSlider)
1209            {
1210                final List<ROI> rois = getSelectedRois(true);
1211                final List<Object> oldValues = new ArrayList<Object>();
1212                final float opacity = alphaSlider.getValue() / 100f;
1213
1214                sequence.beginUpdate();
1215                try
1216                {
1217                    for (ROI roi : rois)
1218                    {
1219                        // save previous opacity
1220                        oldValues.add(Float.valueOf(roi.getOpacity()));
1221                        roi.setOpacity(opacity);
1222                    }
1223                }
1224                finally
1225                {
1226                    sequence.endUpdate();
1227                }
1228
1229                // add opacity change to undo manager
1230                sequence.addUndoableEdit(
1231                        new PropertyROIsEdit(rois, ROI.PROPERTY_OPACITY, oldValues, Float.valueOf(opacity)));
1232            }
1233            else if ((source == posZSpinner) || (source == posTSpinner) || (source == posCSpinner))
1234            {
1235                final List<ROI> rois = getSelectedRois();
1236
1237                // nothing to edit
1238                if ((rois == null) || rois.isEmpty())
1239                    return;
1240
1241                final SpecialValueSpinner spinner = (SpecialValueSpinner) source;
1242                final double value = ((Integer) spinner.getValue()).intValue();
1243
1244                // correct value ?
1245                if (!Double.isNaN(value))
1246                {
1247                    final List<ROI> roiToModify = new ArrayList<ROI>();
1248
1249                    // get the ROI we were modifying
1250                    for (ROI roi : rois)
1251                    {
1252                        // can't edit read only ROI (should not arrive)
1253                        if (roi.isReadOnly())
1254                            continue;
1255
1256                        // roi support position change ?
1257                        if (roi.canSetPosition())
1258                            roiToModify.add(roi);
1259                    }
1260
1261                    // we have effectively some ROI to modify ?
1262                    if (!roiToModify.isEmpty())
1263                    {
1264                        Point5D positionSet = null;
1265
1266                        // single change ?
1267                        if (roiToModify.size() == 1)
1268                        {
1269                            final ROI roi = roiToModify.get(0);
1270
1271                            // get current ROI position
1272                            final Point5D savePosition = roi.getPosition5D();
1273                            final Point5D position = (Point5D) savePosition.clone();
1274
1275                            if (source == posZSpinner)
1276                                position.setZ(value);
1277                            else if (source == posTSpinner)
1278                                position.setT(value);
1279                            else
1280                                position.setC(value);
1281
1282                            // set new position
1283                            roi.setPosition5D(position);
1284                            // keep trace of accepted ROI position
1285                            positionSet = roi.getPosition5D();
1286
1287                            // add position change to undo manager
1288                            Icy.getMainInterface().getUndoManager()
1289                                    .addEdit(new PositionROIEdit(roi, savePosition, false));
1290                        }
1291                        else
1292                        {
1293                            final List<Point5D> savePositions = new ArrayList<Point5D>();
1294                            final List<Point5D> newPositions = new ArrayList<Point5D>();
1295
1296                            // save previous positions
1297                            for (ROI roi : roiToModify)
1298                                savePositions.add(roi.getPosition5D());
1299
1300                            for (ROI roi : roiToModify)
1301                            {
1302                                // get current ROI position
1303                                final Point5D position = roi.getPosition5D();
1304
1305                                if (source == posZSpinner)
1306                                    position.setZ(value);
1307                                else if (source == posTSpinner)
1308                                    position.setT(value);
1309                                else
1310                                    position.setC(value);
1311
1312                                // set new position
1313                                roi.setPosition5D(position);
1314                                // keep trace of first accepted ROI position
1315                                if (positionSet == null)
1316                                    positionSet = roi.getPosition5D();
1317
1318                                // save new position
1319                                newPositions.add(position);
1320                            }
1321
1322                            // add position change to undo manager
1323                            Icy.getMainInterface().getUndoManager()
1324                                    .addEdit(new PositionROIsEdit(roiToModify, savePositions, newPositions, false));
1325                        }
1326
1327                        if (positionSet != null)
1328                        {
1329                            double p;
1330
1331                            // fix field value if needed
1332                            if (source == posZSpinner)
1333                                p = positionSet.getZ();
1334                            else if (source == posTSpinner)
1335                                p = positionSet.getT();
1336                            else
1337                                p = positionSet.getC();
1338
1339                            // change infinite by -1
1340                            if (p == Double.NEGATIVE_INFINITY)
1341                                p = -1d;
1342
1343                            spinner.setValue(Integer.valueOf((int) p));
1344                        }
1345                    }
1346                }
1347            }
1348        }
1349        finally
1350        {
1351            modifyingRoi.release();
1352        }
1353    }
1354
1355    @Override
1356    public void actionPerformed(ActionEvent e)
1357    {
1358        if (!(e.getSource() instanceof JComponent))
1359            return;
1360
1361        final JComponent source = (JComponent) e.getSource();
1362
1363        // source not anymore enable --> cancel change
1364        if (!source.isEnabled())
1365            return;
1366
1367        final Sequence sequence = Icy.getMainInterface().getActiveSequence();
1368        if (sequence == null)
1369            return;
1370
1371        if (!modifyingRoi.tryAcquire())
1372            return;
1373
1374        try
1375        {
1376            if (source == displayNameCheckBox)
1377            {
1378                sequence.beginUpdate();
1379                try
1380                {
1381                    final boolean display = displayNameCheckBox.isSelected();
1382
1383                    for (ROI roi : getSelectedRois(false))
1384                        roi.setShowName(display);
1385                }
1386                finally
1387                {
1388                    sequence.endUpdate();
1389                }
1390            }
1391            else if (source == setAsDefaultBtn)
1392            {
1393                final boolean display = displayNameCheckBox.isSelected();
1394                final Color color = colorButton.getColor();
1395                final double stroke = ((Double) strokeSpinner.getValue()).doubleValue();
1396                final float opacity = alphaSlider.getValue() / 100f;
1397
1398                // set default ROI Overlay properties
1399                ROI.setDefaultColor(color);
1400                ROI.setDefaultShowName(display);
1401                ROI.setDefaultOpacity(opacity);
1402                ROI.setDefaultStroke(stroke);
1403            }
1404        }
1405        finally
1406        {
1407            modifyingRoi.release();
1408        }
1409    }
1410
1411    @Override
1412    public void clipboardChanged()
1413    {
1414        refreshROIActions();
1415    }
1416
1417    // one of the selected ROI changed
1418    public void roiChanged(ROIEvent event)
1419    {
1420        switch (event.getType())
1421        {
1422            case ROI_CHANGED:
1423                // refresh the properties
1424                refreshROIProperties();
1425                break;
1426
1427            case FOCUS_CHANGED:
1428                // nothing to do here
1429                break;
1430
1431            case PROPERTY_CHANGED:
1432                final String propertyName = event.getPropertyName();
1433
1434                if (ROI.PROPERTY_READONLY.equals(propertyName))
1435                    refreshROIActions();
1436                break;
1437
1438            case SELECTION_CHANGED:
1439                // handle externally with the setSelectedROI() method
1440                break;
1441        }
1442    }
1443
1444}