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.FileActions;
022import icy.action.GeneralActions;
023import icy.action.PreferencesActions;
024import icy.file.FileUtil;
025import icy.file.Importer;
026import icy.file.Loader;
027import icy.gui.component.button.IcyCommandButton;
028import icy.gui.component.menu.IcyRibbonApplicationMenuEntryPrimary;
029import icy.gui.component.menu.IcyRibbonApplicationMenuEntrySecondary;
030import icy.gui.plugin.PluginCommandButton;
031import icy.gui.util.ComponentUtil;
032import icy.main.Icy;
033import icy.plugin.PluginDescriptor;
034import icy.plugin.PluginLauncher;
035import icy.plugin.PluginLoader;
036import icy.plugin.PluginLoader.PluginLoaderEvent;
037import icy.plugin.PluginLoader.PluginLoaderListener;
038import icy.preferences.GeneralPreferences;
039import icy.preferences.IcyPreferences;
040import icy.resource.ResourceUtil;
041import icy.resource.icon.IcyIcon;
042import icy.sequence.Sequence;
043import icy.sequence.SequenceImporter;
044import icy.system.IcyExceptionHandler;
045import icy.system.thread.ThreadUtil;
046import icy.type.collection.CollectionUtil;
047import icy.type.collection.list.RecentFileList;
048import icy.util.StringUtil;
049
050import java.awt.BorderLayout;
051import java.awt.event.ActionEvent;
052import java.awt.event.ActionListener;
053import java.io.File;
054import java.util.List;
055
056import javax.swing.JPanel;
057import javax.swing.JScrollPane;
058import javax.swing.SwingConstants;
059
060import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
061import org.pushingpixels.flamingo.api.common.CommandButtonDisplayState;
062import org.pushingpixels.flamingo.api.common.JCommandButton;
063import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
064import org.pushingpixels.flamingo.api.common.JCommandButtonPanel;
065import org.pushingpixels.flamingo.api.common.JCommandButtonPanel.LayoutKind;
066import org.pushingpixels.flamingo.api.common.RichTooltip;
067import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenu;
068import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenuEntryPrimary;
069import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenuEntrySecondary;
070
071/**
072 * @author Stephane
073 */
074public class ApplicationMenu extends RibbonApplicationMenu implements PluginLoaderListener
075{
076     static final int RECENTFILE_MAXLEN = 100;
077
078    /**
079     * Secondary panel management for "Open Recent File"
080     */
081    private class OpenRecentFileRollOverCallBack implements RibbonApplicationMenuEntryPrimary.PrimaryRolloverCallback
082    {
083        public OpenRecentFileRollOverCallBack()
084        {
085            super();
086        }
087
088        @Override
089        public void menuEntryActivated(JPanel targetPanel)
090        {
091            ComponentUtil.setPreferredWidth(targetPanel, 480);
092
093            final JCommandButtonPanel recentFilesPanel = new JCommandButtonPanel(CommandButtonDisplayState.MEDIUM);
094
095            // set to 1 column maximum
096            recentFilesPanel.setMaxButtonColumns(1);
097            recentFilesPanel.setLayoutKind(LayoutKind.ROW_FILL);
098
099            // recent files group
100            recentFilesPanel.addButtonGroup("Recent Files");
101
102            // remove obsolete entries
103            recentFileList.clean();
104
105            final int nbRecentFile = recentFileList.getSize();
106            final IcyIcon icon = new IcyIcon("document");
107
108            for (int i = 0; i < nbRecentFile; i++)
109            {
110                final String entry = recentFileList.getEntryAsName(i, RECENTFILE_MAXLEN, true);
111
112                if (!StringUtil.isEmpty(entry))
113                {
114                    final JCommandButton button = new JCommandButton(entry, icon);
115
116                    button.setHorizontalAlignment(SwingConstants.LEFT);
117
118                    final String[] paths = recentFileList.getEntry(i);
119                    // final File[] files = recentFileList.getEntryAsFiles(i);
120
121                    final RichTooltip toolTip;
122                    final int numFile = paths.length;
123
124                    if (numFile == 1)
125                        toolTip = new RichTooltip("Single file sequence", FileUtil.getFileName(paths[0]));
126                    else
127                        toolTip = new RichTooltip("Multiple files sequence", FileUtil.getFileName(paths[0]));
128
129                    for (int j = 1; j < Math.min(10, numFile); j++)
130                        toolTip.addDescriptionSection(FileUtil.getFileName(paths[j]));
131                    if (numFile > 10)
132                    {
133                        toolTip.addDescriptionSection("...");
134                        toolTip.addDescriptionSection(FileUtil.getFileName(paths[numFile - 1]));
135                    }
136
137                    button.setActionRichTooltip(toolTip);
138
139                    button.addActionListener(new ActionListener()
140                    {
141                        @Override
142                        public void actionPerformed(ActionEvent ae)
143                        {
144                            Loader.load(CollectionUtil.asList(paths), false, true, true);
145                        }
146                    });
147
148                    // add button to history panel
149                    recentFilesPanel.addButtonToLastGroup(button);
150                }
151            }
152
153            // action group
154            recentFilesPanel.addButtonGroup("Action");
155
156            final IcyCommandButton clearButton = new IcyCommandButton(FileActions.clearRecentFilesAction);
157            recentFilesPanel.addButtonToLastGroup(clearButton);
158
159            targetPanel.removeAll();
160            targetPanel.setLayout(new BorderLayout());
161            targetPanel.add(new JScrollPane(recentFilesPanel), BorderLayout.CENTER);
162            targetPanel.validate();
163        }
164    }
165
166    /**
167     * Secondary panel management for "Import"
168     * Display all other importers (anything which is not "openable" from File)
169     */
170    private class ImportResourceRollOverCallBack implements RibbonApplicationMenuEntryPrimary.PrimaryRolloverCallback
171    {
172        public ImportResourceRollOverCallBack()
173        {
174            super();
175        }
176
177        @Override
178        public void menuEntryActivated(JPanel targetPanel)
179        {
180            ComponentUtil.setPreferredWidth(targetPanel, 480);
181
182            final JCommandButtonPanel importPanel = new JCommandButtonPanel(CommandButtonDisplayState.MEDIUM);
183
184            // set to 1 column maximum
185            importPanel.setMaxButtonColumns(1);
186            importPanel.setLayoutKind(LayoutKind.ROW_FILL);
187
188            // add Sequence importers
189            final List<PluginDescriptor> sequenceImporterPlugins = PluginLoader.getPlugins(SequenceImporter.class);
190            if (!sequenceImporterPlugins.isEmpty())
191            {
192                importPanel.addButtonGroup("Sequence importer");
193                for (PluginDescriptor plugin : sequenceImporterPlugins)
194                {
195                    final AbstractCommandButton button = PluginCommandButton.createButton(plugin, false, false, false);
196
197                    button.setHorizontalAlignment(SwingConstants.LEFT);
198                    button.addActionListener(new ActionListener()
199                    {
200                        @Override
201                        public void actionPerformed(ActionEvent event)
202                        {
203                            final AbstractCommandButton button = (AbstractCommandButton) event.getSource();
204                            final PluginDescriptor pluginDescriptor = PluginLoader.getPlugin(button.getName());
205
206                            if (pluginDescriptor != null)
207                            {
208                                try
209                                {
210                                    // we really need to start the plugin here in case it uses EzPlug for instance
211                                    final SequenceImporter importer = (SequenceImporter) PluginLauncher
212                                            .startSafe(pluginDescriptor);
213
214                                    // asynchronous loading
215                                    ThreadUtil.bgRun(new Runnable()
216                                    {
217                                        @Override
218                                        public void run()
219                                        {
220
221                                            try
222                                            {
223                                                // plugin correctly started ? --> do load operation
224                                                final Sequence result = importer.load();
225                                                
226                                                // display result sequence
227                                                if (result != null)
228                                                    Icy.getMainInterface().addSequence(result);
229                                            }
230                                            catch (Exception exc)
231                                            {
232                                                IcyExceptionHandler.handleException(exc, false);
233                                            }
234                                        }
235                                    });
236                                }
237                                catch (Exception exc)
238                                {
239                                    IcyExceptionHandler.handleException(exc, false);
240                                }
241                            }
242                        }
243                    });
244
245                    importPanel.addButtonToLastGroup(button);
246                }
247            }
248
249            // add Sequence importers
250            final List<PluginDescriptor> importerPlugins = PluginLoader.getPlugins(Importer.class);
251            if (!importerPlugins.isEmpty())
252            {
253                importPanel.addButtonGroup("General importer");
254
255                for (PluginDescriptor plugin : importerPlugins)
256                {
257                    final AbstractCommandButton button = PluginCommandButton.createButton(plugin, false, false, false);
258
259                    button.setHorizontalAlignment(SwingConstants.LEFT);
260                    button.addActionListener(new ActionListener()
261                    {
262                        @Override
263                        public void actionPerformed(ActionEvent event)
264                        {
265                            final AbstractCommandButton button = (AbstractCommandButton) event.getSource();
266                            final PluginDescriptor pluginDescriptor = PluginLoader.getPlugin(button.getName());
267
268                            if (pluginDescriptor != null)
269                            {
270                                try
271                                {
272                                    // we really need to start the plugin here in case it uses EzPlug for instance
273                                    final Importer importer = (Importer) PluginLauncher.startSafe(pluginDescriptor);
274
275                                    // asynchronous loading
276                                    ThreadUtil.bgRun(new Runnable()
277                                    {
278                                        @Override
279                                        public void run()
280                                        {
281
282                                            try
283                                            {
284                                                // plugin correctly started ? --> do load operation
285                                                importer.load();
286                                            }
287                                            catch (Exception exc)
288                                            {
289                                                IcyExceptionHandler.handleException(exc, false);
290                                            }
291                                        }
292                                    });
293                                }
294                                catch (Exception exc)
295                                {
296                                    IcyExceptionHandler.handleException(exc, false);
297                                }
298                            }
299                        }
300                    });
301
302                    importPanel.addButtonToLastGroup(button);
303                }
304            }
305
306            targetPanel.removeAll();
307            targetPanel.setLayout(new BorderLayout());
308            targetPanel.add(new JScrollPane(importPanel), BorderLayout.CENTER);
309            targetPanel.validate();
310        }
311    }
312
313    /**
314     * Default roll callback
315     */
316    private class EmptyRollOverCallBack implements RibbonApplicationMenuEntryPrimary.PrimaryRolloverCallback
317    {
318        public EmptyRollOverCallBack()
319        {
320            super();
321        }
322
323        @Override
324        public void menuEntryActivated(JPanel targetPanel)
325        {
326            ComponentUtil.setPreferredWidth(targetPanel, 480);
327
328            // ribbon need at least one component in targetPanel
329            targetPanel.removeAll();
330            targetPanel.add(new JPanel());
331            targetPanel.validate();
332        }
333    }
334
335    final RecentFileList recentFileList;
336
337    private final RibbonApplicationMenuEntryPrimary amepCreate;
338    private final RibbonApplicationMenuEntrySecondary amesCreateGraySequence;
339    private final RibbonApplicationMenuEntrySecondary amesCreateRGBSequence;
340    private final RibbonApplicationMenuEntrySecondary amesCreateRGBASequence;
341    private final RibbonApplicationMenuEntryPrimary amepOpen;
342    private final RibbonApplicationMenuEntryPrimary amepImport;
343    private final RibbonApplicationMenuEntryPrimary amepSaveDefault;
344    private final RibbonApplicationMenuEntrySecondary amepSave;
345    private final RibbonApplicationMenuEntrySecondary amepSaveAs;
346    private final RibbonApplicationMenuEntrySecondary amepSaveMetaData;
347    private final RibbonApplicationMenuEntryPrimary amepClose;
348    private final RibbonApplicationMenuEntrySecondary amesCloseCurrent;
349    private final RibbonApplicationMenuEntrySecondary amesCloseOthers;
350    private final RibbonApplicationMenuEntrySecondary amesCloseAll;
351    private final RibbonApplicationMenuEntryPrimary amepPreferences;
352    private final RibbonApplicationMenuEntryPrimary amepExit;
353
354    /**
355     * 
356     */
357    public ApplicationMenu()
358    {
359        super();
360
361        recentFileList = new RecentFileList(IcyPreferences.applicationRoot().node("loader"));
362
363        // CREATE SEQUENCE
364        amepCreate = new IcyRibbonApplicationMenuEntryPrimary(FileActions.newSequenceAction);
365
366        amesCreateGraySequence = new IcyRibbonApplicationMenuEntrySecondary(FileActions.newGraySequenceAction);
367        amesCreateRGBSequence = new IcyRibbonApplicationMenuEntrySecondary(FileActions.newRGBSequenceAction);
368        amesCreateRGBASequence = new IcyRibbonApplicationMenuEntrySecondary(FileActions.newARGBSequenceAction);
369
370        amepCreate.addSecondaryMenuGroup("New image", amesCreateGraySequence, amesCreateRGBSequence, amesCreateRGBASequence);
371
372        // OPEN & IMPORT
373        amepOpen = new IcyRibbonApplicationMenuEntryPrimary(FileActions.openSequenceAction);
374        amepOpen.setRolloverCallback(new OpenRecentFileRollOverCallBack());
375
376        amepImport = new IcyRibbonApplicationMenuEntryPrimary(new IcyIcon(ResourceUtil.ICON_DOC_IMPORT), "Import",
377                null, CommandButtonKind.POPUP_ONLY);
378        amepImport.setRolloverCallback(new ImportResourceRollOverCallBack());
379
380        // SAVE & EXPORT
381        amepSaveDefault = new IcyRibbonApplicationMenuEntryPrimary(FileActions.saveDefaultSequenceAction);
382
383        amepSave = new IcyRibbonApplicationMenuEntrySecondary(FileActions.saveSequenceAction);
384        amepSaveAs = new IcyRibbonApplicationMenuEntrySecondary(FileActions.saveAsSequenceAction);
385        amepSaveMetaData = new IcyRibbonApplicationMenuEntrySecondary(FileActions.saveMetaDataAction);
386
387        amepSaveDefault.addSecondaryMenuGroup("Save", amepSave, amepSaveAs, amepSaveMetaData);
388        
389        // final RibbonApplicationMenuEntryPrimary amepExport = new
390        // RibbonApplicationMenuEntryPrimary(
391        // new ICYResizableIcon.Icy("doc_export.png"), "Export", new ActionListener()
392        // {
393        // @Override
394        // public void actionPerformed(ActionEvent e)
395        // {
396        // System.out.println("export action...");
397        // }
398        // }, CommandButtonKind.ACTION_ONLY);
399        // amepExport.setRolloverCallback(new DefaultRollOverCallBack());
400
401        // CLOSE
402        amepClose = new IcyRibbonApplicationMenuEntryPrimary(FileActions.closeSequenceAction);
403
404        amesCloseCurrent = new IcyRibbonApplicationMenuEntrySecondary(FileActions.closeCurrentSequenceAction);
405        amesCloseOthers = new IcyRibbonApplicationMenuEntrySecondary(FileActions.closeOthersSequencesAction);
406        amesCloseAll = new IcyRibbonApplicationMenuEntrySecondary(FileActions.closeAllSequencesAction);
407
408        amepClose.addSecondaryMenuGroup("Close Sequence", amesCloseCurrent, amesCloseOthers, amesCloseAll);
409
410        // PREFERENCES
411        amepPreferences = new IcyRibbonApplicationMenuEntryPrimary(PreferencesActions.preferencesAction);
412
413        // EXIT
414        amepExit = new IcyRibbonApplicationMenuEntryPrimary(GeneralActions.exitApplicationAction);
415        amepExit.setRolloverCallback(new EmptyRollOverCallBack());
416
417        // build menu
418
419        addMenuEntry(amepCreate);
420        addMenuEntry(amepOpen);
421        addMenuEntry(amepSaveDefault);
422
423        addMenuSeparator();
424        addMenuEntry(amepImport);
425        // addMenuEntry(amepExport);
426
427        addMenuSeparator();
428        addMenuEntry(amepClose);
429
430        addMenuSeparator();
431        addMenuEntry(amepPreferences);
432
433        addMenuSeparator();
434        addMenuEntry(amepExit);
435
436        setDefaultCallback(new OpenRecentFileRollOverCallBack());
437
438        refreshState();
439
440        PluginLoader.addListener(this);
441    }
442
443    private void refreshState()
444    {
445        final Sequence focusedSequence = Icy.getMainInterface().getActiveSequence();
446
447        final boolean hasImporter = (!PluginLoader.getPlugins(SequenceImporter.class).isEmpty())
448                || (!PluginLoader.getPlugins(Importer.class).isEmpty());
449
450        amepImport.setEnabled(hasImporter);
451        amepSaveDefault.setEnabled(focusedSequence != null);
452        amepSave.setEnabled((focusedSequence != null) && !StringUtil.isEmpty(focusedSequence.getFilename()));
453        amepSaveAs.setEnabled(focusedSequence != null);
454        amepSaveMetaData.setEnabled((focusedSequence != null) && !StringUtil.isEmpty(focusedSequence.getFilename())
455                && GeneralPreferences.getSequencePersistence());
456        amepClose.setEnabled(Icy.getMainInterface().getSequences().size() > 0);
457    }
458
459    /**
460     * @return the recentFileList
461     */
462    public RecentFileList getRecentFileList()
463    {
464        return recentFileList;
465    }
466
467    public void addRecentLoadedFile(List<File> files)
468    {
469        addRecentLoadedFile(files.toArray(new File[files.size()]));
470    }
471
472    public void addRecentLoadedFile(File[] files)
473    {
474        recentFileList.addEntry(files);
475    }
476
477    public void addRecentLoadedFile(File file)
478    {
479        addRecentLoadedFile(new File[] {file});
480    }
481
482    /**
483     * Add a list of recently opened files (String format)
484     */
485    public void addRecentFile(List<String> paths)
486    {
487        addRecentFile(paths.toArray(new String[paths.size()]));
488    }
489
490    public void addRecentFile(String[] paths)
491    {
492        recentFileList.addEntry(paths);
493    }
494
495    public void addRecentFile(String path)
496    {
497        addRecentFile(new String[] {path});
498    }
499
500    public void onSequenceActivationChange()
501    {
502        refreshState();
503    }
504
505    @Override
506    public void pluginLoaderChanged(PluginLoaderEvent e)
507    {
508        // amepImport.addSecondaryMenuGroup("Import a sequence", getImportEntries());
509        refreshState();
510    }
511}