/* * Copyright 2003-2010 Tufts University Licensed under the * Educational Community License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. */ package tufts.vue.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Event; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.io.File; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.List; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JCheckBoxMenuItem; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.KeyStroke; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import tufts.Util; import tufts.vue.Actions; import tufts.vue.ActiveInstance; import tufts.vue.DEBUG; import tufts.vue.Images; import tufts.vue.JavaAnalysisPanel; import tufts.vue.LWComponent; import tufts.vue.LWImage; import tufts.vue.LWLink; import tufts.vue.LWMap; import tufts.vue.LWNode; import tufts.vue.LWPathway; import tufts.vue.LWPathwayList; import tufts.vue.LWPortal; import tufts.vue.LWSelection; import tufts.vue.LWText; import tufts.vue.LayoutAction; import tufts.vue.MapTabbedPane; import tufts.vue.MapViewer; import tufts.vue.PresentationTool; import tufts.vue.RecentlyOpenedFilesManager; import tufts.vue.Resource; import tufts.vue.VUE; import tufts.vue.VueAction; import tufts.vue.VueApplet; import tufts.vue.VueConstants; import tufts.vue.VueResources; import tufts.vue.VueTool; import tufts.vue.VueToolbarController; import tufts.vue.VueUtil; import tufts.vue.action.AboutAction; import tufts.vue.action.AnalyzeCM; import tufts.vue.action.CreateCM; import tufts.vue.action.ExitAction; import tufts.vue.action.OpenAction; import tufts.vue.action.OpenURLAction; import tufts.vue.action.PrintAction; import tufts.vue.action.PublishActionFactory; import tufts.vue.action.SaveAction; import tufts.vue.action.ShortcutsAction; import tufts.vue.action.ShowURLAction; import edu.tufts.vue.dataset.QuickImportAction; import edu.tufts.vue.dsm.impl.VueDataSourceManager; import edu.tufts.vue.ontology.action.OntologyControlsOpenAction; import edu.tufts.vue.ontology.ui.OntologyBrowser; import edu.tufts.vue.preferences.VuePrefEvent; import edu.tufts.vue.preferences.VuePrefListener; import edu.tufts.vue.preferences.implementations.BooleanPreference; /** * The main VUE application menu bar. * * @version $Revision: 1.176 $ / $Date: 2010-03-11 21:15:01 $ / $Author: mike $ * @author Scott Fraize */ public class VueMenuBar extends javax.swing.JMenuBar implements java.awt.event.FocusListener { private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(VueMenuBar.class); public static VueMenuBar RootMenuBar; private static JCheckBoxMenuItem fullScreenToolbarItem = null; public static SaveAction saveAction = null; public static SaveAction saveAsAction = null; public static JMenu importMenu = null; public static JMenu publishMenu = null; public static PrintAction printAction = null; public static JMenu pdfExportMenu = null; public static JMenu transformMenu = null; public static JMenu arrangeMenu = null; public static JMenu alignMenu = null; public static JMenu layoutMenu = null; public static JMenu linkMenu = null; public static JMenu playbackMenu = null; public boolean isMenuEnableFontFlg = false; // this may be created multiple times as a workaround for the inability // to support a single JMenuBar for the whole application on the Mac /* public VueMenuBar() { this(VUE.ToolWindows); } */ /* public void paint(Graphics g) { System.err.println("\nVueMenuBar: paint"); } */ private static class VueMenu extends JMenu { private boolean unadjusted = true; VueMenu(String name) { super(name); /* on the mac this works fine on windows the menus paint behind the dockwindows * so we'll install our own UI which creates heavyweight popups. */ if (Util.isWindowsPlatform() ||Util.isUnixPlatform()) this.getPopupMenu().setUI(new VuePopupMenuUI()); } @Override public void addNotify() { if (unadjusted) { GUI.adjustMenuIcons(this); unadjusted = false; } super.addNotify(); } // @Override // protected JMenuItem createActionComponent(Action a) { // JMenuItem mi; // if (false && a == Actions.ToggleAutoZoom) { // mi = new JCheckBoxMenuItem((String)a.getValue(Action.NAME), (Icon)a.getValue(Action.SMALL_ICON)); // } else { // //mi = new JMenuItem((String)a.getValue(Action.NAME), (Icon)a.getValue(Action.SMALL_ICON)); // mi = new JMenuItem(a); // // protected PropertyChangeListener createActionPropertyChangeListener(Action a) { // // PropertyChangeListener pcl = createActionChangeListener(this); // // if (pcl == null) { // // pcl = super.createActionPropertyChangeListener(a); // // } // // return pcl; // // } // // }; // } // mi.setHorizontalTextPosition(JButton.TRAILING); // mi.setVerticalTextPosition(JButton.CENTER); // mi.setEnabled(a.isEnabled()); // return mi; // } } private static JMenu makeMenu(String name) { return new VueMenu(name); // if (Util.isMacPlatform()) // return new JMenu(name); // else // return new VueMenu(name); } // private static class MenuToggleItem extends JCheckBoxMenuItem // { // MenuToggleItem(VueAction va) { // super(va); // va.trackToggler(this); // } // // @Override // // public final void setSelected(boolean selected) { // // if (DEBUG.EVENTS) Log.debug(GUI.name(this) + "; setSelected " + selected + " (isSelected=" + isSelected() + ")"); // // super.setSelected(selected); // // } // } private static JCheckBoxMenuItem makeCheckBox(Action a) { return makeCheckBox((VueAction) a); } private static JCheckBoxMenuItem makeCheckBox(VueAction a) { return makeCheckBox(a, a.isToggler() ? a.getToggleState() : false); } private static JCheckBoxMenuItem makeCheckBox(VueAction a, boolean initialState) { final JCheckBoxMenuItem item = new JCheckBoxMenuItem(a); //final JCheckBoxMenuItem item = new JCheckBoxMenuItem(a.getPermanentActionName()); if (a.getKeyStroke() != null) item.setAccelerator(a.getKeyStroke()); item.setSelected(initialState); return item; } private static JCheckBoxMenuItem makeLinkedCheckBox(VueAction a) { final JCheckBoxMenuItem item = makeCheckBox(a); a.trackToggler(item); return item; } private final JCheckBoxMenuItem viewFullScreen = makeLinkedCheckBox(Actions.ToggleFullScreen); private final JCheckBoxMenuItem viewSuperScreen = new JCheckBoxMenuItem(Actions.SuperScreen) { { Actions.SuperScreen.trackToggler(this); } @Override public void setEnabled(boolean b) { //Log.debug("SS SET ENABLED " + b); super.setEnabled(b); setVisible(b); } }; private final JCheckBoxMenuItem viewKiosk = new JCheckBoxMenuItem(Actions.KioskScreen) { { Actions.SuperScreen.trackToggler(this); } @Override public void setEnabled(boolean b) { //Log.debug("SS SET ENABLED " + b); super.setEnabled(b); setVisible(b); } }; Object[] arguments = { new Date(), "TestFile" }; private final JMenu windowMenu = makeMenu(VueResources.getFormatMessage(arguments, "menu.windows")); //private final OntologyControlsOpenAction ontcontrls = new OntologyControlsOpenAction(VueResources.getString("menu.windows.ontologies")); private static final java.util.concurrent.atomic.AtomicInteger PublishRebuildCount = new java.util.concurrent.atomic.AtomicInteger(); public VueMenuBar() { final int metaMask = tufts.vue.Actions.COMMAND; //////////////////////////////////////////////////////////////////////////////////// // Initialize Top Level Menus //////////////////////////////////////////////////////////////////////////////////// /* Need to pull the menu strings from the resource bundle*/ final JMenu fileMenu = makeMenu(VueResources.getString("menu.file")); final JMenu recentlyOpenedMenu = makeMenu(VueResources.getString("menu.windiws.openrecent")); final JMenu editMenu = makeMenu(VueResources.getString("menu.edit")); final JMenu viewMenu = makeMenu(VueResources.getString("menu.view")); final JMenu formatMenu = makeMenu(VueResources.getString("menu.format")); transformMenu = makeMenu(VueResources.getString("menu.font")); transformMenu.setEnabled(false); arrangeMenu = makeMenu(VueResources.getString("menu.arrange")); arrangeMenu.setEnabled(false); final JMenu contentMenu = makeMenu(VueResources.getString("menu.content")); final JMenu presentationMenu = makeMenu(VueResources.getString("menu.pathway.label")); final JMenu analysisMenu = makeMenu(VueResources.getString("menu.analysis")); alignMenu = makeMenu(VueResources.getString("menu.align")); alignMenu.setEnabled(false); layoutMenu = makeMenu(VueResources.getString("menu.layout")); layoutMenu.setEnabled(false); linkMenu = makeMenu(VueResources.getString("menu.link")); linkMenu.setEnabled(false); final JMenu helpMenu = add(makeMenu(VueResources.getString("menu.help"))); // final JMenu slidePreviewMenu = new JMenu("Slide preview"); final JMenu notesMenu = new JMenu(VueResources.getString("menu.pathways.handoutsandnotes")); playbackMenu = new JMenu(VueResources.getString("menu.playback.play.label")); playbackMenu.setEnabled(false); //////////////////////////////////////////////////////////////////////////////////// // Initialize Actions //////////////////////////////////////////////////////////////////////////////////// final JMenuItem splitScreenItem = new JCheckBoxMenuItem(Actions.ToggleSplitScreen); final JMenuItem toggleLinksItem = new JCheckBoxMenuItem(Actions.ToggleLinks); final JMenuItem toggleSlideIconsItem = makeLinkedCheckBox(Actions.ToggleSlideIcons); final JMenuItem togglePruningItem = new JCheckBoxMenuItem(Actions.TogglePruning); final JMenuItem toggleAutoZoomItem = makeCheckBox(Actions.ToggleAutoZoom); toggleAutoZoomItem.setSelected(Actions.ToggleAutoZoom.getToggleState()); //////////////////////////////////////////////////////////////////////////////////// // Initialize Actions //////////////////////////////////////////////////////////////////////////////////// saveAction = new SaveAction(VueResources.getString("menu.windows.save"), false); saveAction.setEnabled(false); saveAsAction = new SaveAction(VueResources.getString("menu.windows.saveas")); saveAsAction.setEnabled(false); //final SaveAction exportAction = new SaveAction("Export ...",true,true); final OpenAction openAction = new OpenAction(VueResources.getString("menu.windows.open")); final OpenURLAction openFromURLAction = new OpenURLAction(VueResources.getString("menu.windows.openfromurl")); final ExitAction exitAction = new ExitAction(Util.isMacPlatform() ? VueResources.getString("menu.windows.quit") : VueResources.getString("menu.windows.exit")); importMenu = makeMenu(VueResources.getString("menu.file.import")); final QuickImportAction quickImport = new QuickImportAction(); publishMenu = makeMenu(VueResources.getString("menu.windows.publish")); //final JMenu publishAction = Publish.getPublishMenu(); final CreateCM createCMAction = new CreateCM(VueResources.getString("menu.windows.conanalysis")); final AnalyzeCM analyzeCMAction = new AnalyzeCM(VueResources.getString("menu.windows.mergemaps")); // Actions added by the power team printAction = PrintAction.getPrintAction(); printAction.setEnabled(false); // final PDFTransform pdfAction = new PDFTransform("PDF"); /* final HTMLConversion htmlAction = new HTMLConversion("HTML"); final ImageConversion imageAction = new ImageConversion("JPEG"); final ImageMap imageMap = new ImageMap("IMAP"); final SVGConversion svgAction = new SVGConversion("SVG"); final XMLView xmlAction = new XMLView("XML View");*/ final RecentlyOpenedFilesManager rofm = RecentlyOpenedFilesManager.getInstance(); rofm.getPreference().addVuePrefListener(new VuePrefListener() { public void preferenceChanged(VuePrefEvent prefEvent) { rebuildRecentlyOpenedItems(fileMenu, recentlyOpenedMenu, rofm); } }); // create menu for dataset import // Note: now that repositories are configured after loading, this gets called // each time a new repository completes its configuration, which is a bit // overkill for the normal case where everything loads immediately without // problem -- we're resetting the menu once for every data source. Importantly, // this code is idempotent, so it can be re-run as many times as needed. It // must stay this way. edu.tufts.vue.dsm.impl.VueDataSourceManager.getInstance().addDataSourceListener(new edu.tufts.vue.dsm.DataSourceListener() { public void changed(edu.tufts.vue.dsm.DataSource[] dataSource, Object state, edu.tufts.vue.dsm.DataSource changed) { if (DEBUG.DR) Log.debug("data sources changed; " + state); if (state == VueDataSourceManager.DS_CONFIGURED) { // Rebuild the publish menu. Note that we don't wait for just // DS_ALL_CONFIGURED, as it's possible that may never come } else if (state == VueDataSourceManager.DS_ALL_CONFIGURED && VUE.BLOCKING_OSID_LOAD) { // Rebuild the publish menu. If OSID load is blocking, we won't get // any DS_CONFIGURED events -- only the DS_ALL_CONFIGURED at the // end. } else { return; } //final int rebuildCount = PublishRebuildCount.incrementAndGet(); final List<Action> typeActions = new java.util.ArrayList(); int count = 0; boolean sakaiFlag = false; boolean fedoraFlag = false; //publishMenu.removeAll(); final List<Action> publishActions = new java.util.ArrayList(); for(int i =0;i<dataSource.length;i++) { try { final org.osid.repository.Repository r = dataSource[i].getRepository(); if (r == null) { if (DEBUG.DR) Log.debug("null repository in " + dataSource[i]); continue; } if (r.getType().isEqual(edu.tufts.vue.dsm.DataSourceTypes.FEDORA_REPOSITORY_TYPE)) { if(!fedoraFlag) { typeActions.add(PublishActionFactory.createPublishAction(edu.tufts.vue.dsm.DataSourceTypes.FEDORA_REPOSITORY_TYPE)); fedoraFlag = true; if (DEBUG.DR) Log.debug("PUBLISHABLE: " + dataSource[i]); } publishActions.add(PublishActionFactory.createPublishAction(dataSource[i])); count++; } else if (r.getType().isEqual(edu.tufts.vue.dsm.DataSourceTypes.SAKAI_REPOSITORY_TYPE)) { if(!sakaiFlag) { typeActions.add(PublishActionFactory.createPublishAction(edu.tufts.vue.dsm.DataSourceTypes.SAKAI_REPOSITORY_TYPE)); sakaiFlag = true; if (DEBUG.DR) Log.debug("PUBLISHABLE: " + dataSource[i]); } publishActions.add(PublishActionFactory.createPublishAction(dataSource[i])); count++; } } catch(org.osid.repository.RepositoryException ex) { Log.error("changed:", ex); } } // todo: could create an GUI MostRecentOnly Runnable with an exec that is only called // if it's the most recent -- would be very handy -- tho how to collate with various // types? Would need to have a HashMap of all class instances (the inner anonomous // one generated) containing atomic counts for each instance -- eithar that or require // passing in a constant AtomicLong for each instancing group -- that'd be easier. final int rebuildCount = PublishRebuildCount.incrementAndGet(); // technically, would be most efficient to increment this counter // above at the moment we know we'll be issuing a new AWT invocation, // tho any exception between then and here would have to be caught // to ensure the menu was rebuilt no matter what. GUI.invokeAfterAWT(new Runnable() { public void run() { final int curCount = PublishRebuildCount.get(); if (rebuildCount < curCount) { if (DEBUG.INIT || DEBUG.DR) Log.debug("skipping rebuild; runnable count " + rebuildCount + " < " + curCount); return; } else { Log.info("rebuilding with " + publishActions.size() + " publishable data source(s) after " + curCount + " updates"); } // rebuild the menus on the AWT thread for thread safety publishMenu.removeAll(); if (typeActions.size() > 0) { for (Action a : typeActions) publishMenu.add(a); if (publishActions.size() > 0) { publishMenu.addSeparator(); for (Action a : publishActions) publishMenu.add(a); } } else { // no accelerator key for this menu publishMenu.add(createWindowItem(VUE.getContentDock(), 0, VueResources.getString("menu.publisher.resourcewindow"))); } publishMenu.setEnabled(false); fileMenu.remove(publishMenu); fileMenu.add(publishMenu,11); }}); } }); //////////////////////////////////////////////////////////////////////////////////// // Initializing DEBUG code //////////////////////////////////////////////////////////////////////////////////// if (false && DEBUG.Enabled) { // THIS CODE IS TRIGGERING THE TIGER ARRAY BOUNDS BUG: // we're hitting bug in java (1.4.2, 1.5) on Tiger (OSX 10.4.2) here // (apple.laf.ScreenMenuBar array index out of bounds exception) JButton u = new JButton(Actions.Undo); JButton r = new JButton(Actions.Redo); JButton p = new JButton(printAction); JButton v = new JButton(printAction); v.setText(VueResources.getString("vuemenubar.printvisible.tooltip")); u.setBackground(Color.white); r.setBackground(Color.white); add(u).setFocusable(false); add(r).setFocusable(false); add(p).setFocusable(false); add(v).setFocusable(false); //menuBar.add(new tufts.vue.gui.VueButton(Actions.Undo)).setFocusable(false); // not picking up icon yet... } /* if (false && DEBUG.Enabled) { // THIS CODE IS TRIGGERING THE TIGER ARRAY BOUNDS BUG (see above) JMenu exportMenu = add(makeMenu("Export")); exportMenu.add(htmlAction); // exportMenu.add(pdfAction); exportMenu.add(imageAction); exportMenu.add(svgAction); exportMenu.add(xmlAction); exportMenu.add(imageMap); } */ //////////////////////////////////////////////////////////////////////////////////// // Build File Menu //////////////////////////////////////////////////////////////////////////////////// fileMenu.add(Actions.NewMap); fileMenu.add(openAction).setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, metaMask)); fileMenu.add(openFromURLAction); fileMenu.add(Actions.CloseMap); fileMenu.addSeparator(); fileMenu.add(saveAction).setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, metaMask)); fileMenu.add(saveAsAction).setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, metaMask+Event.SHIFT_MASK)); if (VUE.isApplet() && VueApplet.isZoteroApplet()) { fileMenu.add(Actions.SaveCopyToZotero); } fileMenu.add(Actions.Revert); Actions.Revert.setEnabled(false); fileMenu.addSeparator(); // fileMenu.add(Actions.ZoteroAction); //fileMenu.add(exportAction); fileMenu.add(quickImport); //publishMenu.setEnabled(false); if (!VUE.isApplet()) fileMenu.add(publishMenu); pdfExportMenu = new JMenu(VueResources.getString("menu.windows.exporthandouts")); pdfExportMenu.setEnabled(false); //pdfExportMenu.add(Actions.MapAsPDF); final JMenuItem fullPageSlideNotesItem = new JMenuItem(Actions.FullPageSlideNotes); final JMenuItem slides8PerPageItem = new JMenuItem(Actions.Slides8PerPage); final JMenuItem speakerNotes1Item = new JMenuItem(Actions.SpeakerNotes1); final JMenuItem speakerNotes4Item = new JMenuItem(Actions.SpeakerNotes4); final JMenuItem audienceNotesItem = new JMenuItem(Actions.AudienceNotes); final JMenuItem speakerNotesOutlineItem = new JMenuItem(Actions.SpeakerNotesOutline); final JMenuItem nodeNotes4Item = new JMenuItem(Actions.NodeNotes4); final JMenuItem nodeNotesOutlineItem = new JMenuItem(Actions.NodeNotesOutline); pdfExportMenu.add(fullPageSlideNotesItem); pdfExportMenu.add(slides8PerPageItem); pdfExportMenu.add(speakerNotes1Item); pdfExportMenu.add(speakerNotes4Item); pdfExportMenu.add(audienceNotesItem); pdfExportMenu.add(speakerNotesOutlineItem); pdfExportMenu.addSeparator(); pdfExportMenu.add(nodeNotes4Item); pdfExportMenu.add(nodeNotesOutlineItem); fileMenu.addMenuListener(new MenuListener(){ public void menuCanceled(MenuEvent e) {/* no op */} public void menuDeselected(MenuEvent e) {/*no op */} public void menuSelected(MenuEvent e) {handleActivation();} private void handleActivation() { LWPathway p =VUE.getActivePathway(); if (p == null || p.length() == 0) { fullPageSlideNotesItem.setEnabled(false); slides8PerPageItem.setEnabled(false); speakerNotes1Item.setEnabled(false); speakerNotes4Item.setEnabled(false); audienceNotesItem.setEnabled(false); speakerNotesOutlineItem.setEnabled(false); } //pdfExportMenu.setEnabled(false); else { fullPageSlideNotesItem.setEnabled(true); slides8PerPageItem.setEnabled(true); speakerNotes1Item.setEnabled(true); speakerNotes4Item.setEnabled(true); audienceNotesItem.setEnabled(true); speakerNotesOutlineItem.setEnabled(true); } if (VUE.getActiveMap()!=null && VUE.getActiveMap().hasContent()) { nodeNotesOutlineItem.setEnabled(true); nodeNotes4Item.setEnabled(true); } else { nodeNotesOutlineItem.setEnabled(false); nodeNotes4Item.setEnabled(false); } //pdfExportMenu.setEnabled(true); }}); // pdfExportMenu.add(Actions.NodeNotesOutline); fileMenu.addSeparator(); fileMenu.add(printAction).setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, metaMask)); fileMenu.add(printAction).setText(VueResources.getString("menu.windows.printvisible")); if (!VUE.isApplet()) fileMenu.add(pdfExportMenu); rebuildRecentlyOpenedItems(fileMenu, recentlyOpenedMenu, rofm); if (VUE.isApplet() || (VUE.isSystemPropertyTrue("apple.laf.useScreenMenuBar") && GUI.isMacAqua())) { // Do NOT add quit to the file menu. // Either we're an applet w/no quit, or it's already in the mac application menu bar. // FYI, MRJAdapter.isSwingUsingScreenMenuBar() is not telling us the truth. } else { fileMenu.addSeparator(); JMenuItem exitItem = new JMenuItem(VueResources.getString("menu.windows.exit")); exitItem.setMnemonic('x'); exitItem.addActionListener(exitAction); fileMenu.add(exitItem); } //////////////////////////////////////////////////////////////////////////////////// // Build Edit Menu //////////////////////////////////////////////////////////////////////////////////// editMenu.add(Actions.Undo); editMenu.add(Actions.Redo); editMenu.addSeparator(); editMenu.add(Actions.Cut); editMenu.add(Actions.Copy); editMenu.add(Actions.Paste); editMenu.add(Actions.Duplicate); editMenu.add(Actions.Rename); editMenu.add(Actions.Delete); editMenu.addSeparator(); editMenu.add(Actions.SelectAll); editMenu.add(Actions.SelectAllNodes); editMenu.add(Actions.SelectAllLinks); editMenu.add(Actions.ExpandSelection); editMenu.add(Actions.ShrinkSelection); editMenu.add(Actions.Reselect); editMenu.add(Actions.DeselectAll); if (!tufts.Util.isMacPlatform() || VUE.isApplet()) { editMenu.addSeparator(); editMenu.add(Actions.Preferences); } if (DEBUG.IMAGE) editMenu.add(Images.ClearCacheAction); //////////////////////////////////////////////////////////////////////////////////// // Build View Menu //////////////////////////////////////////////////////////////////////////////////// viewMenu.add(Actions.ZoomIn); viewMenu.add(Actions.ZoomOut); viewMenu.addSeparator(); viewMenu.add(Actions.ZoomFit); viewMenu.add(Actions.ZoomToSelection); viewMenu.add(Actions.ZoomActual); viewMenu.addSeparator(); if (!(Util.isUnixPlatform() || VUE.isApplet()) ) { viewMenu.add(viewFullScreen); } if (!VUE.isApplet()) { viewMenu.add(splitScreenItem); viewMenu.add(viewSuperScreen); } if (DEBUG.TWITTER) viewMenu.add(viewKiosk); if (!VUE.isApplet()) viewMenu.addSeparator(); if (!VUE.isApplet()) { viewMenu.add(toggleSlideIconsItem); viewMenu.addSeparator(); } if (LWComponent.COLLAPSE_IS_GLOBAL) viewMenu.add(makeCheckBox(Actions.ToggleGlobalCollapse)); else viewMenu.add(Actions.Collapse); viewMenu.add(togglePruningItem); viewMenu.add(Actions.ClearAllPruning); viewMenu.add(toggleLinksItem); // viewMenu.add(Actions.unhideLinks); // JAVA BUG: ADDING A JMenuItem (maybe just JCheckBoxMenuItem) // already constructe, instead of letting the menu code do it // itself, breaks the display of accelerators in the item: // anything other than APPLE (ctrl/alt) display as the APPLE // glpyh. This is true on Tiger java 1.5, Leopard java 1.5 // and 1.6 -- SMF 2008-05-31 viewMenu.add(toggleAutoZoomItem); if (DEBUG.Enabled && DEBUG.KEYS) viewMenu.add(Actions.ToggleAutoZoom); // viewMenu.addSeparator(); // viewMenu.add(Actions.ViewBackward); // viewMenu.add(Actions.ViewForward); // GUI.getFullScreenWindow().addWindowFocusListener(new WindowFocusListener() // { // public void windowGainedFocus(WindowEvent arg0) { // viewFullScreen.setSelected(true); // } // public void windowLostFocus(WindowEvent arg0) { // viewFullScreen.setSelected(false); // } // }); //////////////////////////////////////////////////////////////////////////////////// // Build Format Menu //////////////////////////////////////////////////////////////////////////////////// formatMenu.add(Actions.CopyStyle); formatMenu.add(Actions.PasteStyle); formatMenu.addSeparator(); //build format submenus... buildMenu(alignMenu, Actions.ALIGN_MENU_ACTIONS); buildMenu(arrangeMenu, Actions.ARRANGE_MENU_ACTIONS); buildMenu(layoutMenu,LayoutAction.LAYOUT_ACTIONS); buildMenu(linkMenu, Actions.LINK_MENU_ACTIONS); transformMenu.add(Actions.FontBigger); transformMenu.add(Actions.FontSmaller); transformMenu.add(Actions.FontBold); transformMenu.add(Actions.FontItalic); transformMenu.add(Actions.FontUnderline); formatMenu.add(buildMenu("menu.node",Actions.NODE_FORMAT_MENU_ACTIONS)); formatMenu.add(transformMenu); formatMenu.add(buildMenu("menu.image", Actions.IMAGE_MENU_ACTIONS)); formatMenu.add(linkMenu); formatMenu.add(alignMenu); formatMenu.add(arrangeMenu); formatMenu.add(layoutMenu); formatMenu.addSeparator(); formatMenu.add(Actions.Group); formatMenu.add(Actions.Ungroup); formatMenu.addSeparator(); //////////////////////////////////////////////////////////////////////////////////// // Build Content Menu //////////////////////////////////////////////////////////////////////////////////// GUI.addToMenu(contentMenu, Actions.NEW_OBJECT_ACTIONS); final JMenuItem addFileItem = new JMenuItem(Actions.AddFileAction); final JMenuItem addURLItem = new JMenuItem(Actions.AddURLAction); final JMenuItem newSlideItem = new JMenuItem(Actions.NewSlide); final JMenuItem newMergeNodeItem = new JMenuItem(Actions.MergeNodeSlide); final JMenuItem removeResourceItem = new JMenuItem(Actions.RemoveResourceAction); final JMenuItem removeResourceKeepImageItem = new JMenuItem(Actions.RemoveResourceKeepImageAction); contentMenu.add(addFileItem); contentMenu.add(addURLItem); contentMenu.addSeparator(); contentMenu.add(removeResourceItem); contentMenu.add(removeResourceKeepImageItem); formatMenu.addMenuListener(new MenuListener(){ public void menuCanceled(MenuEvent e) {/* no op */} public void menuDeselected(MenuEvent e) {/*no op */} public void menuSelected(MenuEvent e) {handleActivation();} private void handleActivation() { LWSelection selection = VUE.getSelection(); if (selection.count(LWText.class) > 0) { if(!isMenuEnableFontFlg){ transformMenu.setEnabled(isMenuEnableFontFlg); } } else { if(!isMenuEnableFontFlg){ transformMenu.setEnabled(false); }else{ transformMenu.setEnabled(true); } } }}); contentMenu.addMenuListener(new MenuListener(){ public void menuCanceled(MenuEvent e) {/* no op */} public void menuDeselected(MenuEvent e) {/*no op */} public void menuSelected(MenuEvent e) {handleActivation();} private void handleActivation() { LWComponent c =VUE.getActiveComponent(); if (c instanceof LWNode) { if ((c).hasResource()) { final Resource resource = c.getResource(); removeResourceItem.setEnabled(true); if (c.hasChildren()) { List<LWComponent> children = c.getChildren(); Iterator<LWComponent> childIterator = children.iterator(); boolean enabled= false; while (childIterator.hasNext()) { LWComponent comp = childIterator.next(); if (comp instanceof LWImage) { LWImage image = ((LWImage)comp); if (image.getResource().equals(resource)) { removeResourceKeepImageItem.setEnabled(true); enabled=true; break; } } } if (!enabled) removeResourceKeepImageItem.setEnabled(false); } addFileItem.setLabel(VueResources.getString("mapViewer.componentMenu.replaceFile.label")); addURLItem.setLabel(VueResources.getString("mapViewer.componentMenu.replaceURL.label")); } else { removeResourceItem.setEnabled(false); removeResourceKeepImageItem.setEnabled(false); addFileItem.setLabel(VueResources.getString("mapViewer.componentMenu.addFile.label")); addURLItem.setLabel(VueResources.getString("mapViewer.componentMenu.addURL.label")); } } }}); // contentMenu.addSeparator(); // contentMenu.add(Actions.AddResource); // contentMenu.add(Actions.UpdateResource).setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_U, metaMask)); //////////////////////////////////////////////////////////////////////////////////// // Build Presentation Menu //////////////////////////////////////////////////////////////////////////////////// if (!VUE.isApplet()) { presentationMenu.add(playbackMenu); presentationMenu.addSeparator(); } presentationMenu.addMenuListener(new MenuListener(){ public void menuCanceled(MenuEvent e) {/* no op */} public void menuDeselected(MenuEvent e) {/*no op */} public void menuSelected(MenuEvent e) {handleActivation();} private void handleActivation() { LWComponent c =VUE.getActiveComponent(); if (c instanceof LWNode) { if (c instanceof LWPortal) { newSlideItem.setEnabled(false); newMergeNodeItem.setEnabled(false); } else { newSlideItem.setEnabled(true); newMergeNodeItem.setEnabled(true); } } }}); presentationMenu.add(newSlideItem); presentationMenu.add(newMergeNodeItem); if (!VUE.isApplet()) presentationMenu.addSeparator(); if (VUE.getSlideDock() != null) { presentationMenu.add(Actions.MasterSlide); presentationMenu.add(Actions.PreviewInViewer); } //slidePreviewMenu.add(Actions.PreviewOnMap); if (!VUE.isApplet()) { presentationMenu.add(Actions.PreviewOnMap); presentationMenu.add(Actions.EditMasterSlide); } // slidePreviewMenu.add(Actions.PreviewInViewer); // presentationMenu.add(slidePreviewMenu); playbackMenu.addMenuListener(new MenuListener() { public void menuCanceled(MenuEvent arg0) { // TODO Auto-generated method stub } public void menuDeselected(MenuEvent arg0) { // TODO Auto-generated method stub } public void menuSelected(MenuEvent arg0) { playbackMenu.removeAll(); final LWPathwayList pathways = VUE.getActiveMap().getPathwayList(); //Iterator i = pathways.iterator(); final Collection coll = pathways.getElementList(); final Iterator i = coll.iterator(); while (i.hasNext()) { final LWPathway path = (LWPathway) i.next(); final JMenuItem menuItem = new JMenuItem(path.getDisplayLabel()); if (path.getEntries().isEmpty()) menuItem.setEnabled(false); menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Actions.startPresentation(path, this); } }); playbackMenu.add(menuItem); } } }); if (!VUE.isApplet()) { notesMenu.add(Actions.MapAsPDF); notesMenu.add(Actions.FullPageSlideNotes); notesMenu.add(Actions.Slides8PerPage); notesMenu.add(Actions.SpeakerNotes1); notesMenu.add(Actions.SpeakerNotes4); notesMenu.add(Actions.AudienceNotes); notesMenu.add(Actions.SpeakerNotesOutline); } if (!VUE.isApplet()) presentationMenu.add(notesMenu); presentationMenu.addMenuListener(new MenuListener(){ public void menuCanceled(MenuEvent e) {/* no op */} public void menuDeselected(MenuEvent e) {/*no op */} public void menuSelected(MenuEvent e) {handleActivation();} private void handleActivation() { LWPathway p =VUE.getActivePathway(); if (p == null || p.length() == 0) notesMenu.setEnabled(false); else notesMenu.setEnabled(true); }}); //////////////////////////////////////////////////////////////////////////////////// // Build Analysis Menu //////////////////////////////////////////////////////////////////////////////////// // if (!VUE.isApplet()) //{ analysisMenu.add(createCMAction); if (VUE.getMergeMapsDock()!= null ){ analysisMenu.add(createWindowItem(VUE.getMergeMapsDock(), 0, VueResources.getString("menu.windows.mergemaps"))); } //analysisMenu.add(analyzeCMAction); analysisMenu.add(createWindowItem(JavaAnalysisPanel.getJavaAnalysisDock(), 0, VueResources.getString("menu.windows.java"))); //} rebuildWindowsMenu(); //////////////////////////////////////////////////////////////////////////////////// // Build Help Menu //////////////////////////////////////////////////////////////////////////////////// if (tufts.Util.isMacPlatform() == false || VUE.isApplet()) { // already in standard MacOSX place helpMenu.add(new AboutAction()); helpMenu.addSeparator(); } helpMenu.add(new ShowURLAction(VueResources.getString("helpMenu.userGuide.label"), VueResources.getString("helpMenu.userGuide.url"))); helpMenu.add(new ShowURLAction(VueResources.getString("helpMenu.feedback.label"), VueResources.getString("helpMenu.feedback.url"))); helpMenu.add(new ShowURLAction(VueResources.getString("helpMenu.vueWebsite.label"), VueResources.getString("helpMenu.vueWebsite.url"))); /* * This feature was removed from the VUE website because of a security issue in the web-feature, it doesn't make * sense to have it here until we decided we're going to support it again on the website. * helpMenu.add(new ShowURLAction(VueResources.getString("helpMenu.mymaps.label"), VueResources.getString("helpMenu.mymaps.url"))); */ helpMenu.addSeparator(); /* helpMenu.add(new ShowURLAction(VueResources.getString("helpMenu.releaseNotes.label"), VueResources.getURL("helpMenu.releaseNotes.file"), new String("ReleaseNotes_" + VueResources.getString("vue.version") + ".htm").replace(' ', '_') )); helpMenu.addSeparator();*/ helpMenu.add(new ShortcutsAction()); helpMenu.addSeparator(); if (!VUE.isApplet()) helpMenu.add(new ShowLogAction()); //////////////////////////////////////////////////////////////////////////////////// // Build final main menu bar //////////////////////////////////////////////////////////////////////////////////// //build out the main menus.. add(fileMenu); add(editMenu); add(viewMenu); add(formatMenu); add(contentMenu); add(presentationMenu); // if (!VUE.isApplet()) add(analysisMenu); add(windowMenu); add(helpMenu); if (DEBUG.Enabled) { add(new JMenu(" ")); // gap add(TitleItem); VUE.addActiveListener(LWMap.class, this); } if (RootMenuBar == null) RootMenuBar = this; } public void rebuildWindowsMenu() { //////////////////////////////////////////////////////////////////////////////////// // Window Menu //////////////////////////////////////////////////////////////////////////////////// windowMenu.removeAll(); if (VUE.getFormatDock() != null) windowMenu.add(createWindowItem(VUE.getFormatDock(), KeyEvent.VK_1,VueResources.getString("menu.windows.formatpalette"))); windowMenu.addSeparator(); if (VUE.getInfoDock() !=null) windowMenu.add(createWindowItem(VUE.getInfoDock(), KeyEvent.VK_2, VueResources.getString("menu.windows.info"))); windowMenu.add(Actions.KeywordAction); windowMenu.add(Actions.NotesAction); windowMenu.addSeparator(); // if (VUE.getContentDock()!= null && !VUE.isApplet()){ windowMenu.add(createWindowItem(VUE.getContentDock(), KeyEvent.VK_3, VueResources.getString("dockWindow.contentPanel.title"))); if (!VUE.isApplet()) windowMenu.add(Actions.ResourcesAction); windowMenu.add(Actions.DatasetsAction); windowMenu.add(Actions.OntologiesAction); windowMenu.addSeparator(); // OntologyBrowser.getBrowser().initializeBrowser(false, null); // } if (VUE.getInteractionToolsDock()!= null) windowMenu.add(createWindowItem(VUE.getInteractionToolsDock(), KeyEvent.VK_4, VueResources.getString("dockWindow.interactionTools.title"))); if (VUE.getLayersDock() != null) windowMenu.add(createWindowItem(VUE.getLayersDock(), KeyEvent.VK_5, VueResources.getString("menu.windows.layers"))); if (VUE.getMapInfoDock() !=null) windowMenu.add(createWindowItem(VUE.getMapInfoDock(), KeyEvent.VK_6, VueResources.getString("menu.windows.mapinfo"))); if (VUE.getOutlineDock() !=null && !VUE.isApplet()) windowMenu.add(createWindowItem(VUE.getOutlineDock(), KeyEvent.VK_7, VueResources.getString("menu.windows.outline"))); if (VUE.getPannerDock() !=null && !VUE.isApplet()) windowMenu.add(createWindowItem(VUE.getPannerDock(), 0, VueResources.getString("menu.windows.panner"))); if (VUE.getPresentationDock() !=null) windowMenu.add(createWindowItem(VUE.getPresentationDock(), KeyEvent.VK_8, VueResources.getString("menu.windows.pathways"))); if (VUE.getMetadataSearchMainGUI()!= null) windowMenu.add(createWindowItem(VUE.getMetadataSearchMainGUI(), KeyEvent.VK_9, VueResources.getString("menu.windows.search"))); // final KeyStroke acceleratorKey = KeyStroke.getKeyStroke(KeyEvent.VK_9, Actions.COMMAND); // Actions.SearchFilterAction.putValue(Action.ACCELERATOR_KEY, acceleratorKey); // windowMenu.add(Actions.SearchFilterAction); windowMenu.addSeparator(); if (VUE.getFloatingZoomDock()!=null) { fullScreenToolbarItem = createWindowItem(VUE.getFloatingZoomDock(),KeyEvent.VK_0, VueResources.getString("menu.windows.fullscreentoolbar")); fullScreenToolbarItem.setEnabled(false); windowMenu.add(fullScreenToolbarItem); } windowMenu.add(Actions.GatherWindows); } private final JMenuItem TitleItem = new JMenu(""); //private final java.util.Map<LWMap,JMenuItem> items = new java.util.HashMap(); public void activeChanged(tufts.vue.ActiveEvent e, LWMap map) { //TitleItem.setFont(VueConstants.SmallFont); if (VUE.isStartupUnderway()) return; TitleItem.setLabel("[ " + (map==null?"?":map.getLabel()) + " ]"); TitleItem.removeAll(); MapTabbedPane tp = VUE.getLeftTabbedPane(); for (int i = 0; i < tp.getTabCount(); i++) { final MapViewer v = tp.getViewerAt(i); final LWMap m = v.getMap(); JMenuItem item = new JMenuItem(new VueAction(m.getSaveFileModelVersion() + " | " + m.getLabel()) { public void act() { Log.debug("quik-map " + m); // this currently only works in full-screen mode ActiveInstance.getHandler(LWMap.class).setActive(this, m); // VueTabbedPane should be auto-updating based // on the active map event, but too close to // release to mess with fundamental events right now. VUE.getLeftTabbedPane().setSelectedMap(m); } public boolean overrideIgnoreAllActions() { return true; } } ); if (m == map) item.setEnabled(false); item.setToolTipText(""+m.getFile()); TitleItem.add(item); } } public static void toggleFullScreenTools() { fullScreenToolbarItem.setEnabled(FullScreen.inFullScreen()); } public JCheckBoxMenuItem createWindowItem(DockWindow dock,int accelKey, String text) { final JCheckBoxMenuItem checkBox; if (dock != null) { final WindowDisplayAction windowAction = new WindowDisplayAction(dock); final KeyStroke acceleratorKey = KeyStroke.getKeyStroke(accelKey, Actions.COMMAND); if (accelKey > 0) windowAction.putValue(Action.ACCELERATOR_KEY, acceleratorKey); if (text !=null) windowAction.setTitle(text); checkBox = new JCheckBoxMenuItem(windowAction); //checkBox.setText(text); windowAction.setLinkedButton(checkBox); } else { checkBox = new JCheckBoxMenuItem(text + " (missing window)"); } return checkBox; } protected void rebuildRecentlyOpenedItems(JMenu fileMenu, JMenu recentlyOpenedMenu, RecentlyOpenedFilesManager rofm) { if (rofm.getFileListSize() > 0) { List files = rofm.getRecentlyOpenedFiles(); Iterator i = files.iterator(); recentlyOpenedMenu.removeAll(); while (i.hasNext()) { File f = new File((String)i.next()); if (f.exists()) recentlyOpenedMenu.add(new RecentOpenAction(f)); else f=null; } fileMenu.remove(recentlyOpenedMenu); fileMenu.add(recentlyOpenedMenu,2); } } private KeyEvent alreadyProcessed; /* public boolean doProcessKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { //return super.processKeyBinding(ks, e, condition, pressed); //if (e != alreadyProcessed) { System.out.println("VueMenuBar: handling relayed " + ks); return processKeyBinding(ks, e, condition, pressed); //} //return true; } */ public void processKeyEvent(KeyEvent e) { if (!e.isConsumed()) super.processKeyEvent(e); else Log.debug("processKeyEvent: already consumed " + GUI.name(e)); } /** hardwired entry-point called by FocusManager for handling unconsumed KeyEvent's */ void doProcessKeyEvent(KeyEvent e) { // todo: need to allow BACK_SPACE to process as DELETE (esp // for laptop keyboards?) -- time for an action map for this // code -- long overdue -- e.g. Actions.Delete should // trigger for VK_BACK_SPACE as well as VK_DELETE. if (e != alreadyProcessed) { if (DEBUG.KEYS) Log.debug("doProcessKeyEvent " + GUI.name(e)); processKeyEvent(e); } else if (DEBUG.KEYS) Log.debug("already processed " + GUI.name(e)); } // // todo: this doesn't work: safer if can get working instead of above // void doProcessKeyPressEventToBinding(KeyEvent e) { // if (e != alreadyProcessed) { // //System.out.println("VueMenuBar: doProcessKeyPressEventToBinding " + e); // Log.debug("KEY->BIND " + e); // KeyStroke ks = KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers(), false); // super.processKeyBinding(ks, e, WHEN_FOCUSED, true); // } // else Log.debug("already processed " + e); // } protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { if (e.isConsumed()) Log.debug("GOT CONSUMED " + ks); if (!pressed) // we only ever handle on key-press return false; // todo: find the right keymap to stick these kinds of non-menu actions in if (Actions.FocusToSearchField.keyStroke.equals(ks)) { Actions.FocusToSearchField.fire(e); return true; } else if (Actions.FocusToViewer.keyStroke.equals(ks)) { Actions.FocusToViewer.fire(e); return true; } final boolean didAction = super.processKeyBinding(ks, e, condition, pressed); if (DEBUG.KEYS) { String used = didAction ? "CONSUMED " : "NOACTION "; Log.debug("processKeyBinding " + used + "[" + ks + "] " + GUI.name(e)); } if (didAction) e.consume(); // checking for extra keys to process here can give us "Aquired pre-used event!" stack // traces from javax.swing.KeyboardManager.fireKeyboardAction if they're actually fired. alreadyProcessed = e; return didAction; } public void setVisible(boolean b) { Log.debug("VMB: setVisible: " + b); super.setVisible(b); } public void focusGained(java.awt.event.FocusEvent e) { Log.debug("VMB: focusGained from " + e.getOppositeComponent()); } public void focusLost(java.awt.event.FocusEvent e) { Log.debug("VMB: focusLost to " + e.getOppositeComponent()); } // public static JMenu buildMenu(String name, Action[] actions) { // return buildMenu(makeMenu(name), actions); // } public static JMenu buildMenu(String localizationKey, Action[] actions) { return buildMenu(localizationKey, actions, true); } public static JMenu buildMenu(String localizationKey, Action[] actions, boolean enabled) { final JMenu menu = new JMenu(VueResources.getString(localizationKey, localizationKey)); buildMenu(menu, actions); if (!enabled) menu.setEnabled(false); return menu; } public static JMenu buildMenu(JMenu menu, Action[] actions) { for (int i = 0; i < actions.length; i++) { Action a = actions[i]; if (a == null) menu.addSeparator(); else menu.add(a); } return menu; } private static class RecentOpenAction extends VueAction { private File file; public RecentOpenAction(File f) { super(f.getName()); if (DEBUG.Enabled) putValue(SHORT_DESCRIPTION, f.toString()); else putValue(SHORT_DESCRIPTION, ""); file = f; } @Override public boolean isUserEnabled() { return true; } // Don't allow if locked into a presentation: //public boolean isUserEnabled() { return VUE.getActiveTool().permitsToolChange(); } public void act() { OpenAction.displayMap(file); } } private static class ShowLogAction extends VueAction { private static DockWindow errorDock; private static JTextArea textArea; //private static final String ReportAddress = "vue-help@elist.tufts.edu"; private static final String ReportAddress = "vue-report@fraize.org"; public ShowLogAction() { super(VueResources.getString("menu.help.vuelog")); } @Override public boolean isUserEnabled() { return true; } public void act() { if (errorDock == null) buildGUI(); textArea.setText(tufts.Util.getExceptionLog().toString()); // errorDock.setVisible(true); GUI.makeVisibleOnScreen(panel); } JPanel panel = null; private void buildGUI() { panel = new JPanel(new BorderLayout()); textArea = new JTextArea(); textArea.setFont(VueConstants.SmallFixedFont); textArea.setLineWrap(true); textArea.setEditable(false); panel.add(new JScrollPane(textArea), BorderLayout.CENTER); if (!Util.isWindowsPlatform()) // can't get enough log into the email for now on Windows to be useful. panel.add(new JButton(VueResources.getString("button.submitrep.label")) { @Override protected void fireActionPerformed(ActionEvent ae) { //System.out.println("ACTION " + ae); final String body; if (Util.isWindowsPlatform()) { // There's a 2048 byte WinXP argument limit for url.dll,FileProtocolHandler, // so don't add anything extra... body = Util.getExceptionLog().toString(); } else { body = "Thank you for submitting a problem report.\n" + "Please feel free to add any comments/feedback here:\n" + "\n\n\n" + Util.getExceptionLog() + "\nEnd report: " + new java.util.Date() + ".\n"; } final String subject = "VUE Log Report from " + VUE.getSystemProperty("user.name"); try { VueUtil.openURL(Util.makeQueryURL("mailto:" + ReportAddress, "subject", subject, "body", body)); } catch (Throwable t) { t.printStackTrace(); } } }, BorderLayout.SOUTH); errorDock = GUI.createDockWindow(VueResources.getString("menu.help.vuelog"), panel); errorDock.setSize(800,600); } } public static void main(String args[]) { VUE.init(args); // Ensure the tools are loaded to we can see their shortcuts: VueToolbarController.getController(); JFrame frame = new JFrame("vueParentWindow"); // Ensure that all the Actions are instantiated so we can see them: tufts.vue.Actions.Delete.toString(); // Let us see the actual menu bar: frame.setJMenuBar(new VueMenuBar()); frame.setVisible(true); // do this or we can't see the menu bar new ShortcutsAction().act(); } public void setMenuEnableFlg(boolean enable) { isMenuEnableFontFlg = enable; } }