/* * @(#)MDIApplication.java * * Copyright (c) 1996-2010 The authors and contributors of JHotDraw. * You may not use, copy or modify this file, except in compliance with the * accompanying license terms. */ package org.jhotdraw.app; import javax.annotation.Nullable; import org.jhotdraw.app.action.app.AbstractPreferencesAction; import org.jhotdraw.app.action.window.ToggleToolBarAction; import org.jhotdraw.app.action.window.FocusWindowAction; import org.jhotdraw.app.action.window.ArrangeWindowsAction; import org.jhotdraw.app.action.window.MaximizeWindowAction; import org.jhotdraw.app.action.window.MinimizeWindowAction; import org.jhotdraw.app.action.file.SaveFileAsAction; import org.jhotdraw.app.action.file.SaveFileAction; import org.jhotdraw.app.action.file.PrintFileAction; import org.jhotdraw.app.action.file.NewFileAction; import org.jhotdraw.app.action.file.OpenFileAction; import org.jhotdraw.app.action.file.CloseFileAction; import org.jhotdraw.app.action.file.OpenDirectoryAction; import org.jhotdraw.app.action.file.ExportFileAction; import org.jhotdraw.app.action.edit.AbstractFindAction; import org.jhotdraw.app.action.edit.ClearSelectionAction; import org.jhotdraw.app.action.edit.CopyAction; import org.jhotdraw.app.action.edit.CutAction; import org.jhotdraw.app.action.edit.DeleteAction; import org.jhotdraw.app.action.edit.DuplicateAction; import org.jhotdraw.app.action.edit.PasteAction; import org.jhotdraw.app.action.edit.RedoAction; import org.jhotdraw.app.action.edit.SelectAllAction; import org.jhotdraw.app.action.edit.UndoAction; import org.jhotdraw.app.action.app.AboutAction; import org.jhotdraw.app.action.app.OpenApplicationFileAction; import org.jhotdraw.app.action.app.ExitAction; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import org.jhotdraw.gui.*; import org.jhotdraw.util.*; import org.jhotdraw.util.prefs.*; import java.awt.*; import java.awt.event.*; import java.beans.*; import java.io.*; import java.net.URI; import java.util.*; import java.util.prefs.*; import javax.swing.*; import javax.swing.event.*; import org.jhotdraw.app.action.*; import org.jhotdraw.app.action.file.ClearFileAction; import org.jhotdraw.app.action.file.ClearRecentFilesMenuAction; import org.jhotdraw.app.action.file.LoadDirectoryAction; import org.jhotdraw.app.action.file.LoadFileAction; import org.jhotdraw.app.action.file.NewWindowAction; import org.jhotdraw.net.URIUtil; /** * {@code MDIApplication} handles the lifecycle of multiple {@link View}s * using a Windows multiple document interface (MDI). * <p> * This user interface created by this application follows the guidelines given * in the * <a href="http://msdn.microsoft.com/en-us/library/aa511258.aspx" * >Windows User Experience Interaction Guidelines</a>. * <p> * An application consists of a parent {@code JFrame} which holds a {@code JDesktopPane}. * The views reside in {@code JInternalFrame}s inside of the {@code JDesktopPane}. * The parent frame also contains a menu bar, toolbars and palette windows for * the views. * <p> * The life cycle of the application is tied to the parent {@code JFrame}. * Closing the parent {@code JFrame} quits the application. * * The parent frame has the following standard menus: * <pre> * File Edit Window Help</pre> * * The <b>file menu</b> has the following standard menu items: * <pre> * Clear ({@link ClearFileAction#ID}}) * New ({@link NewFileAction#ID}}) * New Window ({@link NewWindowAction#ID}}) * Load... ({@link LoadFileAction#ID}}) * Open... ({@link OpenFileAction#ID}}) * Load Directory... ({@link LoadDirectoryAction#ID}}) * Open Directory... ({@link OpenDirectoryAction#ID}}) * Load Recent > "Filename" ({@link org.jhotdraw.app.action.file.LoadRecentFileAction#ID}) * Open Recent > "Filename" ({@link org.jhotdraw.app.action.file.OpenRecentFileAction#ID}) * - * Close ({@link CloseFileAction#ID}) * Save ({@link SaveFileAction#ID}) * Save As... ({@link SaveFileAsAction#ID}) * Export... ({@link ExportFileAction#ID}) * Print... ({@link PrintFileAction#ID}) * - * Exit ({@link ExitAction#ID}) * </pre> * * The <b>edit menu</b> has the following standard menu items: * <pre> * Undo ({@link UndoAction#ID}}) * Redo ({@link RedoAction#ID}}) * - * Cut ({@link CutAction#ID}}) * Copy ({@link CopyAction#ID}}) * Paste ({@link PasteAction#ID}}) * Duplicate ({@link DuplicateAction#ID}}) * Delete... ({@link DeleteAction#ID}}) * - * Select All ({@link SelectAllAction#ID}}) * Clear Selection ({@link ClearSelectionAction#ID}}) * - * Find ({@link AbstractFindAction#ID}}) * - * Settings ({@link AbstractPreferencesAction#ID}) * </pre> * * The <b>window menu</b> has the following standard menu items: * <pre> * Arrange Cascade ({@link ArrangeWindowsAction#CASCADE_ID}) * Arrange Vertical ({@link ArrangeWindowsAction#VERTICAL_ID}) * Arrange Horizontal ({@link ArrangeWindowsAction#HORIZONTAL_ID}) * - * "Filename" ({@link FocusWindowAction}) * - * "Toolbar" ({@link ToggleToolBarAction}) * </pre> * * The <b>help menu</b> has the following standard menu items: * <pre> * About ({@link AboutAction#ID}) * </pre> * * The menus provided by the {@code ApplicationModel} are inserted between * the file menu and the window menu. In case the application model supplies * a menu with the title "Edit" or "Help", the standard menu items are added * with a seperator to the end of the menu. * * * * @author Werner Randelshofer. * @version $Id$ */ public class MDIApplication extends AbstractApplication { private static final long serialVersionUID = 1L; private JFrame parentFrame; private JScrollPane scrollPane; private JMDIDesktopPane desktopPane; private Preferences prefs; private LinkedList<Action> toolBarActions; /** Creates a new instance. */ public MDIApplication() { } @Override public void init() { super.init(); initLookAndFeel(); prefs = PreferencesUtil.userNodeForPackage((getModel() == null) ? getClass() : getModel().getClass()); initLabels(); parentFrame = new JFrame(getName()); parentFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); parentFrame.setPreferredSize(new Dimension(600, 400)); desktopPane = new JMDIDesktopPane(); desktopPane.setTransferHandler(new DropFileTransferHandler()); scrollPane = new JScrollPane(); scrollPane.setViewportView(desktopPane); toolBarActions = new LinkedList<>(); setActionMap(createModelActionMap(model)); parentFrame.getContentPane().add( wrapDesktopPane(scrollPane, toolBarActions)); parentFrame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(final WindowEvent evt) { getAction(null, ExitAction.ID).actionPerformed( new ActionEvent(parentFrame, ActionEvent.ACTION_PERFORMED, "windowClosing")); } }); parentFrame.setJMenuBar(createMenuBar(null)); PreferencesUtil.installFramePrefsHandler(prefs, "parentFrame", parentFrame); parentFrame.setVisible(true); } protected ActionMap createModelActionMap(ApplicationModel mo) { ActionMap rootMap = new ActionMap(); rootMap.put(AboutAction.ID, new AboutAction(this)); rootMap.put(ExitAction.ID, new ExitAction(this)); rootMap.put(ClearRecentFilesMenuAction.ID, new ClearRecentFilesMenuAction(this)); rootMap.put(MaximizeWindowAction.ID, new MaximizeWindowAction(this, null)); rootMap.put(MinimizeWindowAction.ID, new MinimizeWindowAction(this, null)); rootMap.put(ArrangeWindowsAction.VERTICAL_ID, new ArrangeWindowsAction(desktopPane, Arrangeable.Arrangement.VERTICAL)); rootMap.put(ArrangeWindowsAction.HORIZONTAL_ID, new ArrangeWindowsAction(desktopPane, Arrangeable.Arrangement.HORIZONTAL)); rootMap.put(ArrangeWindowsAction.CASCADE_ID, new ArrangeWindowsAction(desktopPane, Arrangeable.Arrangement.CASCADE)); ActionMap moMap = mo.createActionMap(this, null); moMap.setParent(rootMap); return moMap; } @Override protected ActionMap createViewActionMap(View v) { ActionMap intermediateMap = new ActionMap(); intermediateMap.put(FocusWindowAction.ID, new FocusWindowAction(v)); ActionMap vMap = model.createActionMap(this, v); vMap.setParent(intermediateMap); intermediateMap.setParent(getActionMap(null)); return vMap; } @Override public void launch(String[] args) { super.launch(args); } @Override public void configure(String[] args) { System.setProperty("apple.laf.useScreenMenuBar", "false"); System.setProperty("com.apple.macos.useScreenMenuBar", "false"); System.setProperty("apple.awt.graphics.UseQuartz", "false"); System.setProperty("swing.aatext", "true"); } protected void initLookAndFeel() { try { String lafName = UIManager.getSystemLookAndFeelClassName(); UIManager.setLookAndFeel(lafName); } catch (Exception e) { e.printStackTrace(); } if (UIManager.getString("OptionPane.css") == null) { UIManager.put("OptionPane.css", "<head>" + "<style type=\"text/css\">" + "b { font: 13pt \"Dialog\" }" + "p { font: 11pt \"Dialog\"; margin-top: 8px }" + "</style>" + "</head>"); } } @Override public void show(final View v) { if (!v.isShowing()) { v.setShowing(true); final JInternalFrame f = new JInternalFrame(); f.setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE); f.setClosable(getAction(v, CloseFileAction.ID) != null); f.setMaximizable(true); f.setResizable(true); f.setIconifiable(false); f.setSize(new Dimension(400, 400)); updateViewTitle(v, f); PreferencesUtil.installInternalFramePrefsHandler(prefs, "view", f, desktopPane); Point loc = new Point(desktopPane.getInsets().left, desktopPane.getInsets().top); boolean moved; do { moved = false; for (View aView : views()) { if (aView != v && aView.isShowing() && SwingUtilities.getRootPane(aView.getComponent()).getParent(). getLocation().equals(loc)) { Point offset = SwingUtilities.convertPoint(SwingUtilities.getRootPane(aView.getComponent()), 0, 0, SwingUtilities.getRootPane(aView.getComponent()).getParent()); loc.x += Math.max(offset.x, offset.y); loc.y += Math.max(offset.x, offset.y); moved = true; break; } } } while (moved); f.setLocation(loc); //paletteHandler.add(f, v); f.addInternalFrameListener(new InternalFrameAdapter() { @Override public void internalFrameClosing(final InternalFrameEvent evt) { getAction(v, CloseFileAction.ID).actionPerformed( new ActionEvent(f, ActionEvent.ACTION_PERFORMED, "windowClosing")); } @Override public void internalFrameClosed(final InternalFrameEvent evt) { v.stop(); } }); v.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { String name = evt.getPropertyName(); if (name == View.HAS_UNSAVED_CHANGES_PROPERTY || name == View.URI_PROPERTY) { updateViewTitle(v, f); } } }); f.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { String name = evt.getPropertyName(); if ("selected".equals(name)) { if (evt.getNewValue().equals(Boolean.TRUE)) { setActiveView(v); } else { if (v == getActiveView()) { setActiveView(null); } } } } }); //f.setJMenuBar(createMenuBar(v)); f.getContentPane().add(v.getComponent()); f.setVisible(true); desktopPane.add(f); if (desktopPane.getComponentCount() == 1) { try { f.setMaximum(true); } catch (PropertyVetoException ex) { // ignore veto } } f.toFront(); try { f.setSelected(true); } catch (PropertyVetoException e) { // Don't care. } v.getComponent().requestFocusInWindow(); v.start(); } } @Override public void hide(View v) { if (v.isShowing()) { JInternalFrame f = (JInternalFrame) SwingUtilities.getRootPane(v.getComponent()).getParent(); if (getActiveView() == v) { setActiveView(null); } f.setVisible(false); f.remove(v.getComponent()); // Setting the JMenuBar to null triggers action disposal of // actions in the openRecentMenu and the windowMenu. This is // important to prevent memory leaks. f.setJMenuBar(null); desktopPane.remove(f); f.dispose(); } } @Override public boolean isSharingToolsAmongViews() { return true; } @Override public Component getComponent() { return parentFrame; } /** * Returns the wrapped desktop pane. */ protected Component wrapDesktopPane(Component c, LinkedList<Action> toolBarActions) { if (getModel() != null) { int id = 0; for (JToolBar tb : new ReversedList<>(getModel().createToolBars(this, null))) { id++; JPanel panel = new JPanel(new BorderLayout()); panel.add(tb, BorderLayout.NORTH); panel.add(c, BorderLayout.CENTER); c = panel; PreferencesUtil.installToolBarPrefsHandler(prefs, "toolbar." + id, tb); toolBarActions.addFirst(new ToggleToolBarAction(tb, tb.getName())); } } return c; } /** * Creates a menu bar. */ protected JMenuBar createMenuBar(@Nullable View v) { JMenuBar mb = new JMenuBar(); // Get menus from application model JMenu fileMenu = null; JMenu editMenu = null; JMenu helpMenu = null; JMenu viewMenu = null; JMenu windowMenu = null; String fileMenuText = labels.getString("file.text"); String editMenuText = labels.getString("edit.text"); String viewMenuText = labels.getString("view.text"); String windowMenuText = labels.getString("window.text"); String helpMenuText = labels.getString("help.text"); LinkedList<JMenu> ll = new LinkedList<>(); getModel().getMenuBuilder().addOtherMenus(ll, this, v); for (JMenu mm : ll) { String text = mm.getText(); if (text == null) { mm.setText("-null-"); } else if (text.equals(fileMenuText)) { fileMenu = mm; continue; } else if (text.equals(editMenuText)) { editMenu = mm; continue; } else if (text.equals(viewMenuText)) { viewMenu = mm; continue; } else if (text.equals(windowMenuText)) { windowMenu = mm; continue; } else if (text.equals(helpMenuText)) { helpMenu = mm; continue; } mb.add(mm); } // Create missing standard menus if (fileMenu == null) { fileMenu = createFileMenu(v); } if (editMenu == null) { editMenu = createEditMenu(v); } if (viewMenu == null) { viewMenu = createViewMenu(v); } if (windowMenu == null) { windowMenu = createWindowMenu(v); } if (helpMenu == null) { helpMenu = createHelpMenu(v); } // Insert standard menus into menu bar if (fileMenu != null) { mb.add(fileMenu, 0); } if (editMenu != null) { mb.add(editMenu, Math.min(1, mb.getComponentCount())); } if (viewMenu != null) { mb.add(viewMenu, Math.min(2, mb.getComponentCount())); } if (windowMenu != null) { mb.add(windowMenu); } if (helpMenu != null) { mb.add(helpMenu); } return mb; } @Override @Nullable public JMenu createFileMenu(View view) { JMenu m; m = new JMenu(); labels.configureMenu(m, "file"); MenuBuilder mb = model.getMenuBuilder(); mb.addClearFileItems(m, this, view); mb.addNewFileItems(m, this, view); mb.addNewWindowItems(m, this, view); mb.addLoadFileItems(m, this, view); mb.addOpenFileItems(m, this, view); if (getAction(view, LoadFileAction.ID) != null ||// getAction(view, OpenFileAction.ID) != null ||// getAction(view, LoadDirectoryAction.ID) != null ||// getAction(view, OpenDirectoryAction.ID) != null) { m.add(createOpenRecentFileMenu(view)); } maybeAddSeparator(m); mb.addCloseFileItems(m, this, view); mb.addSaveFileItems(m, this, view); mb.addExportFileItems(m, this, view); mb.addPrintFileItems(m, this, view); mb.addOtherFileItems(m, this, view); maybeAddSeparator(m); mb.addExitItems(m, this, view); return (m.getItemCount() == 0) ? null : m; } /** * Updates the title of a view and displays it in the given frame. * * @param v The view. * @param f The frame. */ protected void updateViewTitle(View v, JInternalFrame f) { URI uri = v.getURI(); String title; if (uri == null) { title = labels.getString("unnamedFile"); } else { title = URIUtil.getName(uri); } if (v.hasUnsavedChanges()) { title += "*"; } v.setTitle(labels.getFormatted("internalFrame.title", title, getName(), v.getMultipleOpenId())); f.setTitle(v.getTitle()); } @Override @Nullable public JMenu createViewMenu(final View view) { JMenu m = new JMenu(); labels.configureMenu(m, "view"); MenuBuilder mb = model.getMenuBuilder(); mb.addOtherViewItems(m, this, view); return (m.getItemCount() > 0) ? m : null; } @Override @Nullable public JMenu createWindowMenu(View view) { JMenu m; JMenuItem mi; m = new JMenu(); JMenu windowMenu = m; labels.configureMenu(m, "window"); addAction(m, view, ArrangeWindowsAction.CASCADE_ID); addAction(m, view, ArrangeWindowsAction.VERTICAL_ID); addAction(m, view, ArrangeWindowsAction.HORIZONTAL_ID); maybeAddSeparator(m); for (View pr : views()) { addAction(m, view, FocusWindowAction.ID); } if (toolBarActions.size() > 0) { maybeAddSeparator(m); for (Action a : toolBarActions) { JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(a); ActionUtil.configureJCheckBoxMenuItem(cbmi, a); addMenuItem(m, cbmi); } } MenuBuilder mb = model.getMenuBuilder(); mb.addOtherWindowItems(m, this, view); addPropertyChangeListener(new WindowMenuHandler(windowMenu, view)); return (m.getItemCount() == 0) ? null : m; } @Override @Nullable public JMenu createEditMenu(View view) { JMenu m; JMenuItem mi; Action a; m = new JMenu(); labels.configureMenu(m, "edit"); MenuBuilder mb = model.getMenuBuilder(); mb.addUndoItems(m, this, view); maybeAddSeparator(m); mb.addClipboardItems(m, this, view); maybeAddSeparator(m); mb.addSelectionItems(m, this, view); maybeAddSeparator(m); mb.addFindItems(m, this, view); maybeAddSeparator(m); mb.addOtherEditItems(m, this, view); maybeAddSeparator(m); mb.addPreferencesItems(m, this, view); removeTrailingSeparators(m); return (m.getItemCount() == 0) ? null : m; } @Override public JMenu createHelpMenu(View view) { JMenu m = new JMenu(); labels.configureMenu(m, "help"); MenuBuilder mb = model.getMenuBuilder(); mb.addHelpItems(m, this, view); mb.addAboutItems(m, this, view); return (m.getItemCount() == 0) ? null : m; } /** Updates the menu items in the "Window" menu. */ private class WindowMenuHandler implements PropertyChangeListener { private JMenu windowMenu; @Nullable private View view; public WindowMenuHandler(JMenu windowMenu, @Nullable View view) { this.windowMenu = windowMenu; this.view = view; MDIApplication.this.addPropertyChangeListener(this); updateWindowMenu(); } @Override public void propertyChange(PropertyChangeEvent evt) { String name = evt.getPropertyName(); if (name == VIEW_COUNT_PROPERTY || name == "paletteCount") { updateWindowMenu(); } } protected void updateWindowMenu() { JMenu m = windowMenu; m.removeAll(); m.add(getAction(view, ArrangeWindowsAction.CASCADE_ID)); m.add(getAction(view, ArrangeWindowsAction.VERTICAL_ID)); m.add(getAction(view, ArrangeWindowsAction.HORIZONTAL_ID)); m.addSeparator(); for (View pr: views()) { if (getAction(pr, FocusWindowAction.ID) != null) { m.add(getAction(pr, FocusWindowAction.ID)); } } if (toolBarActions.size() > 0) { m.addSeparator(); for (Action a : toolBarActions) { JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(a); ActionUtil.configureJCheckBoxMenuItem(cbmi, a); m.add(cbmi); } } } } /** This transfer handler opens a new view for each dropped file. */ private class DropFileTransferHandler extends TransferHandler { private static final long serialVersionUID = 1L; @Override public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) { Action a = getAction(null, OpenApplicationFileAction.ID); if (a == null) { return false; } for (DataFlavor f : transferFlavors) { if (f.isFlavorJavaFileListType()) { return true; } } return false; } @Override public boolean importData(JComponent comp, Transferable t) { Action a = getAction(null, OpenApplicationFileAction.ID); if (a == null) { return false; } try { @SuppressWarnings("unchecked") java.util.List<File> files = (java.util.List<File>) t.getTransferData(DataFlavor.javaFileListFlavor); for (final File f : files) { a.actionPerformed(new ActionEvent(desktopPane, ActionEvent.ACTION_PERFORMED, f.toString())); } return true; } catch (UnsupportedFlavorException | IOException ex) { return false; } } } }