diff --git a/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java b/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java index c3098c9d..c6ba12de 100644 --- a/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java +++ b/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java @@ -45,6 +45,7 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP public static final String OUTLINE_KEY = "outline"; public static final String THEME_KEY = "theme"; public static final String RECENT_COMPONENTS_KEY = "recentComponents"; + public static final String RECENT_FILES_KEY = "recentFiles"; public static final String SHOW_RULERS_KEY = "showRulers"; public static final String SHOW_GRID_KEY = "showGrid"; diff --git a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java index 8335c7d6..5a39fa5a 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java +++ b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java @@ -84,6 +84,8 @@ public class Presenter implements IPlugInPort { public static final int ICON_SIZE = 32; + private static final int MAX_RECENT_FILES = 5; + private Project currentProject; private Map> componentTypes; /** @@ -248,12 +250,25 @@ public class Presenter implements IPlugInPort { builder.append(" recentFiles = (List) ConfigurationManager.getInstance().readObject(RECENT_FILES_KEY, null); + if (recentFiles == null) + recentFiles = new ArrayList(); + recentFiles.remove(fileName); + recentFiles.add(0, fileName); + while (recentFiles.size() > MAX_RECENT_FILES) + recentFiles.remove(recentFiles.size() - 1); + ConfigurationManager.getInstance().writeValue(RECENT_FILES_KEY, recentFiles); + } + @Override public boolean allowFileAction() { if (projectFileManager.isModified()) { @@ -282,6 +297,8 @@ public class Presenter implements IPlugInPort { try { currentProject.setFileVersion(CURRENT_VERSION); projectFileManager.serializeProjectToFile(currentProject, fileName, isBackup); + if (!isBackup) + addToRecentFiles(fileName); } catch (Exception ex) { LOG.error("Could not save file", ex); if (!isBackup) { diff --git a/diylc/diylc-swing/src/org/diylc/images/IconLoader.java b/diylc/diylc-swing/src/org/diylc/images/IconLoader.java index f877b576..41ae7bad 100644 --- a/diylc/diylc-swing/src/org/diylc/images/IconLoader.java +++ b/diylc/diylc-swing/src/org/diylc/images/IconLoader.java @@ -39,7 +39,7 @@ public enum IconLoader { "messages.png"), SearchBox("search-box.png"), Screwdriver("screwdriver.png"), Hammer("hammer.png"), FlipHorizontal( "flip_horizontal.png"), FlipVertical("flip_vertical.png"), MagicWand("magic_wand.png"), PinGrey("pin_grey.png"), PinGreen( "pin_green.png"), CoffeebeanEdit("coffeebean_edit.png"), ApplicationEdit("application_edit.png"), ComponentAdd( - "component_add.png"); + "component_add.png"), History("history.png"), DocumentPlain("document_plain.png"); protected String name; diff --git a/diylc/diylc-swing/src/org/diylc/images/document_plain.png b/diylc/diylc-swing/src/org/diylc/images/document_plain.png new file mode 100644 index 00000000..d2ccb761 Binary files /dev/null and b/diylc/diylc-swing/src/org/diylc/images/document_plain.png differ diff --git a/diylc/diylc-swing/src/org/diylc/images/history.png b/diylc/diylc-swing/src/org/diylc/images/history.png new file mode 100644 index 00000000..4f7ebd8e Binary files /dev/null and b/diylc/diylc-swing/src/org/diylc/images/history.png differ diff --git a/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java b/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java index 75447267..a94d23d7 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java +++ b/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java @@ -192,7 +192,7 @@ public class ActionFactory { this.plugInPort = plugInPort; putValue(AbstractAction.NAME, "New"); putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK)); - putValue(AbstractAction.SMALL_ICON, IconLoader.DocumentPlainYellow.getIcon()); + putValue(AbstractAction.SMALL_ICON, IconLoader.DocumentPlain.getIcon()); } @Override diff --git a/diylc/diylc-swing/src/org/diylc/swing/IDynamicSubmenuHandler.java b/diylc/diylc-swing/src/org/diylc/swing/IDynamicSubmenuHandler.java new file mode 100644 index 00000000..2373ea43 --- /dev/null +++ b/diylc/diylc-swing/src/org/diylc/swing/IDynamicSubmenuHandler.java @@ -0,0 +1,10 @@ +package org.diylc.swing; + +import java.util.List; + +public interface IDynamicSubmenuHandler { + + void onActionPerformed(String name); + + List getAvailableItems(); +} diff --git a/diylc/diylc-swing/src/org/diylc/swing/ISwingUI.java b/diylc/diylc-swing/src/org/diylc/swing/ISwingUI.java index 1af5be28..8844d701 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/ISwingUI.java +++ b/diylc/diylc-swing/src/org/diylc/swing/ISwingUI.java @@ -48,9 +48,8 @@ public interface ISwingUI extends IView { void injectMenuAction(Action action, String menuName); /** - * Injects a custom submenu into application's main menu. If action is set to null - * {@link Separator} will be added. If the specified menu does not exist it will be automatically - * created. + * Injects a custom submenu into application's main menu. If the specified menu does not exist it + * will be automatically created. * * @param name * @param icon @@ -58,6 +57,18 @@ public interface ISwingUI extends IView { */ void injectSubmenu(String name, Icon icon, String parentMenuName); + /** + * Injects a dynamic submenu into application's main menu. Items are read from the + * handler and notifications are sent to the handler when an item is + * clicked on. + * + * @param name + * @param icon + * @param parentMenuName + * @param handler + */ + void injectDynamicSubmenu(String name, Icon icon, String parentMenuName, IDynamicSubmenuHandler handler); + /** * Runs a task in background while showing busy cursor and a glass pane. * @@ -69,7 +80,7 @@ public interface ISwingUI extends IView { * @return {@link JFrame} that can be used to reference secondary dialogs and frames */ JFrame getOwnerFrame(); - + /** * */ diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/MainFrame.java b/diylc/diylc-swing/src/org/diylc/swing/gui/MainFrame.java index 6972dabc..bb46e83e 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/gui/MainFrame.java +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/MainFrame.java @@ -8,9 +8,6 @@ import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; @@ -28,12 +25,15 @@ import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; +import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButtonMenuItem; import javax.swing.SwingConstants; import javax.swing.SwingWorker; import javax.swing.Timer; +import javax.swing.event.MenuEvent; +import javax.swing.event.MenuListener; import org.apache.log4j.Logger; import org.diylc.appframework.miscutils.ConfigurationManager; @@ -46,6 +46,7 @@ import org.diylc.common.PropertyWrapper; import org.diylc.core.IView; import org.diylc.images.IconLoader; import org.diylc.presenter.Presenter; +import org.diylc.swing.IDynamicSubmenuHandler; import org.diylc.swing.ISwingUI; import org.diylc.swing.gui.editor.PropertyEditorDialog; import org.diylc.swing.plugins.autosave.AutoSavePlugin; @@ -94,10 +95,10 @@ public class MainFrame extends JFrame implements ISwingUI { DialogFactory.getInstance().initialize(this); this.presenter = new Presenter(this); - + canvasPlugin = new CanvasPlugin(this); - presenter.installPlugin(new ToolBox(this)); + presenter.installPlugin(new ToolBox(this)); presenter.installPlugin(new FileMenuPlugin(this)); presenter.installPlugin(new EditMenuPlugin(this)); presenter.installPlugin(new ConfigPlugin(this)); @@ -106,12 +107,12 @@ public class MainFrame extends JFrame implements ISwingUI { presenter.installPlugin(new HelpMenuPlugin(this)); presenter.installPlugin(new StatusBar(this)); - + presenter.installPlugin(canvasPlugin); - + presenter.installPlugin(new ComponentTree(this, canvasPlugin.getCanvasPanel())); presenter.installPlugin(new FramePlugin()); - + presenter.installPlugin(new AutoSavePlugin(this)); presenter.createNewProject(); @@ -302,6 +303,52 @@ public class MainFrame extends JFrame implements ISwingUI { menuMap.put(name, submenu); } + @Override + public void injectDynamicSubmenu(String name, Icon icon, String parentMenuName, final IDynamicSubmenuHandler handler) { + LOG.info(String.format("injectDynamicSubmenu(%s, icon, %s)", name, parentMenuName)); + final JMenu menu = findOrCreateMenu(parentMenuName); + final JMenu submenu = new JMenu(name); + submenu.setIcon(icon); + menu.add(submenu); + menuMap.put(name, submenu); + + final JMenuItem emptyItem = new JMenuItem(""); + emptyItem.setEnabled(false); + submenu.add(emptyItem); + + submenu.addMenuListener(new MenuListener() { + + @Override + public void menuSelected(MenuEvent e) { + submenu.removeAll(); + List items = handler.getAvailableItems(); + if (items == null || items.isEmpty()) + submenu.add(emptyItem); + else + for (String item : items) { + final JMenuItem menuItem = new JMenuItem(item); + menuItem.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + handler.onActionPerformed(menuItem.getText()); + } + }); + submenu.add(menuItem); + } + submenu.revalidate(); + submenu.repaint(); + submenu.doClick(); + } + + @Override + public void menuDeselected(MenuEvent e) {} + + @Override + public void menuCanceled(MenuEvent e) {} + }); + } + @Override public void executeBackgroundTask(final ITask task) { getGlassPane().setVisible(true); @@ -326,7 +373,7 @@ public class MainFrame extends JFrame implements ISwingUI { getGlassPane().setVisible(false); LOG.error("Background task execution interrupted", e); task.failed(e); - } + } } }; worker.execute(); @@ -365,7 +412,8 @@ public class MainFrame extends JFrame implements ISwingUI { fileName = "Untitled"; } String modified = (Boolean) params[1] ? " (modified)" : ""; - setTitle(String.format("DIYLC G3 version %s.%s - %s %s", plugInPort.getCurrentVersionNumber().getMinor(), plugInPort.getCurrentVersionNumber().getBuild(), fileName, modified)); + setTitle(String.format("DIYLC G3 version %s.%s - %s %s", plugInPort.getCurrentVersionNumber().getMinor(), + plugInPort.getCurrentVersionNumber().getBuild(), fileName, modified)); } } } diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/file/FileMenuPlugin.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/file/FileMenuPlugin.java index 06731d42..767b7d5f 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/file/FileMenuPlugin.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/file/FileMenuPlugin.java @@ -1,12 +1,16 @@ package org.diylc.swing.plugins.file; import java.util.EnumSet; +import java.util.List; +import org.apache.log4j.Logger; +import org.diylc.appframework.miscutils.ConfigurationManager; import org.diylc.common.EventType; import org.diylc.common.IPlugIn; import org.diylc.common.IPlugInPort; import org.diylc.images.IconLoader; import org.diylc.swing.ActionFactory; +import org.diylc.swing.IDynamicSubmenuHandler; import org.diylc.swing.ISwingUI; /** @@ -14,7 +18,9 @@ import org.diylc.swing.ISwingUI; * * @author Branislav Stojkovic */ -public class FileMenuPlugin implements IPlugIn { +public class FileMenuPlugin implements IPlugIn, IDynamicSubmenuHandler { + + private static final Logger LOG = Logger.getLogger(FileMenuPlugin.class); private static final String FILE_TITLE = "File"; private static final String TRACE_MASK_TITLE = "Trace Mask"; @@ -23,6 +29,7 @@ public class FileMenuPlugin implements IPlugIn { private TraceMaskDrawingProvider traceMaskDrawingProvider; private ISwingUI swingUI; + private IPlugInPort plugInPort; public FileMenuPlugin(ISwingUI swingUI) { super(); @@ -31,6 +38,7 @@ public class FileMenuPlugin implements IPlugIn { @Override public void connect(IPlugInPort plugInPort) { + this.plugInPort = plugInPort; this.drawingProvider = new ProjectDrawingProvider(plugInPort, false, true); this.traceMaskDrawingProvider = new TraceMaskDrawingProvider(plugInPort); @@ -40,6 +48,7 @@ public class FileMenuPlugin implements IPlugIn { swingUI.injectMenuAction(actionFactory.createImportAction(plugInPort, swingUI), FILE_TITLE); swingUI.injectMenuAction(actionFactory.createSaveAction(plugInPort, swingUI), FILE_TITLE); swingUI.injectMenuAction(actionFactory.createSaveAsAction(plugInPort, swingUI), FILE_TITLE); + swingUI.injectDynamicSubmenu("Recent Files", IconLoader.History.getIcon(), FILE_TITLE, this); swingUI.injectMenuAction(null, FILE_TITLE); swingUI.injectMenuAction(actionFactory.createExportPDFAction(drawingProvider, swingUI), FILE_TITLE); swingUI.injectMenuAction(actionFactory.createExportPNGAction(drawingProvider, swingUI), FILE_TITLE); @@ -62,4 +71,22 @@ public class FileMenuPlugin implements IPlugIn { public void processMessage(EventType eventType, Object... params) { // Do nothing. } + + // Dynamic menu for recent files + + @Override + public void onActionPerformed(String name) { + LOG.info("Openning recent file: " + name); + if (!plugInPort.allowFileAction()) { + LOG.info("Aborted openning recent file"); + return; + } + this.plugInPort.loadProjectFromFile(name); + } + + @SuppressWarnings("unchecked") + @Override + public List getAvailableItems() { + return (List) ConfigurationManager.getInstance().readObject(IPlugInPort.RECENT_FILES_KEY, null); + } } diff --git a/diylc/diylc-swing/update.xml b/diylc/diylc-swing/update.xml index 6ed442b9..5519639c 100644 --- a/diylc/diylc-swing/update.xml +++ b/diylc/diylc-swing/update.xml @@ -1646,6 +1646,10 @@ 2017-04-10 20:00:00.000 CET + + NEW_FEATURE + Added 'Recent Files' to the main menu to keep track of the previous 10 files + NEW_FEATURE Hold Control key to zoom with mouse wheel. Zooming tracks mouse cursor position