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 icy.action.GeneralActions;
022import icy.action.PreferencesActions;
023import icy.action.WindowActions;
024import icy.gui.component.button.IcyCommandButton;
025import icy.gui.component.button.IcyCommandMenuButton;
026import icy.gui.component.button.IcyCommandToggleButton;
027import icy.gui.component.button.IcyCommandToggleMenuButton;
028import icy.gui.frame.IcyFrame;
029import icy.gui.frame.progress.TaskFrame;
030import icy.gui.main.ActiveSequenceListener;
031import icy.gui.main.MainFrame;
032import icy.gui.menu.search.SearchBar;
033import icy.gui.plugin.PluginCommandButton;
034import icy.gui.util.LookAndFeelUtil;
035import icy.gui.util.RibbonUtil;
036import icy.gui.viewer.Viewer;
037import icy.imagej.ImageJWrapper;
038import icy.main.Icy;
039import icy.plugin.PluginDescriptor;
040import icy.plugin.PluginDescriptor.PluginClassNameSorter;
041import icy.plugin.PluginLoader;
042import icy.plugin.PluginLoader.PluginLoaderEvent;
043import icy.plugin.PluginLoader.PluginLoaderListener;
044import icy.preferences.GeneralPreferences;
045import icy.preferences.WorkspaceLocalPreferences;
046import icy.resource.ResourceUtil;
047import icy.resource.icon.IcyIcon;
048import icy.sequence.Sequence;
049import icy.sequence.SequenceEvent;
050import icy.sequence.SequenceEvent.SequenceEventSourceType;
051import icy.system.thread.ThreadUtil;
052import icy.workspace.Workspace;
053import icy.workspace.Workspace.TaskDefinition;
054import icy.workspace.Workspace.TaskDefinition.BandDefinition;
055import icy.workspace.Workspace.TaskDefinition.BandDefinition.ItemDefinition;
056import icy.workspace.WorkspaceInstaller;
057import icy.workspace.WorkspaceLoader;
058
059import java.awt.Component;
060import java.awt.event.ActionEvent;
061import java.awt.event.ActionListener;
062import java.beans.PropertyChangeEvent;
063import java.beans.PropertyChangeListener;
064import java.util.ArrayList;
065import java.util.Collections;
066import java.util.List;
067
068import javax.swing.JMenu;
069import javax.swing.JMenuItem;
070import javax.swing.JPopupMenu;
071import javax.swing.JSeparator;
072import javax.swing.SwingConstants;
073
074import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
075import org.pushingpixels.flamingo.api.common.CommandToggleButtonGroup;
076import org.pushingpixels.flamingo.api.common.JCommandButton;
077import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
078import org.pushingpixels.flamingo.api.common.RichTooltip;
079import org.pushingpixels.flamingo.api.common.popup.JCommandPopupMenu;
080import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
081import org.pushingpixels.flamingo.api.common.popup.PopupPanelCallback;
082import org.pushingpixels.flamingo.api.ribbon.JRibbon;
083import org.pushingpixels.flamingo.api.ribbon.JRibbonBand;
084import org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority;
085import org.pushingpixels.flamingo.api.ribbon.RibbonTask;
086import org.pushingpixels.flamingo.api.ribbon.resize.CoreRibbonResizeSequencingPolicies;
087
088/**
089 * This class is used to separate ribbon construction from the ribbon frame
090 * 
091 * @author Stephane
092 */
093public class MainRibbon implements PluginLoaderListener, ActiveSequenceListener
094{
095    /**
096     * TASK / BAND NAMES
097     */
098    public static final String TASK_PLUGINS = "Plugins";
099    public static final String BAND_SETUP = "Setup";
100    public static final String BAND_NEW = "New";
101    public static final String BAND_OTHERS = "Others";
102
103    /**
104     * loaded workspaces
105     */
106    private final ArrayList<Workspace> workspaces;
107    private Workspace systemWorkspace;
108
109    /**
110     * Search bar object
111     */
112    SearchBar searchBar;
113
114    /**
115     * internals
116     */
117    BandDefinition setupPluginsBandDef;
118    BandDefinition newPluginsBandDef;
119    BandDefinition othersPluginsBandDef;
120
121    private final JRibbon ribbon;
122    private final ApplicationMenu applicationMenu;
123    private final SequenceOperationTask sequenceOperationTask;
124    // use ToolRibbonTask constructor to preserve backward compatibility
125    private final ToolRibbonTask roiTask;
126    private final ImageJTask ijTask;
127    private final JRibbonBand setupPluginsBand;
128    private final JRibbonBand newPluginsBand;
129    final JMenu othersPluginsMenu;
130
131    CommandToggleButtonGroup multiWindowGroup;
132    IcyCommandToggleButton multiWindowButton;
133
134    /**
135     * @param ribbon
136     */
137    public MainRibbon(JRibbon ribbon)
138    {
139        super();
140
141        this.ribbon = ribbon;
142
143        workspaces = new ArrayList<Workspace>();
144        othersPluginsMenu = new JMenu("Plugins");
145
146        // APPLICATION MENU & MISC
147
148        applicationMenu = new ApplicationMenu();
149        ribbon.setApplicationMenu(applicationMenu);
150        final RichTooltip toolTip = new RichTooltip("ICY Application menu",
151                "Load, close and save Sequence from there.");
152        ribbon.setApplicationMenuRichTooltip(toolTip);
153        // ribbon.configureHelp(new ICYResizableIcon(new IcyIcon("lightbulb"),
154        // new
155        // Help("Main_Page"));
156
157        // TASKBAR
158
159        buidlTaskBar();
160
161        // FIXED TASKS
162
163        // load image task first as tools task need all plugins loaded...
164        sequenceOperationTask = new SequenceOperationTask();
165        // use ToolRibbonTask constructor to preserve backward compatibility
166        roiTask = new ToolRibbonTask();
167        ijTask = new ImageJTask();
168        ribbon.addTask(sequenceOperationTask);
169        ribbon.addTask(roiTask);
170        ribbon.addTask(ijTask);
171
172        // WORKSPACES
173
174        // load workspace from files
175        loadWorkspaces();
176
177        // store system band definition
178        setupPluginsBandDef = systemWorkspace.findBand(TASK_PLUGINS, BAND_SETUP);
179        newPluginsBandDef = systemWorkspace.findBand(TASK_PLUGINS, BAND_NEW);
180        othersPluginsBandDef = systemWorkspace.findBand(TASK_PLUGINS, BAND_OTHERS);
181
182        // build workspaces menu
183        buildWorkspaces();
184
185        // store system band
186        final RibbonTask pluginTask = RibbonUtil.getTask(ribbon, TASK_PLUGINS);
187        setupPluginsBand = RibbonUtil.getBand(pluginTask, BAND_SETUP);
188        newPluginsBand = RibbonUtil.getBand(pluginTask, BAND_NEW);
189        // othersPluginsBand = RibbonUtil.getBand(pluginTask, BAND_OTHERS);
190
191        // build plugin setup band
192        buildSetupPluginBand();
193
194        // save workspaces back so removed stuff are cleaned
195        // saveWorkspaces();
196
197        PluginLoader.addListener(this);
198        Icy.getMainInterface().addActiveSequenceListener(this);
199    }
200
201    // some stuff which need to be initialized after ribbon creation
202    public void init()
203    {
204        ijTask.init();
205
206        final MainFrame mainFrame = Icy.getMainInterface().getMainFrame();
207        mainFrame.addPropertyChangeListener(MainFrame.PROPERTY_DETACHEDMODE, new PropertyChangeListener()
208        {
209            @Override
210            public void propertyChange(PropertyChangeEvent evt)
211            {
212                multiWindowGroup.setSelected(multiWindowButton, Icy.getMainInterface().isDetachedMode());
213            }
214        });
215    }
216
217    public ROITask getROIRibbonTask()
218    {
219        return roiTask;
220    }
221
222    /**
223     * @deprecated Use {@link #getROIRibbonTask()} instead
224     */
225    @Deprecated
226    public ToolRibbonTask getToolRibbon()
227    {
228        return roiTask;
229    }
230
231    public SequenceOperationTask getSequenceOperationTask()
232    {
233        return sequenceOperationTask;
234    }
235
236    public ImageJTask getImageJTask()
237    {
238        return ijTask;
239    }
240
241    public ImageJWrapper getImageJ()
242    {
243        return ijTask.getImageJ();
244    }
245
246    public SearchBar getSearchBar()
247    {
248        return searchBar;
249    }
250
251    private void loadWorkspaces()
252    {
253        ArrayList<String> workspacesName;
254
255        // load names from preference
256        workspacesName = WorkspaceLocalPreferences.getActivesWorkspace();
257
258        // wait for workspace loader is ready
259        WorkspaceLoader.waitWhileLoading();
260
261        // load workspaces
262        workspaces.clear();
263        for (String name : workspacesName)
264        {
265            // get workspace from loader
266            final Workspace ws = WorkspaceLoader.getWorkspace(name);
267
268            // add to active workspace list if not empty
269            if (ws == null)
270                System.err.println("Workspace " + name + " not found !");
271            else if (isInConflict(ws, true))
272                System.err.println("Workspace '" + name + "' is discarded (conflict detected)");
273            else
274                workspaces.add(ws);
275        }
276
277        // clean up invalid entries
278        workspacesName.clear();
279        for (Workspace ws : workspaces)
280            workspacesName.add(ws.getName());
281
282        // save back cleaned names to preference
283        WorkspaceLocalPreferences.setActivesWorkspace(workspacesName);
284
285        // always add the system workspace
286        systemWorkspace = new Workspace(Workspace.WORKSPACE_SYSTEM_NAME);
287
288        // recreate system workspace manually if needed
289        systemWorkspace.setDescription("System workspace");
290        // this actually add only missing tasks and bands
291        systemWorkspace.addBand(MainRibbon.TASK_PLUGINS, MainRibbon.BAND_SETUP);
292        systemWorkspace.addBand(MainRibbon.TASK_PLUGINS, MainRibbon.BAND_NEW);
293        systemWorkspace.addBand(MainRibbon.TASK_PLUGINS, MainRibbon.BAND_OTHERS);
294
295        workspaces.add(systemWorkspace);
296    }
297
298    /**
299     * return true if specified workspace is in conflict with current actives workspace
300     */
301    private boolean isInConflict(Workspace ws, boolean showAsError)
302    {
303        for (TaskDefinition task : ws.getTasks())
304        {
305            for (BandDefinition band : task.getBands())
306            {
307                // conflict : same task and same band in 2 different workspaces
308                if (findBand(task.getName(), band.getName()) != null)
309                {
310                    if (showAsError)
311                        System.err.println("Duplicated band : " + task.getName() + "/" + band.getName());
312
313                    return true;
314                }
315            }
316        }
317
318        return false;
319    }
320
321    /**
322     * get all tasks from actives workspace
323     */
324    private List<TaskDefinition> getTasks()
325    {
326        final List<TaskDefinition> result = new ArrayList<TaskDefinition>();
327
328        for (Workspace ws : workspaces)
329            for (TaskDefinition task : ws.getTasks())
330                if (!Workspace.contains(result, task))
331                    result.add(task);
332
333        return result;
334    }
335
336    /**
337     * get all bands for a task from actives workspace
338     */
339    private List<BandDefinition> getBands(String taskName)
340    {
341        final List<BandDefinition> result = new ArrayList<BandDefinition>();
342
343        for (Workspace ws : workspaces)
344            for (TaskDefinition task : ws.getTasks())
345                if (task.getName().equals(taskName))
346                    for (BandDefinition band : task.getBands())
347                        if (!Workspace.contains(result, band))
348                            result.add(band);
349
350        return result;
351    }
352
353    // private Workspace findWorkspace(String workspaceName)
354    // {
355    // for (Workspace ws : workspaces)
356    // if (ws.getName().equals(workspaceName))
357    // return ws;
358    //
359    // return null;
360    // }
361
362    private BandDefinition findBand(String taskName, String bandName)
363    {
364        for (Workspace ws : workspaces)
365        {
366            final TaskDefinition task = ws.findTask(taskName);
367            if (task != null)
368            {
369                final BandDefinition band = task.findBand(bandName);
370                if (band != null)
371                    return band;
372            }
373        }
374
375        return null;
376    }
377
378    /**
379     * Get all items from all active workspace
380     */
381    private ArrayList<ItemDefinition> getAllItems()
382    {
383        final ArrayList<ItemDefinition> result = new ArrayList<ItemDefinition>();
384
385        // get all items from active workspaces
386        for (Workspace workspace : workspaces)
387            result.addAll(workspace.getAllItems());
388
389        return result;
390    }
391
392    ItemDefinition findItem(String className)
393    {
394        // search item in active workspaces
395        for (Workspace workspace : workspaces)
396        {
397            final ItemDefinition item = workspace.findItem(className);
398            if (item != null)
399                return item;
400        }
401
402        return null;
403    }
404
405    void addItem(String className)
406    {
407        addItem(PluginLoader.getPlugin(className));
408    }
409
410    /**
411     * Add an item for the specified plugin.
412     */
413    void addItem(PluginDescriptor plugin)
414    {
415        // check that plugin can be displayed in menu
416        if ((plugin != null) && plugin.isActionable())
417        {
418            final IcyCommandButton pluginButton = PluginCommandButton.createButton(plugin);
419
420            // add it to the new installed plugins workspace and save it
421            newPluginsBandDef.addItem(plugin.getClassName(), RibbonElementPriority.TOP);
422            systemWorkspace.save();
423            // add it to the new installed plugins band
424            newPluginsBand.addCommandButton(pluginButton, RibbonElementPriority.TOP);
425        }
426    }
427
428    void updateItem(String className)
429    {
430        updateItem(PluginLoader.getPlugin(className), findItem(className));
431    }
432
433    void updateItem(PluginDescriptor plugin, ItemDefinition item)
434    {
435        // check that plugin can be displayed in menu
436        if ((plugin != null) && plugin.isActionable())
437        {
438            if (item != null)
439            {
440                // find the corresponding button
441                final AbstractCommandButton button = RibbonUtil.findButton(
442                        RibbonUtil.getBand(RibbonUtil.getTask(ribbon, item.getTaskName()), item.getBandName()),
443                        item.getClassName());
444
445                // button found --> update it
446                if (button != null)
447                    PluginCommandButton.setButton(button, plugin);
448            }
449            else
450            {
451                final Workspace workspace = WorkspaceInstaller.getCurrentInstallingWorkspace();
452
453                // check plugin wasn't installed from a workspace
454                if ((workspace == null) || (workspace.findItem(plugin.getClassName()) == null))
455                    // add a new item for this plugin
456                    addItem(plugin);
457            }
458        }
459    }
460
461    void removeItem(String className)
462    {
463        removeItem(findItem(className));
464    }
465
466    void removeItem(ItemDefinition item)
467    {
468        if (item != null)
469        {
470            // FIXME : unsafe (ribbon component is not supposed to support that)
471            RibbonUtil.removeButton(
472                    RibbonUtil.getBand(RibbonUtil.getTask(ribbon, item.getTaskName()), item.getBandName()),
473                    item.getClassName());
474            // remove item and save workspace
475            item.remove();
476            // save system workspace only, we want to preserve plugins organization
477            systemWorkspace.save();
478        }
479    }
480
481    private void buildSetupPluginBand()
482    {
483        setupPluginsBand.addCommandButton(new IcyCommandButton(PreferencesActions.onlinePluginPreferencesAction),
484                RibbonElementPriority.TOP);
485
486        RibbonUtil.setFixedResizePolicies(setupPluginsBand);
487    }
488
489    // /**
490    // * Add a new installed plugin
491    // */
492    // public void addNewPlugin(String className)
493    // {
494    // // get the corresponding plugin
495    // final PluginDescriptor plugin = PluginLoader.getPlugin(className);
496    //
497    // // check that menu can be displayed in menu
498    // if ((plugin == null) || !plugin.isActionable())
499    // return;
500    //
501    // final IcyCommandButton pluginButton = buildPluginCommandButton(plugin);
502    //
503    // // add it to the new installed plugins workspace and save it
504    // newPluginsBandDef.addItem(plugin.getClassName(), RibbonElementPriority.TOP);
505    // systemWorkspace.save();
506    // // add it to the new installed plugins band
507    // newPluginsBand.addCommandButton(pluginButton, RibbonElementPriority.TOP);
508    // }
509
510    private JRibbonBand[] createRibbonBands(TaskDefinition task)
511    {
512        final List<BandDefinition> bands = getBands(task.getName());
513        final int size = bands.size();
514        final JRibbonBand[] result = new JRibbonBand[size];
515
516        for (int i = 0; i < size; i++)
517        {
518            final BandDefinition band = bands.get(i);
519            // TODO : get icon from BandDefinition
520            result[i] = new JRibbonBand(band.getName(), new IcyIcon(ResourceUtil.ICON_DOC));
521            // use restrictive resize policy by default
522            RibbonUtil.setRestrictiveResizePolicies(result[i]);
523        }
524
525        return result;
526    }
527
528    private RibbonTask createRibbonTask(TaskDefinition task)
529    {
530        final String name = task.getName();
531
532        final RibbonTask ribbonTask = RibbonUtil.getTask(ribbon, name);
533        if (ribbonTask != null)
534        {
535            System.out.println("Ribbon task " + name + " already exists...");
536            return ribbonTask;
537        }
538
539        final RibbonTask result;
540
541        try
542        {
543            result = new RibbonTask(name, createRibbonBands(task));
544        }
545        catch (IllegalArgumentException e)
546        {
547            return null;
548        }
549
550        // use roundRobin collapse policy
551        result.setResizeSequencingPolicy(new CoreRibbonResizeSequencingPolicies.RoundRobin(result));
552
553        return result;
554    }
555
556    /**
557     * build ribbon from workspaces
558     */
559    private void buildWorkspaces()
560    {
561        final ArrayList<PluginDescriptor> plugins = PluginLoader.getPlugins(false);
562
563        // get all TaskDefinition from all active workspace
564        for (TaskDefinition task : getTasks())
565        {
566            // create the task with all needed bands
567            final RibbonTask ribbonTask = createRibbonTask(task);
568
569            // empty task --> ignore
570            if (ribbonTask == null)
571                continue;
572
573            for (BandDefinition band : getBands(task.getName()))
574            {
575                final JRibbonBand ribbonBand = RibbonUtil.getBand(ribbonTask, band.getName());
576
577                if (ribbonBand != null)
578                {
579                    // special case of OTHER plugins
580                    if (band == othersPluginsBandDef)
581                    {
582                        final IcyCommandButton btn = new IcyCommandButton("Other Plugins",
583                                new IcyIcon(ResourceUtil.ICON_COG));
584                        btn.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
585                        btn.setPopupRichTooltip(new RichTooltip("Other plugins",
586                                "You can find here all plugins which are not associated to a workspace"));
587                        btn.setPopupCallback(new PopupPanelCallback()
588                        {
589                            @Override
590                            public JPopupPanel getPopupPanel(JCommandButton commandButton)
591                            {
592                                final JPopupMenu popupMenu = othersPluginsMenu.getPopupMenu();
593
594                                // FIXME : set as heavy weight component for VTK (doesn't work)
595                                // popupMenu.setLightWeightPopupEnabled(false);
596                                popupMenu.show(btn, 0, btn.getHeight());
597
598                                return null;
599                            }
600                        });
601                        ribbonBand.addCommandButton(btn, RibbonElementPriority.TOP);
602
603                        // refresh unassigned list
604                        refreshOthersPluginsList();
605
606                        // adjust restrictive resize policy
607                        RibbonUtil.setFixedResizePolicies(ribbonBand);
608                    }
609                    else
610                    {
611                        for (ItemDefinition item : band.getItems())
612                        {
613                            // simple separator
614                            if (item.isSeparator())
615                                ribbonBand.startGroup();
616                            else
617                            {
618                                final String className = item.getClassName();
619                                final PluginDescriptor plugin = PluginDescriptor.getPlugin(plugins, className);
620
621                                // plugin found ?
622                                if (plugin != null)
623                                {
624                                    // check that menu can be displayed in menu
625                                    if (plugin.isActionable())
626                                        ribbonBand.addCommandButton(PluginCommandButton.createButton(plugin),
627                                                item.getPriority());
628                                }
629                            }
630                        }
631
632                        // adjust restrictive resize policy
633                        RibbonUtil.setRestrictiveResizePolicies(ribbonBand);
634                    }
635                }
636            }
637
638            // add task to ribbon only if not empty
639            if (ribbonTask.getBandCount() > 0)
640                ribbon.addTask(ribbonTask);
641        }
642    }
643
644    /**
645     * clean workspace (remove absent plug-in)
646     */
647    public void cleanWorkspaces()
648    {
649        final ArrayList<PluginDescriptor> plugins = PluginLoader.getPlugins(false);
650
651        // get all items TaskDefinition from all active workspace
652        for (ItemDefinition item : getAllItems())
653        {
654            // avoid separator
655            if (!item.isSeparator())
656            {
657                final String className = item.getClassName();
658                final PluginDescriptor plugin = PluginDescriptor.getPlugin(plugins, className);
659
660                // plugin not found --> remove from workspace
661                if (plugin == null)
662                    item.remove();
663            }
664        }
665
666        // save cleaned workspaces
667        saveWorkspaces();
668    }
669
670    void refreshOthersPluginsList()
671    {
672        // build others plugin list
673        final ArrayList<PluginDescriptor> othersPlugins = new ArrayList<PluginDescriptor>();
674
675        // scan all actionable plugins to find unassigned ones
676        for (PluginDescriptor plugin : PluginLoader.getActionablePlugins())
677        {
678            final String className = plugin.getClassName();
679            // search item in actives workspace
680            final ItemDefinition item = findItem(className);
681
682            // plugin not defined in active workspaces --> add it to the list
683            if ((item == null) || (item.getBandDefinition() == othersPluginsBandDef))
684                othersPlugins.add(plugin);
685        }
686
687        // refresh unassigned plugins menu
688        builOthersPluginsMenu(othersPlugins);
689
690        // rebuild the unassigned workspace
691        othersPluginsBandDef.clear();
692        for (PluginDescriptor plugin : othersPlugins)
693            othersPluginsBandDef.addItem(plugin.getClassName());
694
695        // save the system workspace
696        systemWorkspace.save();
697    }
698
699    private void builOthersPluginsMenu(ArrayList<PluginDescriptor> plugins)
700    {
701        othersPluginsMenu.removeAll();
702
703        for (PluginDescriptor pluginDescriptor : plugins)
704        {
705            String pluginEntry = pluginDescriptor.getSimplePackageName();
706            JMenu menuToPutPlugin = othersPluginsMenu;
707
708            while (pluginEntry != null)
709            {
710                final int index = pluginEntry.indexOf(".");
711                final String pluginDir;
712
713                if (index != -1)
714                {
715                    pluginDir = pluginEntry.substring(0, index);
716                    pluginEntry = pluginEntry.substring(index + 1);
717                }
718                else
719                {
720                    pluginDir = pluginEntry;
721                    pluginEntry = null;
722                }
723
724                // look if name is already a menu.
725                boolean menuExist = false;
726                for (Component component : menuToPutPlugin.getMenuComponents())
727                {
728                    if (component instanceof JMenu)
729                    {
730                        final JMenu menu = (JMenu) component;
731
732                        if (menu.getText().equals(pluginDir))
733                        {
734                            menuToPutPlugin = menu;
735                            menuExist = true;
736                            break;
737                        }
738                    }
739                }
740
741                if (menuExist == false)
742                {
743                    // create Menu
744                    final JMenu menu = new JMenu(pluginDir);
745                    menuToPutPlugin.add(menu);
746                    menuToPutPlugin = menu;
747                }
748            }
749
750            menuToPutPlugin.add(new PluginMenuItem(pluginDescriptor));
751        }
752
753        if (plugins.isEmpty())
754            othersPluginsMenu.add(new JMenuItem("No plugins"));
755
756        othersPluginsMenu.validate();
757    }
758
759    /**
760     * save workspaces from menu
761     */
762    void saveWorkspaces()
763    {
764        // save all active workspaces
765        for (Workspace workspace : workspaces)
766            workspace.save();
767    }
768
769    /**
770     * build task bar (little bar with small icons over all at top)
771     */
772    private void buidlTaskBar()
773    {
774        // PREFERENCES
775        ribbon.addTaskbarComponent(new IcyCommandButton(PreferencesActions.preferencesAction));
776
777        // PLUGINS
778        ribbon.addTaskbarComponent(new IcyCommandButton(PreferencesActions.onlinePluginPreferencesAction));
779
780        // SEPARATOR
781        ribbon.addTaskbarComponent(new JSeparator(SwingConstants.VERTICAL));
782
783        // MULTI FRAME MODE
784        multiWindowGroup = new CommandToggleButtonGroup();
785        multiWindowButton = new IcyCommandToggleButton(GeneralActions.detachedModeAction);
786        ribbon.addTaskbarComponent(multiWindowButton);
787
788        multiWindowGroup.add(multiWindowButton);
789        multiWindowGroup.setSelected(multiWindowButton, GeneralPreferences.getMultiWindowMode());
790
791        // WINDOWS
792        final IcyCommandButton windowsButton = new IcyCommandButton(new IcyIcon("app_window"));
793
794        windowsButton.setPopupRichTooltip(
795                new RichTooltip("Windows", "Show specific windows and general windows setting..."));
796        windowsButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
797        windowsButton.setPopupCallback(new PopupPanelCallback()
798        {
799            @Override
800            public JPopupPanel getPopupPanel(JCommandButton commandButton)
801            {
802                final JCommandPopupMenu result = new JCommandPopupMenu();
803
804                // ALWAYS ON TOP
805                final CommandToggleButtonGroup aotGroup = new CommandToggleButtonGroup();
806                final IcyCommandToggleMenuButton aotButton = new IcyCommandToggleMenuButton(
807                        WindowActions.stayOnTopAction);
808                result.addMenuButton(aotButton);
809
810                aotGroup.add(aotButton);
811                aotGroup.setSelected(aotButton, GeneralPreferences.getAlwaysOnTop());
812
813                // SEPARATOR
814                result.addMenuSeparator();
815
816                // LOOK AND FEEL
817                final IcyCommandMenuButton lafButton = new IcyCommandMenuButton("Appearance",
818                        new IcyIcon(ResourceUtil.ICON_SMILEY_HAPPY));
819
820                lafButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
821                lafButton.setPopupRichTooltip(new RichTooltip("Look and feel", "Change appearance of the interface"));
822                lafButton.setPopupCallback(new PopupPanelCallback()
823                {
824                    @Override
825                    public JPopupPanel getPopupPanel(JCommandButton commandButton)
826                    {
827                        // better to build it on request as it takes a bit of time
828                        // and we want to speed up the initial loading
829                        return LookAndFeelUtil.getLookAndFeelMenu();
830                    }
831                });
832                result.addMenuButton(lafButton);
833
834                // SEPARATOR
835                result.addMenuSeparator();
836
837                // SWIMMING POOL
838                final IcyCommandMenuButton spButton = new IcyCommandMenuButton(WindowActions.swimmingPoolAction);
839                result.addMenuButton(spButton);
840
841                // SCRIPT EDITOR
842                // TODO : reactivate when done
843                // final IcyCommandMenuButton seButton = new IcyCommandMenuButton("Script Editor",
844                // new
845                // ICYResizableIcon(new IcyIcon(
846                // "lighting"));
847                // seButton.addActionListener(new ActionListener()
848                // {
849                // @Override
850                // public void actionPerformed(ActionEvent e)
851                // {
852                // new ScriptEditor();
853                // }
854                // });
855                // result.addMenuButton(seButton);
856
857                // SEPARATOR
858                result.addMenuSeparator();
859
860                // REORGANIZE TILE
861                final IcyCommandMenuButton tileButton = new IcyCommandMenuButton("Tile", new IcyIcon("2x2_grid"));
862                tileButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
863                tileButton.setPopupCallback(new PopupPanelCallback()
864                {
865                    @Override
866                    public JPopupPanel getPopupPanel(JCommandButton commandButton)
867                    {
868                        final JCommandPopupMenu result = new JCommandPopupMenu();
869
870                        // grid
871                        result.addMenuButton(new IcyCommandMenuButton(WindowActions.gridTileAction));
872                        // horizontal
873                        result.addMenuButton(new IcyCommandMenuButton(WindowActions.horizontalTileAction));
874                        // vertical
875                        result.addMenuButton(new IcyCommandMenuButton(WindowActions.verticalTileAction));
876
877                        return result;
878                    }
879                });
880                result.addMenuButton(tileButton);
881
882                // REORGANIZE CASCADE
883                result.addMenuButton(new IcyCommandMenuButton(WindowActions.cascadeAction));
884
885                // SEPARATOR
886                result.addMenuSeparator();
887
888                // OPENED SEQUENCES
889                final ArrayList<Viewer> allViewers = Icy.getMainInterface().getViewers();
890                final IcyCommandMenuButton sequencesButton = new IcyCommandMenuButton("Opened sequences",
891                        new IcyIcon(ResourceUtil.ICON_PICTURE));
892                sequencesButton.setPopupRichTooltip(new RichTooltip("Opened sequences", "Show the selected sequence"));
893                sequencesButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
894                sequencesButton.setPopupCallback(new PopupPanelCallback()
895                {
896                    @Override
897                    public JPopupPanel getPopupPanel(JCommandButton commandButton)
898                    {
899                        final JCommandPopupMenu result = new JCommandPopupMenu();
900
901                        // SEQUENCES
902                        for (Viewer viewer : allViewers)
903                        {
904                            final Viewer v = viewer;
905
906                            final IcyCommandMenuButton seqButton = new IcyCommandMenuButton(viewer.getTitle());
907                            seqButton.addActionListener(new ActionListener()
908                            {
909                                @Override
910                                public void actionPerformed(ActionEvent e)
911                                {
912                                    ThreadUtil.invokeLater(new Runnable()
913                                    {
914                                        @Override
915                                        public void run()
916                                        {
917                                            // remove minimized state
918                                            if (v.isMinimized())
919                                                v.setMinimized(false);
920                                            // then grab focus
921                                            v.requestFocus();
922                                            v.toFront();
923                                        }
924                                    });
925                                }
926                            });
927                            result.addMenuButton(seqButton);
928                        }
929
930                        return result;
931                    }
932                });
933                sequencesButton.setEnabled(allViewers.size() > 0);
934                result.addMenuButton(sequencesButton);
935
936                // OPENED FRAMES
937                final ArrayList<IcyFrame> allFrames = IcyFrame.getAllFrames();
938                final IcyCommandMenuButton framesButton = new IcyCommandMenuButton("Opened frames",
939                        new IcyIcon(ResourceUtil.ICON_WINDOW));
940                framesButton.setPopupRichTooltip(new RichTooltip("Opened frames", "Show all frames"));
941                framesButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
942                framesButton.setPopupCallback(new PopupPanelCallback()
943                {
944                    @Override
945                    public JPopupPanel getPopupPanel(JCommandButton commandButton)
946                    {
947                        final JCommandPopupMenu result = new JCommandPopupMenu();
948
949                        // FRAMES
950                        for (IcyFrame frame : allFrames)
951                        {
952                            if ((frame instanceof Viewer) || (frame instanceof TaskFrame) || !frame.isVisible())
953                                continue;
954
955                            final IcyFrame f = frame;
956
957                            final IcyCommandMenuButton frameButton = new IcyCommandMenuButton(frame.getTitle());
958                            frameButton.addActionListener(new ActionListener()
959                            {
960                                @Override
961                                public void actionPerformed(ActionEvent e)
962                                {
963                                    ThreadUtil.invokeLater(new Runnable()
964                                    {
965                                        @Override
966                                        public void run()
967                                        {
968                                            // remove minimized state
969                                            if (f.isMinimized())
970                                                f.setMinimized(false);
971                                            // then grab focus
972                                            f.requestFocus();
973                                            f.toFront();
974                                        }
975                                    });
976                                }
977                            });
978                            result.addMenuButton(frameButton);
979                        }
980
981                        return result;
982                    }
983                });
984
985                // check if we have visible frame
986                boolean hasVisibleFrame = false;
987                for (IcyFrame frame : allFrames)
988                    if (!((frame instanceof Viewer) || (frame instanceof TaskFrame) || !frame.isVisible()))
989                        hasVisibleFrame = true;
990
991                framesButton.setEnabled(hasVisibleFrame);
992                result.addMenuButton(framesButton);
993
994                return result;
995            }
996        });
997        ribbon.addTaskbarComponent(windowsButton);
998
999        // SEPARATOR
1000        ribbon.addTaskbarComponent(new JSeparator(SwingConstants.VERTICAL));
1001
1002        // SEARCH BAR
1003        searchBar = new SearchBar();
1004        searchBar.setColumns(14);
1005
1006        ribbon.addTaskbarComponent(searchBar);
1007
1008        // HELP / INFOS
1009        final IcyCommandButton helpAndInfoButton = new IcyCommandButton(new IcyIcon(ResourceUtil.ICON_INFO));
1010
1011        helpAndInfoButton.setPopupRichTooltip(
1012                new RichTooltip("General help and information", "Help, Updates and Informations about Icy."));
1013        helpAndInfoButton.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
1014        helpAndInfoButton.setPopupCallback(new PopupPanelCallback()
1015        {
1016            @Override
1017            public JPopupPanel getPopupPanel(JCommandButton commandButton)
1018            {
1019                final JCommandPopupMenu result = new JCommandPopupMenu();
1020
1021                // HELP
1022                result.addMenuButton(new IcyCommandMenuButton(GeneralActions.onlineHelpAction));
1023                // WEB SITE
1024                result.addMenuButton(new IcyCommandMenuButton(GeneralActions.websiteAction));
1025                // CHECK FOR UPDATE
1026                result.addMenuButton(new IcyCommandMenuButton(GeneralActions.checkUpdateAction));
1027                // CHANGELOG
1028                result.addMenuButton(new IcyCommandMenuButton(GeneralActions.changeLogAction));
1029                // ABOUT
1030                result.addMenuButton(new IcyCommandMenuButton(GeneralActions.aboutAction));
1031
1032                return result;
1033            }
1034        });
1035        ribbon.addTaskbarComponent(helpAndInfoButton);
1036
1037        // LINK
1038        // ribbon.addTaskbarComponent(new IcyCommandButton(GeneralActions.linkAction));
1039    }
1040
1041    private void checkPluginsMenuCoherence()
1042    {
1043        // get plugins we have to display in menu
1044        final ArrayList<PluginDescriptor> plugins = PluginLoader.getActionablePlugins();
1045        final ArrayList<ItemDefinition> items = getAllItems();
1046        final PluginClassNameSorter pluginsSorter = PluginClassNameSorter.instance;
1047
1048        // sort plugins on classname
1049        Collections.sort(plugins, pluginsSorter);
1050
1051        final PluginDescriptor keyPlugin = new PluginDescriptor();
1052
1053        ThreadUtil.invokeLater(new Runnable()
1054        {
1055            @Override
1056            public void run()
1057            {
1058                // find removed plugins
1059                for (ItemDefinition item : items)
1060                {
1061                    if (!item.isSeparator() && (item.getBandDefinition() != othersPluginsBandDef))
1062                    {
1063                        // set the className for searched element
1064                        keyPlugin.getIdent().setClassName(item.getClassName());
1065
1066                        // not found in plugin list --> remove it
1067                        if (Collections.binarySearch(plugins, keyPlugin, pluginsSorter) < 0)
1068                            removeItem(item);
1069                    }
1070                }
1071
1072                // update or add plugin button
1073                for (PluginDescriptor plugin : plugins)
1074                    updateItem(plugin, findItem(plugin.getClassName()));
1075
1076                refreshOthersPluginsList();
1077            }
1078        });
1079    }
1080
1081    @Override
1082    public void pluginLoaderChanged(PluginLoaderEvent e)
1083    {
1084        // update menu according to plugins change
1085        checkPluginsMenuCoherence();
1086    }
1087
1088    @Override
1089    public void sequenceActivated(Sequence sequence)
1090    {
1091        sequenceOperationTask.onSequenceChange();
1092        roiTask.onSequenceActivationChange();
1093        ijTask.onSequenceActivationChange();
1094        applicationMenu.onSequenceActivationChange();
1095    }
1096
1097    @Override
1098    public void sequenceDeactivated(Sequence sequence)
1099    {
1100        // nothing here
1101    }
1102
1103    @Override
1104    public void activeSequenceChanged(SequenceEvent event)
1105    {
1106        final SequenceEventSourceType type = event.getSourceType();
1107
1108        if ((type == SequenceEventSourceType.SEQUENCE_DATA) || (type == SequenceEventSourceType.SEQUENCE_TYPE))
1109            sequenceOperationTask.onSequenceChange();
1110        if (type == SequenceEventSourceType.SEQUENCE_ROI)
1111        {
1112            sequenceOperationTask.onSequenceChange();
1113            roiTask.onSequenceChange();
1114        }
1115    }
1116}