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}