/* * 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. */ /* * NavEditorPanel.java * Creation date: Jul 8, 2003 * By: Frank Worsley */ package org.openquark.gems.client.navigator; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.KeyboardFocusManager; import java.awt.Rectangle; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.LayoutFocusTraversalPolicy; import javax.swing.Scrollable; import javax.swing.SwingUtilities; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.ScopedEntityNamingPolicy; import org.openquark.cal.compiler.ScopedEntityNamingPolicy.UnqualifiedUnlessAmbiguous; import org.openquark.cal.metadata.ArgumentMetadata; import org.openquark.cal.metadata.CALFeatureMetadata; import org.openquark.cal.metadata.ClassMethodMetadata; import org.openquark.cal.metadata.FunctionMetadata; import org.openquark.cal.metadata.FunctionalAgentMetadata; import org.openquark.cal.metadata.InstanceMethodMetadata; import org.openquark.gems.client.navigator.NavAddress.NavAddressMethod; /** * This class implements the metadata editing component for the CAL navigator. * It dynamically builds an editing UI depending on what kind of metadata is being * edited. The UI consists of editing sections embedded within the component. * Sections can be expanded by the user to reveal additional metadata attributes * that can be edited. * * This component is completely independent of the actual NavFrame class and * can be used by any class implementing the NavFrameOwner interface. * * @author Frank Worsley */ class NavEditorPanel extends JPanel implements Scrollable { private static final long serialVersionUID = 4454976555509376719L; /** * The focus traversal policy for the editor panel. It ensures that the top * editor section will receive initial and default focus. * @author Frank Worsley */ private class EditorFocusTraversalPolicy extends LayoutFocusTraversalPolicy { private static final long serialVersionUID = 3928878956202325352L; @Override public Component getDefaultComponent(Container c) { return editorSections.get(0); } @Override public Component getFirstComponent(Container c) { return editorSections.get(0); } @Override public Component getLastComponent(Container c) { return editorSections.get(editorSections.size() - 1); } } /** * A listener that listens to the component that has focus. If the focused component * is an editor section or the child of an editor section it will give the editor * section a focused look. * @author Frank Worsley */ private class EditorFocusChangeListener implements PropertyChangeListener { /** The editor section that currently has focus. */ private NavEditorSection currentFocusedSection = null; /** The editor section that last had focused (current section if a section currently has focus). */ private Component lastFocusedSection = null; /** The component that last had focus. */ private Component lastFocusedComponent = null; public void propertyChange(PropertyChangeEvent evt) { Component newFocusOwner = (Component) evt.getNewValue(); Component parent = newFocusOwner; // Check if the new focus owner is a child of an editor section. // If it is then give the section the focused look. while (parent != null) { if (parent instanceof NavEditorSection) { if (newFocusOwner instanceof NavEditorSection && newFocusOwner != lastFocusedSection) { // If it's a new section make sure the top title is visible if (lastFocusedSection != newFocusOwner) { JComponent expander = ((NavEditorSection) newFocusOwner).getExpander(); expander.scrollRectToVisible(expander.getBounds()); } lastFocusedSection = newFocusOwner; } else if (newFocusOwner != lastFocusedComponent) { // We need to convert the coordinates and scroll directly in the // editor pane since some editors may be viewports themselves // and will then try to do the scrolling (ie: JTextField). Rectangle bounds = newFocusOwner.getBounds(); Rectangle parentBounds = SwingUtilities.convertRectangle(newFocusOwner.getParent(), bounds, NavEditorPanel.this); scrollRectToVisible(parentBounds); lastFocusedComponent = newFocusOwner; } if (currentFocusedSection != null && parent != currentFocusedSection) { currentFocusedSection.setFocusedLook(false); } if (currentFocusedSection != parent) { currentFocusedSection = (NavEditorSection) parent; currentFocusedSection.setFocusedLook(true); } return; } parent = parent.getParent(); } // clear the previous section's focused look if (currentFocusedSection != null) { currentFocusedSection.setFocusedLook(false); currentFocusedSection = null; } } /** * Restore the focus to the component of the editor panel that was last focused. */ public void restoreSavedFocus() { // We try the last component, section and then just do the default component. We have to // check if the parent is still this editor panel since editors/sections may have been // removed from the panel since the focus was last saved. if (lastFocusedComponent != null && SwingUtilities.isDescendingFrom(lastFocusedComponent, NavEditorPanel.this)) { lastFocusedComponent.requestFocus(); } else if (lastFocusedSection != null && SwingUtilities.isDescendingFrom(lastFocusedSection, NavEditorPanel.this)) { lastFocusedSection.requestFocus(); } else { getFocusTraversalPolicy().getDefaultComponent(NavEditorPanel.this).requestFocus(); } } } /** The navigator owner that owns this editor panel. */ private final NavFrameOwner owner; /** The metadata object being edited by this editor panel. */ private final CALFeatureMetadata metadata; /** The address of the object whose metadata is being edited. */ private final NavAddress address; /** The editor sections contained in this editor panel in the order they appear. */ private final List<NavEditorSection> editorSections = new ArrayList<NavEditorSection>(); /** The JPanel that contains the editor sections. */ private final JPanel sectionPanel = new JPanel(); /** The argument editor section, if editing entity metadata. */ private NavEditorSection argumentSection = null; /** Whether or not the values stored by the editors in the editor panel have changed. */ private boolean hasChanged = false; /** The label for displaying the name of the metadata being edited. */ private final JLabel titleLabel = new JLabel(); /** The label for displaying the type of the metadata being edited. */ private final JLabel typeLabel = new JLabel(); /** The label to display the type of the argument for which editing argument metadata. */ private final JLabel argTypeLabel = new JLabel(); /** The focus listener that is used to create the focus effects of the editor sections. */ private EditorFocusChangeListener focusListener; /** * Constructs a new editor panel to edit the given metadata object. * @param owner the owner of this editor panel * @param address the address of the metadata to edit */ public NavEditorPanel(NavFrameOwner owner, NavAddress address) { if (owner == null || address == null) { throw new NullPointerException(); } this.owner = owner; this.address = address; this.metadata = NavAddressHelper.getMetadata(owner, address); if (metadata == null) { throw new IllegalArgumentException("no metadata associated with address: " + address); } initialize(); } /** * Initializes the UI of the editor panel by creating default components * and adding editor sections relevant to the metadata being edited. */ private void initialize() { // Setup the basic properties setBackground(Color.WHITE); setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); setLayout(new BorderLayout()); // Install a focus policy to always give default focus to the topmost editor section focusListener = new EditorFocusChangeListener(); addFocusListener(); setFocusCycleRoot(true); setFocusTraversalPolicy(new EditorFocusTraversalPolicy()); // Add the title labels typeLabel.setFont(getFont().deriveFont(Font.BOLD, getFont().getSize() + 2)); titleLabel.setFont(getFont().deriveFont(Font.BOLD, getFont().getSize() + 12)); argTypeLabel.setFont(getFont().deriveFont(Font.ITALIC, getFont().getSize() + 12)); Box vbox = Box.createVerticalBox(); Box hbox = Box.createHorizontalBox(); hbox.add(typeLabel); hbox.add(Box.createHorizontalGlue()); vbox.add(hbox); hbox = Box.createHorizontalBox(); hbox.add(titleLabel); hbox.add(argTypeLabel); hbox.add(Box.createHorizontalGlue()); vbox.add(hbox); add(vbox, BorderLayout.NORTH); // Add the panel for the editor sections sectionPanel.setOpaque(false); sectionPanel.setLayout(new BoxLayout(sectionPanel, BoxLayout.Y_AXIS)); add(sectionPanel, BorderLayout.CENTER); // Sucks up extra vertical space at the bottom of the section panel sectionPanel.add(Box.createVerticalGlue()); // Add the basic metadata editing section addSection(new NavFeatureEditorSection(this)); // Add entity metadata editing sections if (metadata instanceof FunctionalAgentMetadata || metadata instanceof InstanceMethodMetadata) { addSection(new NavGemEntityEditorSection(this)); boolean hasReturnValue = metadata instanceof FunctionMetadata || metadata instanceof ClassMethodMetadata || metadata instanceof InstanceMethodMetadata; argumentSection = new NavEntityArgumentEditorSection(this, hasReturnValue); addSection(argumentSection); addSection(new NavExampleEditorSection(this)); } // Add an argument editing section if (metadata instanceof ArgumentMetadata) { addSection(new NavArgumentEditorSection(this)); } // Add a custom attribute section addSection(new NavCustomAttributeEditorSection(this)); // Load the metadata revert(); // Add the finishing touches updateTitleLabel(); editorSections.get(0).setExpanded(true); } /** * Install a listener for focus events to handle focusing the editing sections. */ public void addFocusListener() { KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); focusManager.addPropertyChangeListener("focusOwner", focusListener); } /** * Removes the focus listener installed by the editor panel. This must be called * when the editor panel is disposed off, otherwise it will leak a focus listener. */ private void removeFocusListener() { KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); focusManager.removePropertyChangeListener("focusOwner", focusListener); } /** * Sets focus on the component of the editor panel that should have initial focus. */ public void setInitialFocus() { focusListener.restoreSavedFocus(); } /** * Updates the text displayed in the title label to match what is stored * in the metadata. */ private void updateTitleLabel() { argTypeLabel.setText(null); typeLabel.setText(getTypeString()); titleLabel.setText(NavAddressHelper.getDisplayText(owner, address, ScopedEntityNamingPolicy.UNQUALIFIED)); if (address.getParameter(NavAddress.ARGUMENT_PARAMETER) != null) { int argumentNumber = Integer.parseInt(address.getParameter(NavAddress.ARGUMENT_PARAMETER)); ModuleTypeInfo moduleInfo = owner.getPerspective().getWorkingModuleTypeInfo(); ScopedEntityNamingPolicy namingPolicy = new UnqualifiedUnlessAmbiguous(moduleInfo); NavAddress parentAddress; if (address.getMethod() == NavAddress.INSTANCE_METHOD_METHOD) { parentAddress = NavAddress.getAddress(address.toFeatureName()); // this strips out the &argument=n parameter } else { parentAddress = address.withAllStripped(); } String[] typeStrings = NavAddressHelper.getTypeStrings(owner, parentAddress, namingPolicy); argTypeLabel.setText(" :: " + typeStrings[argumentNumber]); } } /** * @return the string to use for the type label */ private String getTypeString() { NavAddressMethod method = address.getMethod(); if (address.getParameter(NavAddress.ARGUMENT_PARAMETER) != null) { if (method == NavAddress.INSTANCE_METHOD_METHOD) { NavAddress parentAddress = NavAddress.getAddress(address.toFeatureName()); // this strips out the &argument=n parameter String parentName = NavAddressHelper.getDisplayText(owner, parentAddress); return NavigatorMessages.getString("NAV_EditingInstanceMethodArg_Header", parentName); } else { String parentName = NavAddressHelper.getDisplayText(owner, address.withAllStripped()); if (method == NavAddress.FUNCTION_METHOD) { return NavigatorMessages.getString("NAV_EditingFunctionArg_Header", parentName); } else if (method == NavAddress.CLASS_METHOD_METHOD) { return NavigatorMessages.getString("NAV_EditingClassMethodArg_Header", parentName); } else if (method == NavAddress.DATA_CONSTRUCTOR_METHOD) { return NavigatorMessages.getString("NAV_EditingConstructorArg_Header", parentName); } else if (method == NavAddress.COLLECTOR_METHOD) { return NavigatorMessages.getString("NAV_EditingCollectorArg_Header", parentName); } else { return NavigatorMessages.getString("NAV_EditingGenericArg_Header"); } } } else if (method == NavAddress.COLLECTOR_METHOD) { return NavigatorMessages.getString("NAV_EditingCollector_Header"); } else if (method == NavAddress.MODULE_METHOD) { return NavigatorMessages.getString("NAV_EditingModule_Header"); } else if (method == NavAddress.FUNCTION_METHOD) { return NavigatorMessages.getString("NAV_EditingFunction_Header"); } else if (method == NavAddress.TYPE_CLASS_METHOD) { return NavigatorMessages.getString("NAV_EditingClass_Header"); } else if (method == NavAddress.TYPE_CONSTRUCTOR_METHOD) { return NavigatorMessages.getString("NAV_EditingType_Header"); } else if (method == NavAddress.DATA_CONSTRUCTOR_METHOD) { return NavigatorMessages.getString("NAV_EditingConstructor_Header"); } else if (method == NavAddress.CLASS_METHOD_METHOD) { return NavigatorMessages.getString("NAV_EditingClassMethod_Header"); } else if (method == NavAddress.CLASS_INSTANCE_METHOD) { return NavigatorMessages.getString("NAV_EditingClassInstance_Header"); } else if (method == NavAddress.INSTANCE_METHOD_METHOD) { return NavigatorMessages.getString("NAV_EditingInstanceMethod_Header"); } else { throw new IllegalArgumentException("address type not supported: " + method); } } /** * Adds a new editor section to this editor panel. * @param section the new section to add */ void addSection(NavEditorSection section) { editorSections.add(section); sectionPanel.add(Box.createVerticalStrut(10)); sectionPanel.add(section); } /** * Removes all editor sections from this editor panel. */ void removeAllSections() { sectionPanel.removeAll(); sectionPanel.add(Box.createVerticalGlue()); editorSections.clear(); } /** * Called when the values stored by the editors in the given section have changed. * @param section the section whose editors changed */ void sectionChanged(NavEditorSection section) { hasChanged = true; } /** * @return true if the editor sections in this editor panel have changed since the metadata was last saved */ public boolean hasChanged() { return hasChanged; } /** * Validates all sections in the editor panel. * @return true if all values are valid, false otherwise */ public boolean checkValues() { boolean hasErrors = false; for (final NavEditorSection section : editorSections) { if (!section.doValidate()) { hasErrors = true; } } return !hasErrors; } /** * Refreshes the metadata being edited. This will display the newly refreshed metadata * in the edit boxes, overwriting any changes made by the user. */ public void refresh() { NavAddressHelper.refreshMetadata(owner, address, metadata); revert(); } /** * This will revert the displayed values to the stored values from the metadata object. */ private void revert() { for (final NavEditorSection section : editorSections) { section.doRevert(); section.doValidate(); } hasChanged = false; updateTitleLabel(); } /** * This method tells the editor panel that the editing process should stop. * It causes the focus listener to be removed and the editComplete method of the owner * of this editor panel to be invoked. */ void stopEditing() { removeFocusListener(); owner.editComplete(address); } /** * Saves the values stored in the editors of all sections back into the metadata * object being edited and saves the metadata back to permanent storage. */ public boolean save() { // Make sure all sections are valid if (!checkValues()) { return false; } // Save each section for (final NavEditorSection section : editorSections) { section.doSave(); } // Now try to permanently store the metadata. if (saveMetadata()) { owner.metadataChanged(address); hasChanged = false; updateTitleLabel(); return true; } return false; } /** * @return a metadata object for the metadata currently being displayed in the editors. * The metadata will have been saved into the object that is returned, but it was not * saved to permanent storage. */ public CALFeatureMetadata getEditedMetadata() { // Back up the current metadata. CALFeatureMetadata currentMetadata = metadata.copy(); // Save each section for (final NavEditorSection section : editorSections) { section.doSave(); } // Restore the saved metadata. CALFeatureMetadata editedMetadata = metadata.copy(); currentMetadata.copyTo(metadata); return editedMetadata; } /** * Sets the metadata currently being displayed in the editor fields to the values stored in * the given metadata object. The new values will not be saved to permanent storage. * @param editedMetadata the new metadata to be edited */ public void setEditedMetadata(CALFeatureMetadata editedMetadata) { editedMetadata.copyTo(metadata); revert(); hasChanged = true; } /** * Saves the metadata object being edited. * @return true if saved successfully, false otherwise */ private boolean saveMetadata() { return NavAddressHelper.saveMetadata(owner, address, metadata); } /** * @return the navigator owner that is using this editor panel */ NavFrameOwner getNavigatorOwner() { return owner; } /** * @return the address of the metadata object being edited */ NavAddress getAddress() { return address; } /** * @return the metadata object being edited by this editor panel */ CALFeatureMetadata getMetadata() { return metadata; } /** * @see javax.swing.Scrollable#getScrollableTracksViewportHeight() */ public boolean getScrollableTracksViewportHeight() { return false; } /** * @see javax.swing.Scrollable#getScrollableTracksViewportWidth() */ public boolean getScrollableTracksViewportWidth() { return true; } /** * @see javax.swing.Scrollable#getPreferredScrollableViewportSize() */ public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } /** * @see javax.swing.Scrollable#getScrollableBlockIncrement(java.awt.Rectangle, int, int) */ public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { return 150; } /** * @see javax.swing.Scrollable#getScrollableUnitIncrement(java.awt.Rectangle, int, int) */ public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { return 50; } }