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.menu;
020
021import java.awt.event.ActionEvent;
022import java.awt.event.ActionListener;
023import java.util.ArrayList;
024import java.util.EventListener;
025import java.util.List;
026
027import javax.swing.SwingConstants;
028import javax.swing.event.EventListenerList;
029
030import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
031import org.pushingpixels.flamingo.api.common.CommandToggleButtonGroup;
032import org.pushingpixels.flamingo.api.common.HorizontalAlignment;
033import org.pushingpixels.flamingo.api.common.JCommandButton;
034import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
035import org.pushingpixels.flamingo.api.common.JCommandToggleButton;
036import org.pushingpixels.flamingo.api.common.popup.JCommandPopupMenu;
037import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
038import org.pushingpixels.flamingo.api.common.popup.PopupPanelCallback;
039import org.pushingpixels.flamingo.api.ribbon.JRibbonBand;
040import org.pushingpixels.flamingo.api.ribbon.JRibbonComponent;
041import org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority;
042import org.pushingpixels.flamingo.api.ribbon.RibbonTask;
043import org.pushingpixels.flamingo.api.ribbon.resize.CoreRibbonResizeSequencingPolicies;
044import org.pushingpixels.flamingo.internal.ui.ribbon.BasicBandControlPanelUI;
045
046import icy.action.RoiActions;
047import icy.gui.component.NumberTextField;
048import icy.gui.component.button.IcyButton;
049import icy.gui.component.button.IcyCommandButton;
050import icy.gui.component.button.IcyCommandMenuButton;
051import icy.gui.component.button.IcyCommandToggleButton;
052import icy.gui.inspector.RoisPanel;
053import icy.gui.plugin.PluginCommandButton;
054import icy.gui.util.RibbonUtil;
055import icy.main.Icy;
056import icy.plugin.PluginDescriptor;
057import icy.plugin.PluginLoader;
058import icy.plugin.PluginLoader.PluginLoaderEvent;
059import icy.plugin.PluginLoader.PluginLoaderListener;
060import icy.resource.ResourceUtil;
061import icy.resource.icon.IcyIcon;
062import icy.roi.ROI;
063import icy.roi.ROI2D;
064import icy.roi.ROI3D;
065import icy.sequence.Sequence;
066import icy.system.thread.ThreadUtil;
067import icy.util.StringUtil;
068import plugins.kernel.roi.roi2d.ROI2DArea;
069import plugins.kernel.roi.roi2d.ROI2DShape;
070import plugins.kernel.roi.roi2d.plugin.ROI2DAreaPlugin;
071import plugins.kernel.roi.roi2d.plugin.ROI2DEllipsePlugin;
072import plugins.kernel.roi.roi2d.plugin.ROI2DLinePlugin;
073import plugins.kernel.roi.roi2d.plugin.ROI2DPointPlugin;
074import plugins.kernel.roi.roi2d.plugin.ROI2DPolyLinePlugin;
075import plugins.kernel.roi.roi2d.plugin.ROI2DPolygonPlugin;
076import plugins.kernel.roi.roi2d.plugin.ROI2DRectanglePlugin;
077import plugins.kernel.roi.roi3d.ROI3DArea;
078import plugins.kernel.roi.roi3d.plugin.ROI3DLinePlugin;
079import plugins.kernel.roi.roi3d.plugin.ROI3DPointPlugin;
080import plugins.kernel.roi.roi3d.plugin.ROI3DPolyLinePlugin;
081import plugins.kernel.roi.roi4d.ROI4DArea;
082import plugins.kernel.roi.roi5d.ROI5DArea;
083import plugins.kernel.roi.tool.plugin.ROILineCutterPlugin;
084
085/**
086 * ROI dedicated task
087 * 
088 * @author Stephane
089 */
090public class ROITask extends RibbonTask implements PluginLoaderListener
091{
092    public static final String NAME = "Region Of Interest";
093
094    /**
095     * @deprecated Use {@link #setSelected(String)} with <code>null</code> parameter instead.
096     */
097    @Deprecated
098    public static final String SELECT = "Selection";
099    /**
100     * @deprecated Use {@link #setSelected(String)} with <code>null</code> parameter instead.
101     */
102    @Deprecated
103    public static final String MOVE = "Move";
104
105    /**
106     * @deprecated Use {@link ROITask#isROITool()} instead.
107     */
108    @Deprecated
109    public static boolean isROITool(String command)
110    {
111        // assume it's a ROI command when it's not null
112        return (command != null);
113    }
114
115    /**
116     * Listener class
117     */
118    public interface ROITaskListener extends EventListener
119    {
120        public void toolChanged(String command);
121    }
122
123    static class ROI2DBand extends JRibbonBand
124    {
125        public static final String BAND_NAME = "2D ROI";
126
127        final List<IcyCommandToggleButton> pluginButtons;
128
129        public ROI2DBand()
130        {
131            super(BAND_NAME, new IcyIcon(ResourceUtil.ICON_ROI_POLYGON));
132
133            startGroup();
134
135            pluginButtons = new ArrayList<IcyCommandToggleButton>();
136
137            // refresh buttons (don't set button group and listener at this point)
138            setHidden(setROIFromPlugins(pluginButtons, this, getROIPlugins(), null, null));
139            updateButtonsState();
140
141            RibbonUtil.setRestrictiveResizePolicies(this);
142            setToolTipText("2D Region Of Interest");
143        }
144
145        public static List<PluginDescriptor> getROIPlugins()
146        {
147            final List<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
148
149            // 2D ROI
150            result.add(PluginLoader.getPlugin(ROI2DAreaPlugin.class.getName()));
151            result.add(PluginLoader.getPlugin(ROI2DRectanglePlugin.class.getName()));
152            result.add(PluginLoader.getPlugin(ROI2DEllipsePlugin.class.getName()));
153            result.add(PluginLoader.getPlugin(ROI2DPolygonPlugin.class.getName()));
154            result.add(PluginLoader.getPlugin(ROI2DPointPlugin.class.getName()));
155            result.add(PluginLoader.getPlugin(ROI2DLinePlugin.class.getName()));
156            result.add(PluginLoader.getPlugin(ROI2DPolyLinePlugin.class.getName()));
157
158            return result;
159        }
160
161        void updateButtonsState()
162        {
163            final Sequence sequence = Icy.getMainInterface().getActiveSequence();
164
165            for (IcyCommandToggleButton button : pluginButtons)
166                button.setEnabled(sequence != null);
167        }
168    }
169
170    static class ROI3DBand extends JRibbonBand
171    {
172        public static final String BAND_NAME = "3D ROI";
173
174        final List<IcyCommandToggleButton> pluginButtons;
175
176        public ROI3DBand()
177        {
178            super(BAND_NAME, new IcyIcon(ResourceUtil.ICON_CUBE_3D));
179
180            startGroup();
181
182            pluginButtons = new ArrayList<IcyCommandToggleButton>();
183
184            // refresh buttons (don't set button group and listener at this point)
185            setHidden(setROIFromPlugins(pluginButtons, this, getROIPlugins(), null, null));
186            updateButtonsState();
187
188            RibbonUtil.setRestrictiveResizePolicies(this);
189            setToolTipText("3D Region Of Interest");
190        }
191
192        public static List<PluginDescriptor> getROIPlugins()
193        {
194            final List<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
195
196            // 3D ROI
197            result.add(PluginLoader.getPlugin(ROI3DPointPlugin.class.getName()));
198            result.add(PluginLoader.getPlugin(ROI3DLinePlugin.class.getName()));
199            result.add(PluginLoader.getPlugin(ROI3DPolyLinePlugin.class.getName()));
200
201            return result;
202        }
203
204        void updateButtonsState()
205        {
206            final Sequence sequence = Icy.getMainInterface().getActiveSequence();
207
208            for (IcyCommandToggleButton button : pluginButtons)
209                button.setEnabled(sequence != null);
210        }
211    }
212
213    static class ROIExtBand extends JRibbonBand
214    {
215        public static final String BAND_NAME = "External ROI";
216
217        final List<IcyCommandToggleButton> pluginButtons;
218
219        public ROIExtBand()
220        {
221            super(BAND_NAME, new IcyIcon(ResourceUtil.ICON_ROI));
222
223            startGroup();
224
225            pluginButtons = new ArrayList<IcyCommandToggleButton>();
226
227            // refresh buttons (don't set button group and listener at this point)
228            setHidden(setROIFromPlugins(pluginButtons, this, getROIPlugins(), null, null));
229            updateButtonsState();
230
231            RibbonUtil.setRestrictiveResizePolicies(this);
232            setToolTipText("External Region Of Interest");
233            updateButtonsState();
234        }
235
236        public static List<PluginDescriptor> getROIPlugins()
237        {
238            final List<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
239
240            // remove default 2D & 3D ROI to only keep external ROI
241            result.removeAll(ROI2DBand.getROIPlugins());
242            result.removeAll(ROI3DBand.getROIPlugins());
243            // explicitly remove the ROI cutter from the list
244            result.remove(PluginLoader.getPlugin(ROILineCutterPlugin.class.getName()));
245
246            return result;
247        }
248
249        void updateButtonsState()
250        {
251            final Sequence sequence = Icy.getMainInterface().getActiveSequence();
252
253            for (IcyCommandToggleButton button : pluginButtons)
254                button.setEnabled(sequence != null);
255        }
256    }
257
258    static class ROIConversionBand extends JRibbonBand
259    {
260        public static final String BAND_NAME = "Conversion";
261
262        final NumberTextField radiusField;
263        final IcyButton convertToEllipseButton;
264        final IcyButton convertToRectangleButton;
265        final IcyButton convertToStackButton;
266        final IcyButton convertToMaskButton;
267        final IcyButton convertToShapeButton;
268
269        public ROIConversionBand()
270        {
271            super(BAND_NAME, new IcyIcon(ResourceUtil.ICON_LAYER_V2));
272
273            // conversion
274            // convertToStackButton = new IcyCommandButton(RoiActions.convertToStackAction);
275            // convertToMaskButton = new IcyCommandButton(RoiActions.convertToMaskAction);
276            // convertToShapeButton = new IcyCommandButton(RoiActions.convertToShapeAction);
277            // addCommandButton(convertToStackButton, RibbonElementPriority.MEDIUM);
278            // addCommandButton(convertToMaskButton, RibbonElementPriority.MEDIUM);
279            // addCommandButton(convertToShapeButton, RibbonElementPriority.MEDIUM);
280
281            convertToStackButton = new IcyButton(RoiActions.convertToStackAction);
282            convertToStackButton.setHorizontalAlignment(SwingConstants.LEADING);
283            convertToStackButton.setFlat(true);
284            convertToMaskButton = new IcyButton(RoiActions.convertToMaskAction);
285            convertToMaskButton.setHorizontalAlignment(SwingConstants.LEADING);
286            convertToMaskButton.setFlat(true);
287            convertToShapeButton = new IcyButton(RoiActions.convertToShapeAction);
288            convertToShapeButton.setHorizontalAlignment(SwingConstants.LEADING);
289            convertToShapeButton.setFlat(true);
290            convertToEllipseButton = new IcyButton(RoiActions.convertToEllipseAction);
291            convertToEllipseButton.setHorizontalAlignment(SwingConstants.LEADING);
292            convertToEllipseButton.setFlat(true);
293            convertToRectangleButton = new IcyButton(RoiActions.convertToRectangleAction);
294            convertToRectangleButton.setHorizontalAlignment(SwingConstants.LEADING);
295            convertToRectangleButton.setFlat(true);
296            radiusField = new NumberTextField();
297            radiusField.setHorizontalAlignment(SwingConstants.CENTER);
298            radiusField.setToolTipText("Radius for Circle/Rectangle conversion");
299            radiusField.setNumericValue(1);
300
301            JRibbonComponent comp;
302
303            comp = new JRibbonComponent(convertToStackButton);
304            comp.setResizingAware(true);
305            comp.setHorizontalAlignment(HorizontalAlignment.FILL);
306            addRibbonComponent(comp);
307            comp = new JRibbonComponent(convertToMaskButton);
308            comp.setResizingAware(true);
309            comp.setHorizontalAlignment(HorizontalAlignment.FILL);
310            addRibbonComponent(comp);
311            comp = new JRibbonComponent(convertToShapeButton);
312            comp.setResizingAware(true);
313            comp.setHorizontalAlignment(HorizontalAlignment.FILL);
314            addRibbonComponent(comp);
315
316            comp = new JRibbonComponent(convertToEllipseButton);
317            comp.setResizingAware(true);
318            comp.setHorizontalAlignment(HorizontalAlignment.FILL);
319            addRibbonComponent(comp);
320            comp = new JRibbonComponent(convertToRectangleButton);
321            comp.setResizingAware(true);
322            comp.setHorizontalAlignment(HorizontalAlignment.FILL);
323            addRibbonComponent(comp);
324            comp = new JRibbonComponent(radiusField);
325            comp.setResizingAware(true);
326            comp.setHorizontalAlignment(HorizontalAlignment.FILL);
327            addRibbonComponent(comp);
328
329            setToolTipText("Conversion tools for ROI");
330
331            // better to do that to fix the ending gap for band containing wrapped components
332            ((BasicBandControlPanelUI) getControlPanel().getUI()).setLayoutGap(0);
333            RibbonUtil.setRestrictiveResizePolicies(this, 0);
334            updateButtonsState();
335        }
336
337        public double getRadius()
338        {
339            return Math.max(0, radiusField.getNumericValue());
340        }
341
342        public void updateButtonsState()
343        {
344            boolean convertEllipseEnable = false;
345            boolean convertRectangleEnable = false;
346            boolean convertStackEnable = false;
347            boolean convertMaskEnable = false;
348            boolean convertShapeEnable = false;
349            final Sequence seq = Icy.getMainInterface().getActiveSequence();
350
351            if (seq != null)
352            {
353                final List<ROI> selectedRois = seq.getSelectedROIs();
354
355                for (ROI roi : selectedRois)
356                {
357                    if (roi instanceof ROI2D)
358                    {
359                        convertStackEnable = true;
360                        if (!(roi instanceof ROI2DShape))
361                            convertShapeEnable = true;
362                    }
363                    if (!((roi instanceof ROI2DArea) || (roi instanceof ROI3DArea) || (roi instanceof ROI4DArea)
364                            || (roi instanceof ROI5DArea)))
365                        convertMaskEnable = true;
366                    if (roi instanceof ROI3D)
367                    {
368                        if (roi instanceof ROI3DArea)
369                            convertShapeEnable = true;
370                    }
371                    convertEllipseEnable = true;
372                    convertRectangleEnable = true;
373                }
374            }
375
376            convertToEllipseButton.setEnabled(convertEllipseEnable);
377            convertToRectangleButton.setEnabled(convertRectangleEnable);
378            radiusField.setEnabled(convertEllipseEnable || convertRectangleEnable);
379            convertToStackButton.setEnabled(convertStackEnable);
380            convertToMaskButton.setEnabled(convertMaskEnable);
381            convertToShapeButton.setEnabled(convertShapeEnable);
382        }
383    }
384
385    static class ROIBooleanOpBand extends JRibbonBand
386    {
387        public static final String BAND_NAME = "Boolean Op";
388
389        final IcyCommandButton booleanUnionButton;
390        final IcyCommandButton booleanIntersectionButton;
391        final IcyCommandButton booleanOthersButton;
392        final IcyCommandMenuButton booleanInversionButton;
393        final IcyCommandMenuButton booleanExclusiveUnionButton;
394        final IcyCommandMenuButton booleanSubtractionButton;
395
396        public ROIBooleanOpBand()
397        {
398            super(BAND_NAME, new IcyIcon(ResourceUtil.ICON_ROI_OR));
399
400            booleanUnionButton = new IcyCommandButton(RoiActions.boolOrAction);
401            booleanIntersectionButton = new IcyCommandButton(RoiActions.boolAndAction);
402            booleanInversionButton = new IcyCommandMenuButton(RoiActions.boolNotAction);
403            booleanExclusiveUnionButton = new IcyCommandMenuButton(RoiActions.boolXorAction);
404            booleanSubtractionButton = new IcyCommandMenuButton(RoiActions.boolSubtractAction);
405
406            booleanOthersButton = new IcyCommandButton("Other operation");
407            booleanOthersButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
408            booleanOthersButton.setPopupCallback(new PopupPanelCallback()
409            {
410                @Override
411                public JPopupPanel getPopupPanel(JCommandButton arg0)
412                {
413                    final JCommandPopupMenu result = new JCommandPopupMenu();
414
415                    result.addMenuButton(booleanInversionButton);
416                    result.addMenuButton(booleanExclusiveUnionButton);
417                    result.addMenuButton(booleanSubtractionButton);
418
419                    return result;
420                }
421            });
422            booleanOthersButton.setEnabled(false);
423
424            addCommandButton(booleanUnionButton, RibbonElementPriority.MEDIUM);
425            addCommandButton(booleanIntersectionButton, RibbonElementPriority.MEDIUM);
426            addCommandButton(booleanOthersButton, RibbonElementPriority.MEDIUM);
427
428            setToolTipText("Boolean operation for ROI");
429            RibbonUtil.setRestrictiveResizePolicies(this);
430            updateButtonsState();
431        }
432
433        public void updateButtonsState()
434        {
435            boolean singleOp = false;
436            boolean boolOp = false;
437            final Sequence seq = Icy.getMainInterface().getActiveSequence();
438
439            if (seq != null)
440            {
441                final List<ROI> selectedRois = seq.getSelectedROIs();
442
443                singleOp = !selectedRois.isEmpty();
444                boolOp = selectedRois.size() > 1;
445            }
446
447            booleanUnionButton.setEnabled(boolOp);
448            booleanIntersectionButton.setEnabled(boolOp);
449            booleanInversionButton.setEnabled(singleOp);
450            booleanExclusiveUnionButton.setEnabled(boolOp);
451            booleanSubtractionButton.setEnabled(boolOp);
452            booleanOthersButton.setEnabled(singleOp || boolOp);
453        }
454    }
455
456    static class ROISeparationBand extends JRibbonBand
457    {
458        public static final String BAND_NAME = "Separation";
459
460        final IcyCommandButton separateObjectsButton;
461        final IcyCommandToggleButton cutButton;
462        final IcyCommandButton splitButton;
463
464        public ROISeparationBand()
465        {
466            super(BAND_NAME, new IcyIcon(ResourceUtil.ICON_ROI_COMP));
467
468            // conversion
469            separateObjectsButton = new IcyCommandButton(RoiActions.separateObjectsAction);
470            cutButton = PluginCommandButton
471                    .createToggleButton(PluginLoader.getPlugin(ROILineCutterPlugin.class.getName()), false, true);
472            splitButton = new IcyCommandButton(RoiActions.autoSplitAction);
473            addCommandButton(separateObjectsButton, RibbonElementPriority.MEDIUM);
474            addCommandButton(cutButton, RibbonElementPriority.MEDIUM);
475            // addCommandButton(splitButton, RibbonElementPriority.MEDIUM);
476
477            cutButton.setEnabled(false);
478
479            setToolTipText("Separation tools for ROI");
480            RibbonUtil.setRestrictiveResizePolicies(this);
481        }
482
483        public void updateButtonsState()
484        {
485            boolean separateObjEnable = false;
486            boolean cutEnable = false;
487            boolean splitEnable = false;
488            final Sequence seq = Icy.getMainInterface().getActiveSequence();
489
490            if (seq != null)
491            {
492                final List<ROI> selectedRois = seq.getSelectedROIs();
493
494                cutEnable = seq.hasROI();
495                separateObjEnable = !selectedRois.isEmpty();
496                splitEnable = !selectedRois.isEmpty();
497            }
498
499            separateObjectsButton.setEnabled(separateObjEnable);
500            cutButton.setEnabled(cutEnable);
501            splitButton.setEnabled(splitEnable);
502        }
503    }
504
505    // static class ROIScaleBand extends JRibbonBand
506    // {
507    // public static final String BAND_NAME = "Resize";
508    //
509    // final IcyCommandButton upscaleButton;
510    // final IcyCommandMenuButton upscale2dButton;
511    // final IcyCommandButton downscaleButton;
512    // final IcyCommandMenuButton downscale2dButton;
513    //
514    // public ROIScaleBand()
515    // {
516    // super(BAND_NAME, new IcyIcon(ResourceUtil.ICON_RESIZE_2));
517    //
518    // // up scale
519    // upscale2dButton = new IcyCommandMenuButton(RoiActions.upscale2dAction);
520    // upscaleButton = new IcyCommandButton(RoiActions.upscaleAction);
521    // upscaleButton.setCommandButtonKind(CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION);
522    // upscaleButton.setPopupCallback(new PopupPanelCallback()
523    // {
524    // @Override
525    // public JPopupPanel getPopupPanel(JCommandButton arg0)
526    // {
527    // final JCommandPopupMenu result = new JCommandPopupMenu();
528    //
529    // result.addMenuButton(upscale2dButton);
530    //
531    // return result;
532    // }
533    // });
534    //
535    // // down scale
536    // downscale2dButton = new IcyCommandMenuButton(RoiActions.downscale2dAction);
537    // downscaleButton = new IcyCommandButton(RoiActions.downscaleAction);
538    // downscaleButton.setCommandButtonKind(CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION);
539    // downscaleButton.setPopupCallback(new PopupPanelCallback()
540    // {
541    // @Override
542    // public JPopupPanel getPopupPanel(JCommandButton arg0)
543    // {
544    // final JCommandPopupMenu result = new JCommandPopupMenu();
545    //
546    // result.addMenuButton(downscale2dButton);
547    //
548    // return result;
549    // }
550    // });
551    //
552    // addCommandButton(upscaleButton, RibbonElementPriority.MEDIUM);
553    // addCommandButton(downscaleButton, RibbonElementPriority.MEDIUM);
554    //
555    // setToolTipText("Resize tools for ROI");
556    // RibbonUtil.setRestrictiveResizePolicies(this);
557    // }
558    //
559    // public void updateButtonsState()
560    // {
561    // boolean resizeEnable = false;
562    // final Sequence seq = Icy.getMainInterface().getActiveSequence();
563    //
564    // if (seq != null)
565    // {
566    // final List<ROI> selectedRois = seq.getSelectedROIs();
567    //
568    // resizeEnable = !selectedRois.isEmpty();
569    // }
570    //
571    // upscaleButton.setEnabled(resizeEnable);
572    // upscale2dButton.setEnabled(resizeEnable);
573    // downscaleButton.setEnabled(resizeEnable);
574    // downscale2dButton.setEnabled(resizeEnable);
575    // }
576    // }
577
578    // static class ROIMorphoBand extends JRibbonBand
579    // {
580    // public static final String BAND_NAME = "Morphology";
581    //
582    // final IcyCommandButton erodeButton;
583    // final IcyCommandButton dilateButton;
584    // final IcyCommandButton fillHoledilateButton;
585    // final IcyCommandButton otherButton;
586    //
587    // public ROIMorphoBand()
588    // {
589    // super(BAND_NAME, new IcyIcon(ResourceUtil.ICON_LAYER_V2));
590    //
591    // booleanUnionButton = new IcyCommandButton(RoiActions.boolOrAction);
592    // booleanIntersectionButton = new IcyCommandButton(RoiActions.boolAndAction);
593    // booleanInversionButton = new IcyCommandMenuButton(RoiActions.boolNotAction);
594    // booleanExclusiveUnionButton = new IcyCommandMenuButton(RoiActions.boolXorAction);
595    // booleanSubtractionButton = new IcyCommandMenuButton(RoiActions.boolSubtractAction);
596    //
597    // booleanOthersButton = new IcyCommandButton("Other operation");
598    // booleanOthersButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
599    // booleanOthersButton.setPopupCallback(new PopupPanelCallback()
600    // {
601    // @Override
602    // public JPopupPanel getPopupPanel(JCommandButton arg0)
603    // {
604    // final JCommandPopupMenu result = new JCommandPopupMenu();
605    //
606    // result.addMenuButton(booleanInversionButton);
607    // result.addMenuButton(booleanExclusiveUnionButton);
608    // result.addMenuButton(booleanSubtractionButton);
609    //
610    // return result;
611    // }
612    // });
613    // booleanOthersButton.setEnabled(false);
614    //
615    // addCommandButton(booleanUnionButton, RibbonElementPriority.MEDIUM);
616    // addCommandButton(booleanIntersectionButton, RibbonElementPriority.MEDIUM);
617    // addCommandButton(booleanOthersButton, RibbonElementPriority.MEDIUM);
618    //
619    // setToolTipText("Morphological operator for ROI");
620    // RibbonUtil.setRestrictiveResizePolicies(this);
621    // }
622    //
623    // public void updateButtonsState()
624    // {
625    // boolean separateObjEnable = false;
626    // boolean cutEnable = false;
627    // boolean splitEnable = false;
628    // final Sequence seq = Icy.getMainInterface().getActiveSequence();
629    //
630    // if (seq != null)
631    // {
632    // final List<ROI> selectedRois = seq.getSelectedROIs();
633    //
634    // cutEnable = seq.hasROI();
635    // separateObjEnable = !selectedRois.isEmpty();
636    // splitEnable = !selectedRois.isEmpty();
637    // }
638    //
639    // separateObjectsButton.setEnabled(separateObjEnable);
640    // cutButton.setEnabled(cutEnable);
641    // splitButton.setEnabled(splitEnable);
642    // }
643    // }
644
645    static class ROIIOBand extends JRibbonBand
646    {
647        public static final String BAND_NAME = "File";
648
649        final IcyCommandButton loadFromXMLButton;
650        final IcyCommandButton saveToXMLButton;
651        final IcyCommandButton exportToXLSButton;
652
653        public ROIIOBand()
654        {
655            super(BAND_NAME, new IcyIcon(ResourceUtil.ICON_OPEN));
656
657            // conversion
658            loadFromXMLButton = new IcyCommandButton(RoiActions.loadAction);
659            saveToXMLButton = new IcyCommandButton(RoiActions.saveAction);
660            exportToXLSButton = new IcyCommandButton(RoiActions.xlsExportAction);
661            addCommandButton(loadFromXMLButton, RibbonElementPriority.MEDIUM);
662            addCommandButton(saveToXMLButton, RibbonElementPriority.MEDIUM);
663            addCommandButton(exportToXLSButton, RibbonElementPriority.MEDIUM);
664
665            setToolTipText("File operations for ROI");
666            RibbonUtil.setRestrictiveResizePolicies(this);
667        }
668
669        public void updateButtonsState()
670        {
671            final Sequence seq = Icy.getMainInterface().getActiveSequence();
672            final RoisPanel roisPanel = Icy.getMainInterface().getRoisPanel();
673
674            loadFromXMLButton.setEnabled(seq != null);
675            saveToXMLButton.setEnabled((seq != null) && seq.hasSelectedROI());
676            exportToXLSButton.setEnabled((roisPanel != null) && (roisPanel.getVisibleRois().size() > 0));
677        }
678    }
679
680    static class ROIFillBand extends JRibbonBand
681    {
682        public static final String BAND_NAME = "Fill operation";
683
684        final NumberTextField fillValueField;
685        // final IcyButton fillImage;
686        final IcyButton fillInterior;
687        final IcyButton fillExterior;
688
689        public ROIFillBand()
690        {
691            super(BAND_NAME, new IcyIcon("document"));
692
693            fillInterior = new IcyButton(RoiActions.fillInteriorAction);
694            fillInterior.setHorizontalAlignment(SwingConstants.LEADING);
695            fillInterior.setFlat(true);
696            fillExterior = new IcyButton(RoiActions.fillExteriorAction);
697            fillExterior.setHorizontalAlignment(SwingConstants.LEADING);
698            fillExterior.setFlat(true);
699            fillValueField = new NumberTextField();
700            fillValueField.setHorizontalAlignment(SwingConstants.CENTER);
701            fillValueField.setToolTipText("Value used for filling");
702            fillValueField.setNumericValue(0);
703
704            JRibbonComponent comp;
705
706            comp = new JRibbonComponent(fillInterior);
707            comp.setResizingAware(true);
708            comp.setHorizontalAlignment(HorizontalAlignment.FILL);
709            addRibbonComponent(comp);
710            comp = new JRibbonComponent(fillExterior);
711            comp.setResizingAware(true);
712            comp.setHorizontalAlignment(HorizontalAlignment.FILL);
713            addRibbonComponent(comp);
714            comp = new JRibbonComponent(fillValueField);
715            comp.setResizingAware(true);
716            comp.setHorizontalAlignment(HorizontalAlignment.FILL);
717            addRibbonComponent(comp);
718
719            setToolTipText("Fill operation for ROI");
720
721            // better to do that to fix the ending gap for band containing wrapped components
722            ((BasicBandControlPanelUI) getControlPanel().getUI()).setLayoutGap(0);
723            RibbonUtil.setRestrictiveResizePolicies(this, 0);
724            updateButtonsState();
725        }
726
727        public double getFillValue()
728        {
729            double value = fillValueField.getNumericValue();
730            final Sequence sequence = Icy.getMainInterface().getActiveSequence();
731
732            if ((sequence != null) && (!sequence.isFloatDataType()))
733            {
734                final double bounds[] = sequence.getDataType_().getDefaultBounds();
735
736                // limit value to data type bounds
737                if (value < bounds[0])
738                    value = bounds[0];
739                if (value > bounds[1])
740                    value = bounds[1];
741            }
742
743            // set value back if incorrect
744            fillValueField.setNumericValue(value);
745
746            return value;
747        }
748
749        void updateButtonsState()
750        {
751            final Sequence seq = Icy.getMainInterface().getActiveSequence();
752            final boolean enabled = (seq != null) && !seq.isEmpty() && seq.hasSelectedROI();
753
754            fillValueField.setEnabled(enabled);
755            fillInterior.setEnabled(enabled);
756            fillExterior.setEnabled(enabled);
757        }
758    }
759
760    final ROI2DBand roi2dBand;
761    final ROI3DBand roi3dBand;
762    final ROIExtBand roiExtBand;
763    final ROIConversionBand roiConversionBand;
764    final ROISeparationBand roiSeparationBand;
765    // final ROIScaleBand roiScaleBand;
766    final ROIBooleanOpBand roiBooleanOpBand;
767    final ROIFillBand roiFillBand;
768    final ROIIOBand roiIOBand;
769
770    final CommandToggleButtonGroup buttonGroup;
771    final ActionListener buttonActionListener;
772
773    /**
774     * List of listeners
775     */
776    private final EventListenerList listeners;
777
778    private Runnable buttonUpdater;
779
780    public ROITask()
781    {
782        super(NAME, new ROI2DBand(), new ROI3DBand(), new ROIExtBand(), new ROIConversionBand(),
783                new ROISeparationBand(), new ROIBooleanOpBand(), new ROIFillBand(), new ROIIOBand());
784        // super(NAME,, new ROIRibbonBand());
785
786        setResizeSequencingPolicy(new CoreRibbonResizeSequencingPolicies.CollapseFromLast(this));
787
788        // get band
789        roi2dBand = (ROI2DBand) RibbonUtil.getBand(this, ROI2DBand.BAND_NAME);
790        roi3dBand = (ROI3DBand) RibbonUtil.getBand(this, ROI3DBand.BAND_NAME);
791        roiExtBand = (ROIExtBand) RibbonUtil.getBand(this, ROIExtBand.BAND_NAME);
792        roiConversionBand = (ROIConversionBand) RibbonUtil.getBand(this, ROIConversionBand.BAND_NAME);
793        roiSeparationBand = (ROISeparationBand) RibbonUtil.getBand(this, ROISeparationBand.BAND_NAME);
794        // roiScaleBand = (ROIScaleBand) RibbonUtil.getBand(this, ROIScaleBand.BAND_NAME);
795        roiBooleanOpBand = (ROIBooleanOpBand) RibbonUtil.getBand(this, ROIBooleanOpBand.BAND_NAME);
796        roiFillBand = (ROIFillBand) RibbonUtil.getBand(this, ROIFillBand.BAND_NAME);
797        roiIOBand = (ROIIOBand) RibbonUtil.getBand(this, ROIIOBand.BAND_NAME);
798
799        // create button state updater
800        buttonUpdater = new Runnable()
801        {
802            @Override
803            public void run()
804            {
805                // sleep a bit
806                ThreadUtil.sleep(1);
807
808                ThreadUtil.invokeNow(new Runnable()
809                {
810                    @Override
811                    public void run()
812                    {
813                        roi2dBand.updateButtonsState();
814                        roi3dBand.updateButtonsState();
815                        roiExtBand.updateButtonsState();
816                        roiConversionBand.updateButtonsState();
817                        roiSeparationBand.updateButtonsState();
818                        // roiScaleBand.updateButtonsState();
819                        roiBooleanOpBand.updateButtonsState();
820                        roiFillBand.updateButtonsState();
821                        roiIOBand.updateButtonsState();
822                    }
823                });
824            }
825        };
826
827        listeners = new EventListenerList();
828
829        buttonActionListener = new ActionListener()
830        {
831            @Override
832            public void actionPerformed(ActionEvent e)
833            {
834                setSelectedButton((IcyCommandToggleButton) e.getSource(), true);
835            }
836        };
837
838        // create button group
839        buttonGroup = new CommandToggleButtonGroup();
840        buttonGroup.setAllowsClearingSelection(true);
841
842        // add action listener here
843        for (AbstractCommandButton button : RibbonUtil.getButtons(roi2dBand))
844            button.addActionListener(buttonActionListener);
845        for (AbstractCommandButton button : RibbonUtil.getButtons(roi3dBand))
846            button.addActionListener(buttonActionListener);
847        for (AbstractCommandButton button : RibbonUtil.getButtons(roiExtBand))
848            button.addActionListener(buttonActionListener);
849        // cut button act as a selector
850        roiSeparationBand.cutButton.addActionListener(buttonActionListener);
851
852        for (AbstractCommandButton button : RibbonUtil.getButtons(roi2dBand))
853            buttonGroup.add((IcyCommandToggleButton) button);
854        for (AbstractCommandButton button : RibbonUtil.getButtons(roi3dBand))
855            buttonGroup.add((IcyCommandToggleButton) button);
856        for (AbstractCommandButton button : RibbonUtil.getButtons(roiExtBand))
857            buttonGroup.add((IcyCommandToggleButton) button);
858        // cut button which act as a selector
859        buttonGroup.add(roiSeparationBand.cutButton);
860
861        PluginLoader.addListener(this);
862    }
863
864    protected void refreshROIButtons()
865    {
866        // refresh 2D ROI buttons
867        roi2dBand.setHidden(setROIFromPlugins(roi2dBand.pluginButtons, roi2dBand, ROI2DBand.getROIPlugins(),
868                buttonGroup, buttonActionListener));
869        // refresh 3D ROI buttons
870        roi3dBand.setHidden(setROIFromPlugins(roi3dBand.pluginButtons, roi3dBand, ROI3DBand.getROIPlugins(),
871                buttonGroup, buttonActionListener));
872        // refresh external ROI buttons
873        roiExtBand.setHidden(setROIFromPlugins(roiExtBand.pluginButtons, roiExtBand, ROIExtBand.getROIPlugins(),
874                buttonGroup, buttonActionListener));
875
876        roi2dBand.updateButtonsState();
877        roi3dBand.updateButtonsState();
878        roiExtBand.updateButtonsState();
879    }
880
881    protected static boolean setROIFromPlugins(List<IcyCommandToggleButton> pluginButtons, JRibbonBand band,
882            List<PluginDescriptor> roiPlugins, CommandToggleButtonGroup buttonGroup, ActionListener al)
883    {
884        // remove previous plugin buttons
885        for (IcyCommandToggleButton button : pluginButtons)
886        {
887            if (al != null)
888                button.removeActionListener(al);
889            band.removeCommandButton(button);
890            if (buttonGroup != null)
891                buttonGroup.remove(button);
892        }
893        pluginButtons.clear();
894
895        for (PluginDescriptor plugin : roiPlugins)
896        {
897            final IcyCommandToggleButton button = PluginCommandButton.createToggleButton(plugin, false,
898                    plugin.isKernelPlugin());
899
900            if (plugin.getClassName().equals(ROI2DAreaPlugin.class.getName()))
901                band.addCommandButton(button, RibbonElementPriority.TOP);
902            else
903                band.addCommandButton(button, RibbonElementPriority.MEDIUM);
904
905            if (al != null)
906                button.addActionListener(al);
907            if (buttonGroup != null)
908                buttonGroup.add(button);
909
910            pluginButtons.add(button);
911        }
912
913        return pluginButtons.isEmpty();
914    }
915
916    protected IcyCommandToggleButton getButtonFromName(String name)
917    {
918        if (StringUtil.isEmpty(name))
919            return null;
920
921        for (AbstractCommandButton button : RibbonUtil.getButtons(roi2dBand))
922            if (name.equals(button.getName()))
923                return (IcyCommandToggleButton) button;
924        for (AbstractCommandButton button : RibbonUtil.getButtons(roi3dBand))
925            if (name.equals(button.getName()))
926                return (IcyCommandToggleButton) button;
927        for (AbstractCommandButton button : RibbonUtil.getButtons(roiExtBand))
928            if (name.equals(button.getName()))
929                return (IcyCommandToggleButton) button;
930
931        return null;
932    }
933
934    /**
935     * Returns true if current selected tool is ROI type tool.
936     */
937    public boolean isROITool()
938    {
939        // currently we only have ROI tool
940        // so as soon selected is not null it is ROI tool
941        return getSelected() != null;
942    }
943
944    /**
945     * Returns the current selected button (can be <code>null</code>).
946     */
947    protected JCommandToggleButton getSelectedButton()
948    {
949        return buttonGroup.getSelected();
950    }
951
952    /**
953     * Sets the current selected button (can be <code>null</code>).
954     */
955    protected void setSelectedButton(JCommandToggleButton button, boolean force)
956    {
957        if (force || (getSelectedButton() != button))
958        {
959            // select the button
960            if (button != null)
961                buttonGroup.setSelected(button, true);
962            else
963                buttonGroup.clearSelection();
964
965            // notify tool change
966            toolChanged(getSelected());
967        }
968    }
969
970    /**
971     * Returns the current selected tool.<br>
972     * It can be null if no tool is currently selected.
973     */
974    public String getSelected()
975    {
976        final JCommandToggleButton button = getSelectedButton();
977
978        if (button != null)
979            return button.getName();
980
981        return null;
982    }
983
984    /**
985     * Sets the current selected tool.<br>
986     * If <i>toolName</i> is a invalid tool name or <code>null</code> then no tool is selected.
987     */
988    public void setSelected(String value)
989    {
990        setSelectedButton(getButtonFromName(value), false);
991    }
992
993    public void toolChanged(String toolName)
994    {
995        fireChangedEvent(toolName);
996    }
997
998    public double getRadius()
999    {
1000        return roiConversionBand.getRadius();
1001    }
1002
1003    public double getFillValue()
1004    {
1005        return roiFillBand.getFillValue();
1006    }
1007
1008    /**
1009     * Add a listener
1010     * 
1011     * @param listener
1012     */
1013    public void addListener(ROITaskListener listener)
1014    {
1015        listeners.add(ROITaskListener.class, listener);
1016    }
1017
1018    /**
1019     * Remove a listener
1020     * 
1021     * @param listener
1022     */
1023    public void removeListener(ROITaskListener listener)
1024    {
1025        listeners.remove(ROITaskListener.class, listener);
1026    }
1027
1028    /**
1029     * @param toolName
1030     */
1031    void fireChangedEvent(String toolName)
1032    {
1033        for (ROITaskListener listener : listeners.getListeners(ROITaskListener.class))
1034            listener.toolChanged(toolName);
1035    }
1036
1037    /**
1038     * call this method on sequence activation change
1039     */
1040    public void onSequenceActivationChange()
1041    {
1042        ThreadUtil.runSingle(buttonUpdater);
1043    }
1044
1045    /**
1046     * call this method on sequence ROI change
1047     */
1048    public void onSequenceChange()
1049    {
1050        ThreadUtil.runSingle(buttonUpdater);
1051    }
1052
1053    @Override
1054    public void pluginLoaderChanged(PluginLoaderEvent e)
1055    {
1056        ThreadUtil.invokeLater(new Runnable()
1057        {
1058            @Override
1059            public void run()
1060            {
1061                refreshROIButtons();
1062            }
1063        });
1064    }
1065}