/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * NavFrame.java * Creation date: Jul 4, 2003 * By: Frank Worsley */ package org.openquark.gems.client.navigator; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.prefs.Preferences; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.Box; import javax.swing.ButtonGroup; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JEditorPane; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.text.html.HTMLDocument; import javax.swing.text.html.HTMLEditorKit; import javax.swing.tree.TreePath; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.metadata.CALExample; import org.openquark.cal.metadata.CALFeatureMetadata; import org.openquark.cal.metadata.FunctionalAgentMetadata; import org.openquark.gems.client.GemCutter; import org.openquark.gems.client.ModuleNameDisplayUtilities.TreeViewDisplayMode; import org.openquark.gems.client.utilities.PreferencesHelper; import org.openquark.util.UnsafeCast; import org.openquark.util.WildcardPatternMatcher; import org.openquark.util.ui.UIUtilities; /** * This is class implements the CAL Navigator that allows the user to browse the * different CAL language entities and to view/edit their metadata. * * @author Frank Worsley */ public class NavFrame extends JDialog { private static final long serialVersionUID = 885869144342482223L; /** * This listener updates the state of the UI if the metadata displayed * in the HTML viewer changes. * @author Frank Worsley */ private class NavDocumentListener implements DocumentListener { public void changedUpdate(DocumentEvent e) { updateState(); } public void insertUpdate(DocumentEvent e) { updateState(); } public void removeUpdate(DocumentEvent e) { updateState(); } private void updateState() { NavTreeModel model = navTree.getModel(); NavTreeNode node = model.getNodeForAddress(viewerPane.getCurrentAddress()); getBackButton().setEnabled(viewerPane.getBackHistorySize() > 0); getForwardButton().setEnabled(viewerPane.getForwardHistorySize() > 0); NavAddress address = viewerPane.getCurrentAddress(); getEditButton().setEnabled(NavAddressHelper.getMetadata(owner, address) != null); TreePath selectionPath = navTree.getSelectionPath(); NavTreeNode selected = (selectionPath != null) ? (NavTreeNode) selectionPath.getLastPathComponent() : null; if (node != null && node != selected) { TreePath path = new TreePath(node.getPath()); navTree.setSelectionPath(path); navTree.scrollPathToVisible(path); } } } /** * This listener updates the displayed HTML content if a node is selected in the tree. * @author Frank Worsley */ private class NavTreeSelectionListener implements TreeSelectionListener { public void valueChanged(TreeSelectionEvent e) { TreePath path = e.getNewLeadSelectionPath(); if (path == null) { return; } NavTreeNode treeNode = (NavTreeNode) path.getLastPathComponent(); if (viewerPane.getCurrentAddress().withAnchor(null).equals(treeNode.getAddress())) { return; } stopEditing(true); viewerPane.displayMetadata(treeNode.getAddress()); } } /** * A mouse listener for the navigation tree that displays copy/paste menu items for * easily duplicating metadata. */ private class NavTreeMouseListener extends MouseAdapter { public void mousePressed(MouseEvent e) { // Select the node if the user presses the right mouse button. if (SwingUtilities.isRightMouseButton(e)) { TreePath nodePath = navTree.getPathForLocation(e.getX(), e.getY()); if (nodePath != null) { navTree.setSelectionPath(nodePath); } } maybeShowPopupMenu(e); } public void mouseReleased(MouseEvent e) { maybeShowPopupMenu(e); } private void maybeShowPopupMenu(MouseEvent e) { if (e.isPopupTrigger()) { JPopupMenu popupMenu = new JPopupMenu(); popupMenu.add(getCopyMenuItem()); popupMenu.show(navTree, e.getX(), e.getY()); } } } /* Mnemonics for the toolbar buttons. */ private static final Integer FLAT_ABBREVIATED_DISPLAY_MODE_MNEMONIC = Integer.valueOf(KeyEvent.VK_F); private static final Integer FLAT_FULLY_QUALIFIED_DISPLAY_MODE_MNEMONIC = Integer.valueOf(KeyEvent.VK_Q); private static final Integer HIERARCHICAL_DISPLAY_MODE_MNEMONIC = Integer.valueOf(KeyEvent.VK_H); private static final Integer GO_BACK_MNEMONIC = Integer.valueOf(KeyEvent.VK_LEFT); private static final Integer GO_FORWARD_MNEMONIC = Integer.valueOf(KeyEvent.VK_RIGHT); private static final Integer TOGGLE_PANEL_MNEMONIC = Integer.valueOf(KeyEvent.VK_N); private static final Integer EDIT_MNEMONIC = Integer.valueOf(KeyEvent.VK_E); private static final Integer EDIT_PARENT_MNEMONIC = Integer.valueOf(KeyEvent.VK_LEFT); private static final Integer SAVE_MNEMONIC = Integer.valueOf(KeyEvent.VK_S); private static final Integer CLOSE_MNEMONIC = Integer.valueOf(KeyEvent.VK_C); private static final Integer PRINT_MNEMONIC = Integer.valueOf(KeyEvent.VK_P); /* Preference key names. */ private static final String DIALOG_PROPERTIES_PREF_KEY = "dialogProperties"; private static final String DIVIDER_LOCATION_PREF_KEY = "dividerLocation"; private static final String NAV_TREE_DISPLAY_MODE = "navTreeDisplayMode"; /* Preference default values. */ private static final int DIVIDER_LOCATION_DEFAULT = 1; /** The minimum size for the window if the side pane is hidden. */ private static final Dimension MIN_SIZE_HIDDEN = new Dimension(470, 445); /** The minimum size for the window if the side pane is shown. */ private static final Dimension MIN_SIZE_SHOWN = new Dimension(625, 445); /** The owner of the navigator frame. */ private final NavFrameOwner owner; /** The editor that is used to display the HTML content. */ private final NavViewerPane viewerPane; /** The scroll pane that houses the HTML display or metadata editing component. */ private final JScrollPane navScrollPane; /** The split pane that holds the side panel and HTML viewer. */ private final JSplitPane splitPane; /** The toolbar at the top of the main viewing area. */ private JToolBar toolBar; /** The saved location of the split pane divider. */ private int savedDividerLocation = -1; /** The toolbar at the top of the navigation tree. */ private JToolBar navTreeToolBar; /** The button group for the display mode buttons for the nav tree. */ private ButtonGroup navTreeDisplayModeButtonGroup; /** The flat abbreviated display mode button for the nav tree toolbar. */ private JToggleButton flatAbbreviatedDisplayModeButton; /** The flat fully qualified display mode button for the nav tree toolbar. */ private JToggleButton flatFullyQualifiedDisplayModeButton; /** The hierarchical display mode button for the nav tree toolbar. */ private JToggleButton hierarchicalDisplayModeButton; /** The navigation tree that displays the CAL entities. */ private NavTree navTree = null; /** If currently editing metadata, this is the editor panel being used. */ private NavEditorPanel editorPanel = null; /** If editing argument metadata, this is the editor panel that was editing the parent metadata. */ private NavEditorPanel parentEditorPanel = null; /** The back button for the navigation toolbar. */ private JButton backButton = null; /** The forward button for the navigation toolbar. */ private JButton forwardButton = null; /** The print button for the navigation toolbar. */ private JButton printButton = null; /** The edit button for the navigation toolbar. */ private JButton editButton = null; /** The save button for the navigation toolbar. */ private JButton saveButton = null; /** The done button for the navigation toolbar. */ private JButton closeButton = null; /** The edit parent button for the navigation toolbar. */ private JButton editParentButton = null; /** The toggle button for hiding/showing the navigation tree. */ private JToggleButton showTreeButton = null; /** The copy metadata button. */ private JButton copyButton = null; /** The paste metadata button. */ private JButton pasteButton = null; /** The panel that contains the search text field and related components. */ private JPanel searchPanel = null; /** The search text field for the search panel. */ private JTextField searchField = null; /** The metadata that was copied by the user. */ private CALFeatureMetadata copiedMetadata = null; /** * Constructs a new NavFrame object. * @param owner the owner frame of this navigator dialog */ public NavFrame(NavFrameOwner owner) { super(owner.getParent(), NavigatorMessages.getString("NAV_PropertiesBrowser_Header")); this.owner = owner; splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, false); splitPane.setResizeWeight(0); splitPane.setOneTouchExpandable(true); final JEditorPane headerPane = getHeaderPane(); getContentPane().setLayout(new BorderLayout()); getContentPane().add(headerPane, BorderLayout.NORTH); getContentPane().add(splitPane, BorderLayout.CENTER); // Create the JPanel that houses the HTML viewer JPanel mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); mainPanel.add(getToolBar(), BorderLayout.NORTH); // Create the viewer component. viewerPane = new NavViewerPane(owner); viewerPane.getDocument().addDocumentListener(new NavDocumentListener()); navScrollPane = new JScrollPane(viewerPane); navScrollPane.getViewport().setBackground(Color.WHITE); mainPanel.add(navScrollPane, BorderLayout.CENTER); // Create the navigation tree navTree = new NavTree(); navTree.addTreeSelectionListener(new NavTreeSelectionListener()); navTree.addMouseListener(new NavTreeMouseListener()); final JScrollPane navTreeScrollPane = new JScrollPane(navTree); final JPanel navTreePanel = new JPanel(new BorderLayout()); navTreePanel.add(getNavTreeToolBar(), BorderLayout.NORTH); navTreePanel.add(navTreeScrollPane, BorderLayout.CENTER); // Setup the split pane. Setting the initial divider location for the split // pane seems to be not working properly. The only way I could make the navigation // tree show up with an appropriate inital size is to set its minimum size to that. navTreePanel.setMinimumSize(new Dimension(180, 445)); splitPane.setLeftComponent(navTreePanel); splitPane.setRightComponent(mainPanel); // Add a resize listener to the scrollpane to detect when the divider changes location navScrollPane.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { // enforce minimum window size int location = splitPane.getDividerLocation(); Dimension minSize = (location > 2) ? MIN_SIZE_SHOWN : MIN_SIZE_HIDDEN; setValidSize(minSize); validate(); // remember the divider location if (splitPane.getDividerLocation() > 1) { savedDividerLocation = splitPane.getDividerLocation(); } // update toggle button getShowTreeButton().setSelected(splitPane.getDividerLocation() > 1); } }); // Add a window listener to stop editing when the window is closed addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { if (isEditing()) { stopEditing(false); } Preferences prefs = getPreferences(); PreferencesHelper.putDialogProperties(prefs, DIALOG_PROPERTIES_PREF_KEY, NavFrame.this); prefs.putInt(DIVIDER_LOCATION_PREF_KEY, splitPane.getDividerLocation()); } public void windowOpened(WindowEvent e) { // Sometimes the header pane doesn't redraw when the window appears // so we force it to repaint here headerPane.repaint(); } }); // Display the start page viewerPane.displayMetadata(NavAddress.getRootAddress(NavAddress.WORKSPACE_METHOD)); Preferences prefs = getPreferences(); PreferencesHelper.getDialogProperties(prefs, DIALOG_PROPERTIES_PREF_KEY, this, MIN_SIZE_SHOWN, new Point(50, 50)); splitPane.setDividerLocation(prefs.getInt(DIVIDER_LOCATION_PREF_KEY, DIVIDER_LOCATION_DEFAULT)); TreeViewDisplayMode navTreeDisplayMode = TreeViewDisplayMode.fromName(prefs.get(NAV_TREE_DISPLAY_MODE, TreeViewDisplayMode.FLAT_ABBREVIATED.getName())); setNavTreeDisplayModeButtonStates(navTreeDisplayMode); } /** * @return the preferences instance for this class. */ private Preferences getPreferences() { return Preferences.userNodeForPackage(NavFrame.class); } /** * Overridden to enforce a minimum size for the dialog. */ public void doLayout() { // enforce a different minimum size depending on the window state int location = splitPane.getDividerLocation(); Dimension minSize = (location > 2) ? MIN_SIZE_SHOWN : MIN_SIZE_HIDDEN; setValidSize(minSize); super.doLayout(); } /** * If the window is smaller then its minimum size then the * size is set to the minimum size. */ private void setValidSize(Dimension minSize) { Dimension size = getSize(); if (size.height < minSize.height) { size.height = minSize.height; } if (size.width < minSize.width) { size.width = minSize.width; } if (!size.equals(getSize())) { setSize(size); } } /** * Refreshes the navigation tree by reloading the tree model from the perspective. * This should be called if a new CAL entity is added to the perspective, so that * it appears in the navigation tree. */ public void refreshTree() { final NavTreeModel navModel = new NavTreeModel(); navModel.load(owner.getPerspective(), getNavTreeDisplayMode()); // JTree.setModel() causes the UI to be updated. // Ensure this happens on the AWT thread SwingUtilities.invokeLater(new Runnable() { public void run() { navTree.setModel(navModel); navTree.collapseToModules(); } }); } /** * Refreshes the viewer pane to display the latest metadata. This should be called * when displaying the properties browser, to be sure it displays the correct information. */ public void refreshViewer() { if (!isEditing()) { viewerPane.refresh(); } } /** * Called when the metadata being displayed or edited should be refreshed. * This usually happens as a result of an undo or redo operation. This will only * perform the actual refresh operation if the metadata currently being viewed * or edited matches the url of the metadata that has changed. * @param address the address that identifies the metadata object to refresh */ public void refreshMetadata(NavAddress address) { NavAddress viewerAddress = viewerPane.getCurrentAddress().withAllStripped(); if (isEditing()) { NavAddress editAddress = editorPanel.getAddress(); // See below why we always refresh for collectors. if (address.withAllStripped().equals(editAddress.withAllStripped()) || editAddress.getMethod() == NavAddress.COLLECTOR_METHOD) { editorPanel.refresh(); } } else if (address.withAllStripped().equals(viewerAddress.withAllStripped()) || viewerAddress.getMethod() == NavAddress.COLLECTOR_METHOD) { // Refresh the viewer pane if the address is the same or if we are viewing collector metadata. // That's because some of the collector argument names may depend on the argument names of // a gem that could have changed, so we need to always refresh for collectors. viewerPane.refresh(); } } /** * @return the owner of this navigator frame */ public NavFrameOwner getNavigatorOwner() { return owner; } /** * Sets the side panel to be visible or hidden. A hidden side panel can be expanded again. * If you want to disable expansion of the side panel you have to remove it. * @param sidePanelVisible true if the side panel should be shown, false for it to be hidden */ public void setSidePanelVisible(boolean sidePanelVisible) { if (sidePanelVisible) { splitPane.setDividerLocation(savedDividerLocation > 1 ? savedDividerLocation : -1); } else { savedDividerLocation = splitPane.getDividerLocation(); splitPane.setDividerLocation(1); } } /** * Display the metadata for the given address. * @param address the address to display metadata for */ public void displayMetadata(NavAddress address) { stopEditing(true); NavTreeModel treeModel = navTree.getModel(); NavTreeNode metadataNode = treeModel.getNodeForAddress(address); if (metadataNode != null) { TreePath selectionPath = new TreePath(metadataNode.getPath()); if (!selectionPath.equals(navTree.getSelectionPath())) { // The metadata display will be updated as the new node is selected navTree.setSelectionPath(selectionPath); navTree.scrollPathToVisible(selectionPath); } else { // If the node is already selected, just refresh the viewer viewerPane.displayMetadata(address); viewerPane.refresh(); } } else { // We don't seem to have a node for this metadata. That means it must be // a collector. So just directly tell the viewpane what to display. viewerPane.displayMetadata(address); viewerPane.refresh(); } if (!isVisible()) { setVisible(true); } } /** * Edit the metadata object for the given address. * @param address the address of the metadata object to edit */ public void editMetadata(NavAddress address) { boolean alreadyEditing = isEditing(); if (alreadyEditing && address.equals(editorPanel.getAddress())) { return; } saveChanges(); // if we are going back to editing the parent metadata, reuse the editing panel if (parentEditorPanel != null) { NavAddress parentAddress = parentEditorPanel.getAddress(); if (address.equals(parentAddress)) { editorPanel = parentEditorPanel; editorPanel.addFocusListener(); editorPanel.refresh(); } else { editorPanel = new NavEditorPanel(owner, address); } parentEditorPanel = null; } else if (address.getParameter(NavAddress.ARGUMENT_PARAMETER) != null) { parentEditorPanel = editorPanel; editorPanel = new NavEditorPanel(owner, address); } else { parentEditorPanel = null; editorPanel = new NavEditorPanel(owner, address); } if (alreadyEditing) { resetToolBarForEditing(); navScrollPane.setViewportView(editorPanel); editorPanel.setInitialFocus(); } else { startEditing(); } } /** * Perform a simple case-insensitive substring search on all nodes in the navigation tree. * @param searchString the string to search for * @return a List of NavAddresses for the nodes that matched the search string */ public List<NavAddress> searchMetadata(String searchString) { // indicate in the search field that we are searching String text = searchField.getText(); Font font = searchField.getFont(); searchField.setCaretColor(Color.LIGHT_GRAY); searchField.setBackground(Color.LIGHT_GRAY); searchField.setFont(font.deriveFont(Font.ITALIC)); searchField.setText(NavigatorMessages.getString("NAV_SearchingFor_Message", searchString)); searchField.paintImmediately(searchField.getBounds()); // perform the actual search List<NavAddress> results = new ArrayList<NavAddress>(); NavTreeModel navModel = navTree.getModel(); Enumeration<NavTreeNode> nodes = UnsafeCast.unsafeCast(navModel.getRoot().breadthFirstEnumeration()); // ~ unsafe Pattern searchPattern = Pattern.compile(WildcardPatternMatcher.wildcardPatternToRegExp(searchString), Pattern.CASE_INSENSITIVE); while (nodes.hasMoreElements()) { NavTreeNode node = nodes.nextElement(); String nodeName = node.toString(); // we use find() instead of matches() on the Matcher since we want to simply find a matching subsequence // and not whether the *whole* string matches the pattern. if (searchPattern.matcher(nodeName).find()) { results.add(node.getAddress()); } } // reset the search field searchField.setFont(font); searchField.setText(text); searchField.setCaretColor(Color.BLACK); searchField.setBackground(Color.WHITE); return results; } /** * Tests metadata examples (either just those with evaluatExample set, or all * of them) and prints the results to stdout. * @param surpressSuccesses If true, does not output results of successfully run examples * @param skipNonAutoEvaluableExamples If true, runs only those examples with evaluateExample set to true */ public void testMetadataExamples(boolean surpressSuccesses, boolean skipNonAutoEvaluableExamples) { StringBuilder buffer = new StringBuilder(); NavTreeModel navModel = navTree.getModel(); Enumeration<NavTreeNode> nodes = UnsafeCast.unsafeCast(navModel.getRoot().breadthFirstEnumeration()); // ~ unsafe int agentsPassed = 0; int agentsFailed = 0; int examplesPassed = 0; int examplesFailed = 0; buffer.append("\n-- Testing Metadata Examples --\n\n"); System.out.println(buffer); // output the buffer and flush its contents buffer.setLength(0); if (surpressSuccesses) { owner.getValueRunner().setShowConsoleInfo(false); } while (nodes.hasMoreElements()) { NavTreeNode node = nodes.nextElement(); if (node instanceof NavEntityNode) { NavAddress address = node.getAddress(); CALFeatureMetadata metadata = NavAddressHelper.getMetadata(owner, address); if (metadata instanceof FunctionalAgentMetadata) { boolean failed = false; FunctionalAgentMetadata faMetadata = (FunctionalAgentMetadata) metadata; CALExample[] examples = faMetadata.getExamples(); for (int i = 0; i < examples.length; i++) { if (skipNonAutoEvaluableExamples && !examples[i].evaluateExample()) { continue; } StringBuilder result = new StringBuilder(); boolean success = EditorHelper.evaluateExpression(owner, examples[i].getExpression(), result); if (!success) { if (!failed) { QualifiedName name = faMetadata.getFeatureName().toQualifiedName(); buffer.append (name.getQualifiedName() + " ...\n"); failed = true; } examplesFailed++; buffer.append("\n"); buffer.append(" EXAMPLE FAILED\n"); buffer.append(" Description: " + examples[i].getDescription() + "\n"); buffer.append(" Module Context: " + examples[i].getExpression().getModuleContext() + "\n"); buffer.append(" Expression: " + examples[i].getExpression().getExpressionText() + "\n"); buffer.append(" Result: " + result.toString() + "\n"); } else { examplesPassed++; } } if (!failed) { agentsPassed++; } else { buffer.append("\n"); agentsFailed++; } } } } if (surpressSuccesses) { owner.getValueRunner().setShowConsoleInfo(true); } if (examplesFailed == 0) { buffer.append("No examples failed.\n"); } buffer.append("\n"); buffer.append("-- Testing Summary --\n\n"); buffer.append("Examples Tested: " + (examplesPassed + examplesFailed) + "\n"); buffer.append("Passed: " + examplesPassed + " -- Failed: " + examplesFailed + "\n"); buffer.append("\n"); buffer.append("Functional Agents Tested: " + (agentsPassed + agentsFailed) + "\n"); buffer.append("Passed: " + agentsPassed + " -- Failed: " + agentsFailed + "\n"); System.out.println(buffer); } /** * @return true if the navigator is currently editing metadata */ public boolean isEditing() { return editorPanel != null; } /** * Saves changes made in the editor panel if there were any changes. */ private void saveChanges() { if (isEditing() && editorPanel.hasChanged()) { if (!editorPanel.checkValues()) { JOptionPane.showMessageDialog(this, NavigatorMessages.getString("NAV_InvalidValues_Message"), NavigatorMessages.getString("NAV_InvalidValues_Header"), JOptionPane.ERROR_MESSAGE); } else if (!editorPanel.save()) { JOptionPane.showMessageDialog(this, NavigatorMessages.getString("NAV_ErrorSaving_Message"), NavigatorMessages.getString("NAV_ErrorSaving_Header"), JOptionPane.ERROR_MESSAGE); } } } /** * Prompts the user to save changes if any changes have been made. * If no changes have been made, does nothing. */ private void promptForSaveChanges() { if (isEditing() && editorPanel.hasChanged()) { String title = NavigatorMessages.getString("NAV_SavePrompt_Header"); String message = NavigatorMessages.getString("NAV_SavePrompt_Message", NavAddressHelper.getDisplayText(owner, editorPanel.getAddress())); int response = JOptionPane.showOptionDialog(this, message, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null); if (response == JOptionPane.YES_OPTION) { saveChanges(); } } } /** * Stops editing metadata and returns to viewing the metadata that was edited. * Does nothing if the navigator is not currently editing. * @param autoSave If true, automatically saves changes; if false, prompts the user. */ public void stopEditing(boolean autoSave) { if (isEditing()) { if(autoSave) { saveChanges(); } else { promptForSaveChanges(); } editorPanel.stopEditing(); toolBar.removeAll(); toolBar.add(getBackButton()); toolBar.add(getForwardButton()); toolBar.addSeparator(); toolBar.add(getShowTreeButton()); toolBar.addSeparator(); toolBar.add(getPrintButton()); toolBar.add(getEditButton()); toolBar.addSeparator(); toolBar.add(getSearchPanel()); toolBar.repaint(); getEditButton().getModel().setRollover(false); NavAddress address = editorPanel.getAddress(); editorPanel = null; parentEditorPanel = null; displayMetadata(address); navScrollPane.setViewportView(viewerPane); navScrollPane.requestFocusInWindow(); } } /** * Start the editing process by setting the viewport to the editor panel and * updating the other UI components. */ private void startEditing() { navScrollPane.setViewportView(editorPanel); resetToolBarForEditing(); if (!isVisible()) { setVisible(true); } // make sure the editor panel ends up with the focus editorPanel.setInitialFocus(); } /** * Resets the toolbar to display the correct buttons for whatever * type of metadata is being edited. */ private void resetToolBarForEditing() { toolBar.removeAll(); // We can't permanently remove this button from the toolbar. If the user uses the // Alt.<- key combination to activate the button and it is removed from the toolbar // then the Swing event dispatch thread will throw an exception trying to determine the // parent of the button. So just make the button invisible if it is not needed. getEditParentButton().setVisible(false); toolBar.add(getEditParentButton()); if (editorPanel.getAddress().getParameter(NavAddress.ARGUMENT_PARAMETER) != null) { getEditParentButton().setVisible(true); toolBar.addSeparator(); } toolBar.add(getShowTreeButton()); toolBar.addSeparator(); toolBar.add(getCopyButton()); toolBar.add(getPasteButton()); toolBar.addSeparator(); toolBar.add(getCloseButton()); toolBar.add(getSaveButton()); toolBar.repaint(); getCloseButton().getModel().setRollover(false); getEditParentButton().getModel().setRollover(false); } /** * @return a new toolbar to display at the top of the navigation tree */ private JToolBar getNavTreeToolBar() { if (navTreeToolBar == null) { navTreeToolBar = new JToolBar(); navTreeToolBar.setRollover(true); navTreeToolBar.setFloatable(false); navTreeToolBar.add(getFlatAbbreviatedDisplayModeButton()); navTreeToolBar.add(getFlatFullyQualifiedDisplayModeButton()); navTreeToolBar.add(getHierarchicalDisplayModeButton()); navTreeDisplayModeButtonGroup = new ButtonGroup(); navTreeDisplayModeButtonGroup.add(getFlatAbbreviatedDisplayModeButton()); navTreeDisplayModeButtonGroup.add(getFlatFullyQualifiedDisplayModeButton()); navTreeDisplayModeButtonGroup.add(getHierarchicalDisplayModeButton()); } return navTreeToolBar; } /** * @return the flat abbreviated display mode button for the nav tree toolbar. */ private JToggleButton getFlatAbbreviatedDisplayModeButton() { if (flatAbbreviatedDisplayModeButton == null) { Action flatAbbreviatedDisplayModeAction = new AbstractAction(NavigatorMessages.getString("NAV_FlatAbbreviatedDisplayModeButton"), new ImageIcon(GemCutter.class.getResource("/Resources/nav_flatAbbrev.gif"))) { private static final long serialVersionUID = 5455343245437448702L; public void actionPerformed(ActionEvent e) { setNavTreeDisplayMode(TreeViewDisplayMode.FLAT_ABBREVIATED); } }; flatAbbreviatedDisplayModeAction.putValue(Action.SHORT_DESCRIPTION, NavigatorMessages.getString("NAV_FlatAbbreviatedDisplayModeButtonToolTip")); flatAbbreviatedDisplayModeAction.putValue(Action.MNEMONIC_KEY, FLAT_ABBREVIATED_DISPLAY_MODE_MNEMONIC); flatAbbreviatedDisplayModeButton = new JToggleButton(flatAbbreviatedDisplayModeAction); flatAbbreviatedDisplayModeButton.setText(""); } return flatAbbreviatedDisplayModeButton; } /** * @return the flat abbreviated display mode button for the nav tree toolbar. */ private JToggleButton getFlatFullyQualifiedDisplayModeButton() { if (flatFullyQualifiedDisplayModeButton == null) { Action flatFullyQualifiedDisplayModeAction = new AbstractAction(NavigatorMessages.getString("NAV_FlatFullyQualifiedDisplayModeButton"), new ImageIcon(GemCutter.class.getResource("/Resources/nav_flat.gif"))) { private static final long serialVersionUID = -8596519172745276785L; public void actionPerformed(ActionEvent e) { setNavTreeDisplayMode(TreeViewDisplayMode.FLAT_FULLY_QUALIFIED); } }; flatFullyQualifiedDisplayModeAction.putValue(Action.SHORT_DESCRIPTION, NavigatorMessages.getString("NAV_FlatFullyQualifiedDisplayModeButtonToolTip")); flatFullyQualifiedDisplayModeAction.putValue(Action.MNEMONIC_KEY, FLAT_FULLY_QUALIFIED_DISPLAY_MODE_MNEMONIC); flatFullyQualifiedDisplayModeButton = new JToggleButton(flatFullyQualifiedDisplayModeAction); flatFullyQualifiedDisplayModeButton.setText(""); } return flatFullyQualifiedDisplayModeButton; } /** * @return the hierarchical display mode button for the nav tree toolbar. */ private JToggleButton getHierarchicalDisplayModeButton() { if (hierarchicalDisplayModeButton == null) { Action hierarchicalDisplayModeAction = new AbstractAction(NavigatorMessages.getString("NAV_HierarchicalDisplayModeButton"), new ImageIcon(GemCutter.class.getResource("/Resources/nav_hierarchical.gif"))) { private static final long serialVersionUID = -7344771354219236965L; public void actionPerformed(ActionEvent e) { setNavTreeDisplayMode(TreeViewDisplayMode.HIERARCHICAL); } }; hierarchicalDisplayModeAction.putValue(Action.SHORT_DESCRIPTION, NavigatorMessages.getString("NAV_HierarchicalDisplayModeButtonToolTip")); hierarchicalDisplayModeAction.putValue(Action.MNEMONIC_KEY, HIERARCHICAL_DISPLAY_MODE_MNEMONIC); hierarchicalDisplayModeButton = new JToggleButton(hierarchicalDisplayModeAction); hierarchicalDisplayModeButton.setText(""); } return hierarchicalDisplayModeButton; } /** * @return a new toolbar to display at the top of the HTML viewer */ private JToolBar getToolBar() { if (toolBar == null) { toolBar = new JToolBar(); toolBar.setRollover(true); toolBar.setFloatable(false); toolBar.add(getBackButton()); toolBar.add(getForwardButton()); toolBar.addSeparator(); toolBar.add(getShowTreeButton()); toolBar.addSeparator(); toolBar.add(getPrintButton()); toolBar.add(getEditButton()); toolBar.addSeparator(); toolBar.add(getSearchPanel()); } return toolBar; } /** * @return the panel that contains the search text field and related components */ private JPanel getSearchPanel() { if (searchPanel == null) { searchPanel = new JPanel(); searchField = new JTextField(); searchField.setToolTipText(NavigatorMessages.getString("NAV_SearchLabelToolTip")); searchField.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER && searchField.getText().trim().length() != 0) { viewerPane.displayMetadata(NavAddress.getSearchAddress(searchField.getText())); } } }); Box vbox = Box.createVerticalBox(); vbox.add(Box.createVerticalStrut(2)); vbox.add(searchField); vbox.add(Box.createVerticalStrut(2)); searchPanel.setLayout(new BorderLayout()); searchPanel.add(new JLabel(NavigatorMessages.getString("NAV_SearchLabel")), BorderLayout.WEST); searchPanel.add(vbox); } return searchPanel; } /** * @return the back button for the navigation toolbar */ private JButton getBackButton() { if (backButton == null) { Action backAction = new AbstractAction(NavigatorMessages.getString("NAV_BackButton"), new ImageIcon(GemCutter.class.getResource("/Resources/leftArrow.gif"))) { private static final long serialVersionUID = 1072389882311854589L; public void actionPerformed(ActionEvent e) { stopEditing(true); viewerPane.goBack(); } }; backAction.putValue(Action.SHORT_DESCRIPTION, NavigatorMessages.getString("NAV_BackButtonToolTip")); backAction.putValue(Action.MNEMONIC_KEY, GO_BACK_MNEMONIC); backButton = new JButton(backAction); } return backButton; } /** * @return the back button for the navigation toolbar */ private JButton getForwardButton() { if (forwardButton == null) { Action forwardAction = new AbstractAction(NavigatorMessages.getString("NAV_ForwardButton"), new ImageIcon(GemCutter.class.getResource("/Resources/rightArrow.gif"))) { private static final long serialVersionUID = 4429725727823775928L; public void actionPerformed(ActionEvent e) { stopEditing(true); viewerPane.goForward(); } }; forwardAction.putValue(Action.SHORT_DESCRIPTION, NavigatorMessages.getString("NAV_ForwardButtonToolTip")); forwardAction.putValue(Action.MNEMONIC_KEY, GO_FORWARD_MNEMONIC); forwardButton = new JButton(forwardAction); } return forwardButton; } /** * @return the print button for the navigation toolbar */ private JButton getPrintButton() { if (printButton == null) { Action printAction = new AbstractAction(NavigatorMessages.getString("NAV_PrintButton"), new ImageIcon(GemCutter.class.getResource("/Resources/print.gif"))) { private static final long serialVersionUID = 5666754017212100780L; public void actionPerformed(ActionEvent e) { PrinterJob printerJob = PrinterJob.getPrinterJob(); if (printerJob.printDialog()) { try { printerJob.setPrintable(viewerPane); printerJob.print(); } catch (PrinterException ex) { JOptionPane.showMessageDialog(NavFrame.this, ex.getLocalizedMessage(), NavigatorMessages.getString("NAV_PrintError_Header"), JOptionPane.ERROR_MESSAGE); } } } }; printAction.putValue(Action.SHORT_DESCRIPTION, NavigatorMessages.getString("NAV_PrintButtonToolTip")); printAction.putValue(Action.MNEMONIC_KEY, PRINT_MNEMONIC); printButton = new JButton(printAction); } return printButton; } /** * @return the edit button for the navigation toolbar */ private JButton getEditButton() { if (editButton == null) { Action editAction = new AbstractAction(NavigatorMessages.getString("NAV_EditButton"), new ImageIcon(GemCutter.class.getResource("/Resources/nav_edit.gif"))) { private static final long serialVersionUID = -1584426840280415158L; public void actionPerformed(ActionEvent e) { owner.editMetadata(viewerPane.getCurrentAddress()); } }; editAction.putValue(Action.SHORT_DESCRIPTION, NavigatorMessages.getString("NAV_EditButtonToolTip")); editAction.putValue(Action.MNEMONIC_KEY, EDIT_MNEMONIC); editButton = new JButton(editAction); } return editButton; } /** * @return the button to return to editing the parent metadata */ private JButton getEditParentButton() { if (editParentButton == null) { Action editParentAction = new AbstractAction(NavigatorMessages.getString("NAV_EditParentButton"), new ImageIcon(GemCutter.class.getResource("/Resources/leftArrow.gif"))) { private static final long serialVersionUID = -259012751726184443L; public void actionPerformed(ActionEvent e) { owner.editMetadata(editorPanel.getAddress().withAllStripped()); } }; editParentAction.putValue(Action.SHORT_DESCRIPTION, NavigatorMessages.getString("NAV_EditParentButtonToolTip")); editParentAction.putValue(Action.MNEMONIC_KEY, EDIT_PARENT_MNEMONIC); editParentButton = new JButton(editParentAction); } return editParentButton; } /** * @return the save button for the navigation toolbar */ private JButton getSaveButton() { if (saveButton == null) { Action saveAction = new AbstractAction(NavigatorMessages.getString("NAV_SaveButton"), new ImageIcon(GemCutter.class.getResource("/Resources/save.gif"))) { private static final long serialVersionUID = -6248180609589398157L; public void actionPerformed(ActionEvent e) { saveChanges(); } }; saveAction.putValue(Action.SHORT_DESCRIPTION, NavigatorMessages.getString("NAV_SaveButtonToolTip")); saveAction.putValue(Action.MNEMONIC_KEY, SAVE_MNEMONIC); saveButton = new JButton(saveAction); } return saveButton; } /** * @return the stop editing button for the navigation toolbar */ private JButton getCloseButton() { if (closeButton == null) { Action closeAction = new AbstractAction(NavigatorMessages.getString("NAV_CloseButton"), new ImageIcon(GemCutter.class.getResource("/Resources/checkmark.gif"))) { private static final long serialVersionUID = -6951821908195739490L; public void actionPerformed(ActionEvent e) { stopEditing(true); } }; closeAction.putValue(Action.SHORT_DESCRIPTION, NavigatorMessages.getString("NAV_CloseButtonToolTip")); closeAction.putValue(Action.MNEMONIC_KEY, CLOSE_MNEMONIC); closeButton = new JButton(closeAction); } return closeButton; } /** * @return the toggle button for showing/hiding the navigation tree */ private JToggleButton getShowTreeButton() { if (showTreeButton == null) { Action showAction = new AbstractAction(NavigatorMessages.getString("NAV_NavigationPanelButton"), new ImageIcon(GemCutter.class.getResource("/Resources/catByModule.gif"))) { private static final long serialVersionUID = -6780509043470798286L; public void actionPerformed(ActionEvent e) { setSidePanelVisible(showTreeButton.isSelected()); } }; showAction.putValue(Action.SHORT_DESCRIPTION, NavigatorMessages.getString("NAV_NavigationPanelButtonToolTip")); showAction.putValue(Action.MNEMONIC_KEY, TOGGLE_PANEL_MNEMONIC); showTreeButton = new JToggleButton(showAction); } return showTreeButton; } /** * @return the copy metadata button for copying metadata */ private JButton getCopyButton() { if (copyButton == null) { Action copyAction = new AbstractAction(NavigatorMessages.getString("NAV_CopyButton"), new ImageIcon(GemCutter.class.getResource("/Resources/copy.gif"))) { private static final long serialVersionUID = 4519220031949265137L; public void actionPerformed(ActionEvent e) { if (isEditing()) { copiedMetadata = editorPanel.getEditedMetadata(); getPasteButton().setEnabled(true); } } }; copyAction.putValue(Action.SHORT_DESCRIPTION, NavigatorMessages.getString("NAV_CopyButtonToolTip")); copyAction.setEnabled(true); copyButton = new JButton(copyAction); } return copyButton; } /** * @return the paste metadata button for pasting metadata */ private JButton getPasteButton() { if (pasteButton == null) { Action pasteAction = new AbstractAction(NavigatorMessages.getString("NAV_ReplaceButton"), new ImageIcon(GemCutter.class.getResource("/Resources/paste.gif"))) { private static final long serialVersionUID = 1192026908334065285L; public void actionPerformed(ActionEvent e) { if (isEditing() && copiedMetadata != null) { editorPanel.setEditedMetadata(copiedMetadata); } } }; pasteAction.putValue(Action.SHORT_DESCRIPTION, NavigatorMessages.getString("NAV_ReplaceButtonToolTip")); pasteAction.setEnabled(false); pasteButton = new JButton(pasteAction); } return pasteButton; } /** * @return a new copy menu item for the navigation tree popup menu */ private JMenuItem getCopyMenuItem() { Action copyAction = new AbstractAction(NavigatorMessages.getString("NAV_CopyMenuItem"), new ImageIcon(GemCutter.class.getResource("/Resources/copy.gif"))) { private static final long serialVersionUID = 6561547024209053533L; public void actionPerformed(ActionEvent e) { NavTreeNode selectedNode = (NavTreeNode) navTree.getSelectionPath().getLastPathComponent(); NavAddress address = selectedNode.getAddress(); copiedMetadata = NavAddressHelper.getMetadata(owner, address); getPasteButton().setEnabled(true); } }; TreePath selectionPath = navTree.getSelectionPath(); NavTreeNode selectedNode = selectionPath != null ? (NavTreeNode) selectionPath.getLastPathComponent() : null; NavAddress address = selectedNode != null ? selectedNode.getAddress() : null; copyAction.setEnabled(selectedNode != null && NavAddressHelper.getMetadata(owner, address) != null); return UIUtilities.makeNewMenuItem(copyAction); } /** * @return the pane that displays the company logo at the top of the window */ private JEditorPane getHeaderPane() { // Form the header HTML StringBuilder header = new StringBuilder(); String headerBackground = GemCutter.class.getResource("/Resources/bobj_header_background.png").toString(); String headerImage = GemCutter.class.getResource("/Resources/bobj_header.png").toString(); String altBackground = GemCutter.class.getResource("/Resources/bobj_header_background_alt.jpg").toString(); header.append("<html><body bgcolor='#FFFFFF'>"); header.append("<table border='0' bgcolor='#FFFFFF' width='100%' cellpadding='0' cellspacing='0'><tr>"); header.append("<td align='left' valign='top' background='" + headerBackground + "'>  <font size='5'><b><i>"); header.append(NavigatorMessages.getString("NAV_PropertiesBrowser_Header")); header.append("</i></b></font></td>"); header.append("<td align='right' width='279' background='" + altBackground + "'><img src='" + headerImage + "' border='0'></td>"); header.append("</tr></table>"); header.append("</body></html>"); // Create the editor pane JEditorPane headerPane = new JEditorPane(); HTMLEditorKit editorKit = new HTMLEditorKit(); HTMLDocument doc = (HTMLDocument) editorKit.createDefaultDocument(); headerPane.setEditorKit(editorKit); headerPane.setDocument(doc); headerPane.setEditable(false); headerPane.setText(header.toString()); headerPane.setBorder(null); // The exact minimum width/height needed to display the header. headerPane.setMinimumSize(new Dimension(430, 42)); headerPane.setPreferredSize(new Dimension(430, 42)); return headerPane; } /** * Sets the display mode for the navigation tree, refreshes its display, and saves * out the preference. * @param navTreeDisplayMode the display mode. */ private void setNavTreeDisplayMode(final TreeViewDisplayMode navTreeDisplayMode) { NavTreeModel model = navTree.getModel(); model.load(owner.getPerspective(), navTreeDisplayMode); navTree.collapseToModules(); NavTreeNode node = model.getNodeForAddress(viewerPane.getCurrentAddress()); if (node != null) { TreePath path = new TreePath(node.getPath()); navTree.setSelectionPath(path); navTree.scrollPathToVisible(path); } // save the preference getPreferences().put(NAV_TREE_DISPLAY_MODE, navTreeDisplayMode.getName()); // set the button states setNavTreeDisplayModeButtonStates(navTreeDisplayMode); } /** * Sets the states of the buttons in the nav tree toolbar. * @param navTreeDisplayMode the display mode. */ private void setNavTreeDisplayModeButtonStates(final TreeViewDisplayMode navTreeDisplayMode) { if (navTreeDisplayMode == TreeViewDisplayMode.FLAT_ABBREVIATED) { getFlatAbbreviatedDisplayModeButton().setSelected(true); } else if (navTreeDisplayMode == TreeViewDisplayMode.FLAT_FULLY_QUALIFIED) { getFlatFullyQualifiedDisplayModeButton().setSelected(true); } else if (navTreeDisplayMode == TreeViewDisplayMode.HIERARCHICAL) { getHierarchicalDisplayModeButton().setSelected(true); } else { throw new IllegalArgumentException("Unrecognized TreeViewDisplayMode: " + navTreeDisplayMode); } } /** * @return the display mode of the navigation tree. */ private TreeViewDisplayMode getNavTreeDisplayMode() { if (getFlatAbbreviatedDisplayModeButton().isSelected()) { return TreeViewDisplayMode.FLAT_ABBREVIATED; } else if (getFlatFullyQualifiedDisplayModeButton().isSelected()) { return TreeViewDisplayMode.FLAT_FULLY_QUALIFIED; } else if (getHierarchicalDisplayModeButton().isSelected()) { return TreeViewDisplayMode.HIERARCHICAL; } else { // this is the default mode return TreeViewDisplayMode.FLAT_ABBREVIATED; } } }