package nodebox.client;
import nodebox.ui.Platform;
import nodebox.util.FileUtils;
import javax.swing.*;
import javax.swing.undo.UndoManager;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
/**
* The main menu bar for the NodeBox application.
*/
public class NodeBoxMenuBar extends JMenuBar {
private NodeBoxDocument document;
private boolean enabled;
private static ArrayList<JMenu> recentFileMenus = new ArrayList<JMenu>();
private static Preferences recentFilesPreferences = Preferences.userRoot().node("/nodebox/recent");
private static Logger logger = Logger.getLogger("nodebox.client.NodeBoxMenuBar");
private JMenuItem showConsoleItem;
private UndoManager undoManager;
private UndoAction undoAction;
private RedoAction redoAction;
public NodeBoxMenuBar() {
this(null);
}
public NodeBoxMenuBar(NodeBoxDocument document) {
this.document = document;
if (document != null)
this.undoManager = document.getUndoManager();
// File menu
JMenu fileMenu = new JMenu("File");
fileMenu.add(new NewAction());
fileMenu.add(new OpenAction());
JMenu recentFileMenu = new JMenu("Open Recent");
recentFileMenus.add(recentFileMenu);
buildRecentFileMenu();
fileMenu.add(recentFileMenu);
fileMenu.add(new OpenExamplesAction());
fileMenu.addSeparator();
fileMenu.add(new CloseAction());
fileMenu.add(new SaveAction());
fileMenu.add(new SaveAsAction());
fileMenu.add(new RevertAction());
fileMenu.addSeparator();
if (Application.ENABLE_DEVICE_SUPPORT)
fileMenu.add(new DevicesAction());
fileMenu.add(new CodeLibrariesAction());
fileMenu.add(new DocumentPropertiesAction());
fileMenu.addSeparator();
fileMenu.add(new ExportAction());
fileMenu.add(new ExportRangeAction());
fileMenu.add(new ExportMovieAction());
if (!Platform.onMac()) {
fileMenu.addSeparator();
fileMenu.add(new ExitAction());
}
add(fileMenu);
// Edit menu
JMenu editMenu = new JMenu("Edit");
editMenu.add(undoAction = new UndoAction());
editMenu.add(redoAction = new RedoAction());
editMenu.addSeparator();
editMenu.add(new CutAction());
editMenu.add(new CopyAction());
editMenu.add(new PasteAction());
editMenu.addSeparator();
editMenu.add(new DeleteAction());
if (!Platform.onMac()) {
editMenu.addSeparator();
editMenu.add(new PreferencesAction());
}
add(editMenu);
// Node menu
JMenu nodeMenu = new JMenu("Node");
nodeMenu.add(new NewNodeAction());
nodeMenu.add(new ReloadAction());
nodeMenu.add(new PlayPauseAction());
nodeMenu.add(new RewindAction());
nodeMenu.add(new FullScreenAction());
//nodeMenu.add(newLibraryAction);
add(nodeMenu);
// Window menu
JMenu windowMenu = new JMenu("Window");
windowMenu.add(new MinimizeAction());
windowMenu.add(new ZoomAction());
showConsoleItem = windowMenu.add(new JCheckBoxMenuItem(new ShowConsoleAction()));
setShowConsoleChecked(Application.getInstance() != null && Application.getInstance().isConsoleOpened());
windowMenu.addSeparator();
windowMenu.add(new BringAllToFrontAction());
// TODO Add all active windows.
add(windowMenu);
// Help menu
JMenu helpMenu = new JMenu("Help");
helpMenu.add(new GettingStartedAction());
helpMenu.add(new HelpAndSupportAction());
helpMenu.add(new ReportAnIssueAction());
helpMenu.addSeparator();
if (!Platform.onMac()) {
helpMenu.add(new AboutAction());
}
helpMenu.add(new CheckForUpdatesAction());
helpMenu.add(new NodeboxSiteAction());
add(helpMenu);
}
public void updateUndoRedoState() {
undoAction.update();
redoAction.update();
}
public NodeBoxDocument getDocument() {
return document;
}
public boolean hasDocument() {
return document != null;
}
public static void addRecentFile(File f) {
File canonicalFile;
try {
canonicalFile = f.getCanonicalFile();
} catch (IOException e) {
logger.log(Level.WARNING, "Could not get canonical file name", e);
return;
}
ArrayList<File> fileList = getRecentFiles();
// If the recent file was already in the list, remove it and add it to the top.
// If the list did not contain the file, the remove call does nothing.
fileList.remove(canonicalFile);
fileList.add(0, canonicalFile);
writeRecentFiles(fileList);
buildRecentFileMenu();
}
public static void writeRecentFiles(ArrayList<File> fileList) {
int i = 1;
for (File f : fileList) {
try {
recentFilesPreferences.put(String.valueOf(i), f.getCanonicalPath());
} catch (IOException e) {
logger.log(Level.WARNING, "Could not get canonical file name", e);
return;
}
i++;
if (i > 10) break;
}
try {
recentFilesPreferences.flush();
} catch (BackingStoreException e) {
logger.log(Level.WARNING, "Could not write recent files preferences", e);
}
}
public static ArrayList<File> getRecentFiles() {
ArrayList<File> fileList = new ArrayList<File>(10);
for (int i = 1; i <= 10; i++) {
File file = new File(recentFilesPreferences.get(String.valueOf(i), ""));
if (file.exists()) {
fileList.add(file);
}
}
return fileList;
}
private static void buildRecentFileMenu() {
for (JMenu recentFileMenu : recentFileMenus) {
recentFileMenu.removeAll();
for (File f : getRecentFiles()) {
recentFileMenu.add(new OpenRecentAction(f));
}
}
}
public void setShowConsoleChecked(boolean checked) {
showConsoleItem.getModel().setSelected(checked);
}
//// Actions ////
public abstract class AbstractDocumentAction extends AbstractAction {
@Override
public boolean isEnabled() {
return NodeBoxMenuBar.this.hasDocument() && super.isEnabled();
}
}
public static class NewAction extends AbstractAction {
public NewAction() {
putValue(NAME, "New");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_N));
}
public void actionPerformed(ActionEvent e) {
Application.getInstance().createNewDocument();
}
}
public class OpenAction extends AbstractAction {
public OpenAction() {
putValue(NAME, "Open...");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_O));
}
public void actionPerformed(ActionEvent e) {
File chosenFile = FileUtils.showOpenDialog(getDocument(), NodeBoxDocument.lastFilePath, "ndbx", "NodeBox Document");
if (chosenFile != null) {
Application.getInstance().openDocument(chosenFile);
}
}
}
public static class OpenRecentAction extends AbstractAction {
private File file;
public OpenRecentAction(File file) {
this.file = file;
putValue(NAME, file.getName());
}
public void actionPerformed(ActionEvent e) {
Application.getInstance().openDocument(file);
}
}
public static class OpenExamplesAction extends AbstractAction {
public OpenExamplesAction() {
putValue(NAME, "Open Examples...");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_O, Event.SHIFT_MASK));
}
public void actionPerformed(ActionEvent e) {
Application.getInstance().openExamplesBrowser();
}
}
public class CloseAction extends AbstractDocumentAction {
public CloseAction() {
putValue(NAME, "Close");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_W));
}
public void actionPerformed(ActionEvent e) {
getDocument().close();
}
}
public class SaveAction extends AbstractDocumentAction {
public SaveAction() {
putValue(NAME, "Save");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_S));
}
public void actionPerformed(ActionEvent e) {
getDocument().save();
}
}
public class SaveAsAction extends AbstractDocumentAction {
public SaveAsAction() {
putValue(NAME, "Save As...");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_S, Event.SHIFT_MASK));
}
public void actionPerformed(ActionEvent e) {
getDocument().saveAs();
}
}
public class RevertAction extends AbstractDocumentAction {
public RevertAction() {
putValue(NAME, "Revert to Saved");
}
public void actionPerformed(ActionEvent e) {
getDocument().revert();
}
}
public class DevicesAction extends AbstractDocumentAction {
public DevicesAction() {
putValue(NAME, "Devices...");
}
public void actionPerformed(ActionEvent e) {
getDocument().showDevices();
}
}
public class CodeLibrariesAction extends AbstractDocumentAction {
public CodeLibrariesAction() {
putValue(NAME, "Code Libraries...");
}
public void actionPerformed(ActionEvent e) {
getDocument().showCodeLibraries();
}
}
public class DocumentPropertiesAction extends AbstractDocumentAction {
public DocumentPropertiesAction() {
putValue(NAME, "Document Properties...");
}
public void actionPerformed(ActionEvent e) {
getDocument().showDocumentProperties();
}
}
public class ExportAction extends AbstractDocumentAction {
public ExportAction() {
putValue(NAME, "Export...");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_E));
}
public void actionPerformed(ActionEvent e) {
getDocument().doExport();
}
}
public class ExportRangeAction extends AbstractDocumentAction {
public ExportRangeAction() {
putValue(NAME, "Export Range...");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_E, Event.SHIFT_MASK));
}
public void actionPerformed(ActionEvent e) {
getDocument().exportRange();
}
}
public class ExportMovieAction extends AbstractDocumentAction {
public ExportMovieAction() {
putValue(NAME, "Export Movie...");
}
public void actionPerformed(ActionEvent e) {
getDocument().exportMovie();
}
}
public static class ExitAction extends AbstractAction {
public ExitAction() {
putValue(NAME, "Exit");
}
public void actionPerformed(ActionEvent e) {
Application.getInstance().quit();
}
}
public class UndoAction extends AbstractDocumentAction {
private String undoText = UIManager.getString("AbstractUndoableEdit.undoText");
public UndoAction() {
putValue(NAME, undoText);
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_Z));
setEnabled(false);
}
public void actionPerformed(ActionEvent e) {
Component c = getDocument().getFocusOwner();
getDocument().undo();
updateUndoRedoState();
}
public void update() {
Component c = getDocument().getFocusOwner();
if (undoManager != null && undoManager.canUndo()) {
setEnabled(true);
putValue(Action.NAME, undoManager.getUndoPresentationName());
} else {
setEnabled(false);
putValue(Action.NAME, undoText);
}
}
}
public class RedoAction extends AbstractDocumentAction {
private String redoText = UIManager.getString("AbstractUndoableEdit.redoText");
public RedoAction() {
putValue(NAME, redoText);
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_Z, Event.SHIFT_MASK));
setEnabled(false);
}
public void actionPerformed(ActionEvent e) {
Component c = getDocument().getFocusOwner();
getDocument().redo();
updateUndoRedoState();
}
public void update() {
Component c = getDocument().getFocusOwner();
if (undoManager != null && undoManager.canRedo()) {
setEnabled(true);
putValue(Action.NAME, undoManager.getRedoPresentationName());
} else {
setEnabled(false);
putValue(Action.NAME, redoText);
}
}
}
public class CutAction extends AbstractDocumentAction {
public CutAction() {
putValue(NAME, "Cut");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_X));
}
public void actionPerformed(ActionEvent e) {
getDocument().cut();
}
}
public class CopyAction extends AbstractDocumentAction {
public CopyAction() {
putValue(NAME, "Copy");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_C));
}
public void actionPerformed(ActionEvent e) {
getDocument().copy();
}
}
public class PasteAction extends AbstractDocumentAction {
public PasteAction() {
putValue(NAME, "Paste");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_V));
}
public void actionPerformed(ActionEvent e) {
getDocument().paste();
}
}
public class DeleteAction extends AbstractDocumentAction {
public DeleteAction() {
putValue(NAME, "Delete");
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
}
public void actionPerformed(ActionEvent e) {
getDocument().deleteSelection();
}
}
public static class PreferencesAction extends AbstractAction {
public PreferencesAction() {
putValue(NAME, "Preferences");
}
public void actionPerformed(ActionEvent e) {
Application.getInstance().showPreferences();
}
}
public class NewNodeAction extends AbstractDocumentAction {
public NewNodeAction() {
putValue(NAME, "Create New Node...");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_N, Event.SHIFT_MASK));
}
public void actionPerformed(ActionEvent e) {
document.showNodeSelectionDialog();
}
}
public class ReloadAction extends AbstractDocumentAction {
public ReloadAction() {
putValue(NAME, "Reload");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_R));
}
public void actionPerformed(ActionEvent e) {
getDocument().reload();
}
}
public class PlayPauseAction extends AbstractDocumentAction {
public PlayPauseAction() {
putValue(NAME, "Play/Pause");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_P, Event.META_MASK));
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
getDocument().toggleAnimation();
}
}
public class RewindAction extends AbstractDocumentAction {
public RewindAction() {
putValue(NAME, "Rewind");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_P, Event.META_MASK | Event.SHIFT_MASK));
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
getDocument().doRewind();
}
}
public class FullScreenAction extends AbstractDocumentAction {
public FullScreenAction() {
putValue(NAME, "Full Screen");
putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_F, Event.META_MASK | Event.SHIFT_MASK));
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
getDocument().renderFullScreen();
}
}
// public class NewLibraryAction extends AbstractAction {
// public NewLibraryAction() {
// putValue(NAME, "New Library...");
// }
//
// public void actionPerformed(ActionEvent e) {
// String libraryName = JOptionPane.showInputDialog(NodeBoxDocument.this, "Enter the name for the new library", "Create New Library", JOptionPane.QUESTION_MESSAGE);
// if (libraryName == null || libraryName.trim().length() == 0) return;
// createNewLibrary(libraryName);
// }
// }
public class MinimizeAction extends AbstractDocumentAction {
public MinimizeAction() {
putValue(NAME, "Minimize");
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_M, KeyEvent.META_MASK));
}
public void actionPerformed(ActionEvent e) {
getDocument().setState(Frame.ICONIFIED);
}
}
public class ZoomAction extends AbstractDocumentAction {
public ZoomAction() {
putValue(NAME, "Zoom");
}
public void actionPerformed(ActionEvent e) {
// TODO: Implement
Toolkit.getDefaultToolkit().beep();
}
}
public class ShowConsoleAction extends AbstractDocumentAction {
public ShowConsoleAction() {
putValue(NAME, "Show Console");
}
public void actionPerformed(ActionEvent e) {
Application instance = Application.getInstance();
if (instance.isConsoleOpened())
instance.hideConsole();
else
instance.showConsole();
}
}
public class BringAllToFrontAction extends AbstractDocumentAction {
public BringAllToFrontAction() {
putValue(NAME, "Bring All to Front");
}
public void actionPerformed(ActionEvent e) {
// TODO: Implement
Toolkit.getDefaultToolkit().beep();
}
}
public static class AboutAction extends AbstractAction {
public AboutAction() {
super("About");
}
public void actionPerformed(ActionEvent e) {
Application.getInstance().showAbout();
}
}
public static class NodeboxSiteAction extends AbstractAction {
public NodeboxSiteAction() {
putValue(NAME, "NodeBox Site");
}
public void actionPerformed(ActionEvent e) {
Platform.openURL("http://nodebox.net/");
}
}
public static class GettingStartedAction extends AbstractAction {
public GettingStartedAction() {
super("Getting Started");
}
public void actionPerformed(ActionEvent e) {
Platform.openURL("http://nodebox.net/node/documentation/tutorial/getting-started.html");
}
}
public static class HelpAndSupportAction extends AbstractAction {
public HelpAndSupportAction() {
super("Help and Support");
}
public void actionPerformed(ActionEvent e) {
Platform.openURL("http://nodebox.net/node/documentation/");
}
}
public static class ReportAnIssueAction extends AbstractAction {
public ReportAnIssueAction() {
super("Report an Issue...");
}
public void actionPerformed(ActionEvent e) {
Platform.openURL("https://github.com/nodebox/nodebox/issues");
}
}
public static class CheckForUpdatesAction extends AbstractAction {
public CheckForUpdatesAction() {
super("Check for Updates...");
}
public void actionPerformed(ActionEvent e) {
Application.getInstance().getUpdater().checkForUpdates();
}
}
}