/* $Id: PropPanel.java 18022 2010-02-16 08:24:42Z bobtarling $ ***************************************************************************** * Copyright (c) 2009 Contributors - see below * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * tfmorris ***************************************************************************** * * Some portions of this file was previously release using the BSD License: */ // Copyright (c) 1996-2008 The Regents of the University of California. All // Rights Reserved. Permission to use, copy, modify, and distribute this // software and its documentation without fee, and without a written // agreement is hereby granted, provided that the above copyright notice // and this paragraph appear in all copies. This software program and // documentation are copyrighted by The Regents of the University of // California. The software program and documentation are supplied "AS // IS", without any accompanying services from The Regents. The Regents // does not warrant that the operation of the program will be // uninterrupted or error-free. The end-user understands that the program // was developed for research purposes and is advised not to rely // exclusively on the program for any reason. IN NO EVENT SHALL THE // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. package org.argouml.uml.ui; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.GridLayout; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import javax.swing.Action; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JToolBar; import javax.swing.ListModel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.border.TitledBorder; import javax.swing.event.EventListenerList; import org.apache.log4j.Logger; import org.argouml.application.api.AbstractArgoJPanel; import org.argouml.application.helpers.ResourceLoaderWrapper; import org.argouml.i18n.Translator; import org.argouml.kernel.ProfileConfiguration; import org.argouml.kernel.ProjectManager; import org.argouml.model.Model; import org.argouml.ui.ActionCreateContainedModelElement; import org.argouml.ui.LookAndFeelMgr; import org.argouml.ui.TabModelTarget; import org.argouml.ui.targetmanager.TargetEvent; import org.argouml.ui.targetmanager.TargetListener; import org.argouml.ui.targetmanager.TargettableModelView; import org.tigris.gef.presentation.Fig; import org.tigris.swidgets.GridLayout2; import org.tigris.swidgets.Orientation; import org.tigris.toolbar.ToolBarFactory; /** * This abstract class provides the basic layout and event dispatching support * for all Property Panels.<p> * * The property panel is {@link org.argouml.uml.ui.LabelledLayout} layed out as * a number (specified in the constructor) of equally sized panels that split * the available space. Each panel has a column of "captions" and matching * column of "fields" which are laid out independently from the other * panels.<p> * * The Properties panels for UML Model Elements are structured in an inheritance * hierarchy that matches the UML metamodel. */ public abstract class PropPanel extends AbstractArgoJPanel implements UMLUserInterfaceContainer, ComponentListener { /** * Logger. */ private static final Logger LOG = Logger.getLogger(PropPanel.class); private Object target; private Object modelElement; /** * List of event listeners to notify. This is computed one time and frozen * the first time any target change method (e.g. setTarget, targetAdded) is * called. */ private EventListenerList listenerList; private JPanel buttonPanel = new JPanel(new GridLayout()); private JLabel titleLabel; /** * A list with "actions".<p> * * Action in this respect are one of:<ul> * <li> {@link Action} * <li> {@link JButton} * <li> {@link Object}[] * </ul> */ private List actions = new ArrayList(); private static Font stdFont = LookAndFeelMgr.getInstance().getStandardFont(); /** * Construct new PropPanel using LabelledLayout. * <p> * @param icon The icon to display for the panel * @param label The label for the title of the panel (to be localized). */ public PropPanel(String label, ImageIcon icon) { super(Translator.localize(label)); LabelledLayout layout = new LabelledLayout(); layout.setHgap(5); setLayout(layout); if (icon != null) { setTitleLabel(new JLabel(Translator.localize(label), icon, SwingConstants.LEFT)); } else { setTitleLabel(new JLabel(Translator.localize(label))); } titleLabel.setLabelFor(buttonPanel); add(titleLabel); add(buttonPanel); addComponentListener(this); } /* * @see org.tigris.swidgets.Orientable#setOrientation(org.tigris.swidgets.Orientation) */ @Override public void setOrientation(Orientation orientation) { // TODO: Do we need to change the layout manager when // changing orientation to match the behavior of the constructor? // if (getOrientation() != orientation) { // LabelledLayout layout = new LabelledLayout(orientation == Vertical // .getInstance()); // setLayout(layout); // } super.setOrientation(orientation); } /** * Add a button to the toolbar of a property panel using the action to * control the behavior of the action. * * @param action * the action which will be used in the toolbar button. */ protected void addAction(Action action) { actions.add(action); } /** * Add a button to the toolbar of a property panel using the action to * control the behavior of the action. * * @param action * the action which will be used in the toolbar button. * @param tooltip * the tooltip to set, or null to skip setting of a new tooltip. */ protected void addAction(Action action, String tooltip) { JButton button = new TargettableButton(action); if (tooltip != null) { button.setToolTipText(tooltip); } button.setText(""); button.setFocusable(false); actions.add(button); } /** * Add multiple buttons at once. * * @param actionArray the Actions. */ protected void addAction(Object[] actionArray) { actions.add(actionArray); } public void buildToolbar() { LOG.debug("Building toolbar"); ToolBarFactory factory = new ToolBarFactory(getActions()); factory.setRollover(true); factory.setFloatable(false); JToolBar toolBar = factory.createToolBar(); toolBar.setName("misc.toolbar.properties"); buttonPanel.removeAll(); buttonPanel.add(BorderLayout.WEST, toolBar); // Set the tooltip of the arrow to open combined tools: buttonPanel.putClientProperty("ToolBar.toolTipSelectTool", Translator.localize("action.select")); } /** * Get the actions that will make up the toolbar on this panel. * @return The list of actions to show for this panel. */ protected List getActions() { return actions; } private static class TargettableButton extends JButton implements TargettableModelView { public TargettableButton(Action action) { super(action); } public TargetListener getTargettableModel() { if (getAction() instanceof TargetListener) { return (TargetListener) getAction(); } return null; } } /** * Add a component with the specified label.<p> * * @param label * the label for the component * @param component * the component * @return the label added */ public JLabel addField(String label, Component component) { JLabel jlabel = createLabelFor(label, component); component.setFont(stdFont); add(jlabel); add(component); if (component instanceof UMLLinkedList) { UMLModelElementListModel2 list = (UMLModelElementListModel2) ((UMLLinkedList) component).getModel(); ActionCreateContainedModelElement newAction = new ActionCreateContainedModelElement( list.getMetaType(), list.getTarget(), "New..."); // TODO: i18n } return jlabel; } /** * @param label The text of the label (the method cares about i18n) * @param comp The component that this label is for * @return a new JLabel */ private JLabel createLabelFor(String label, Component comp) { JLabel jlabel = new JLabel(Translator.localize(label)); jlabel.setToolTipText(Translator.localize(label)); jlabel.setFont(stdFont); jlabel.setLabelFor(comp); return jlabel; } /** * Add a component with the specified label positioned after another * component. * * @param label * the label for the component * @param component * the component * @param afterComponent * the component before * @return the newly added label */ public JLabel addFieldAfter(String label, Component component, Component afterComponent) { int nComponent = getComponentCount(); for (int i = 0; i < nComponent; ++i) { if (getComponent(i) == afterComponent) { JLabel jlabel = createLabelFor(label, component); component.setFont(stdFont); add(jlabel, ++i); add(component, ++i); return jlabel; } } throw new IllegalArgumentException("Component not found"); } /** * Add a component with the specified label positioned before another * component.<p> * * @param label * the label for the component * @param component * the to be added component * @param beforeComponent * the component before its label we add * @return the newly added component */ public JLabel addFieldBefore(String label, Component component, Component beforeComponent) { int nComponent = getComponentCount(); for (int i = 0; i < nComponent; ++i) { if (getComponent(i) == beforeComponent) { JLabel jlabel = createLabelFor(label, component); component.setFont(stdFont); add(jlabel, i - 1); add(component, i++); return jlabel; } } throw new IllegalArgumentException("Component not found"); } /** * Add a separator. */ protected final void addSeparator() { add(LabelledLayout.getSeparator()); } /** * Set the target to be associated with a particular property panel.<p> * * This involves resetting the third party listeners. * * @param t * The object to be set as a target. */ public void setTarget(Object t) { LOG.debug("setTarget called with " + t + " as parameter (not target!)"); t = (t instanceof Fig) ? ((Fig) t).getOwner() : t; // If the target has changed notify the third party listener if it // exists and dispatch a new element event listener to // ourself. Otherwise dispatch a target reasserted to ourself. Runnable dispatch = null; if (t != target) { // Set up the target and its model element variant. target = t; modelElement = null; if (listenerList == null) { listenerList = collectTargetListeners(this); } if (Model.getFacade().isAUMLElement(target)) { modelElement = target; } // This will add a new ModelElement event listener // after update is complete dispatch = new UMLChangeDispatch(this, UMLChangeDispatch.TARGET_CHANGED_ADD); buildToolbar(); } else { dispatch = new UMLChangeDispatch(this, UMLChangeDispatch.TARGET_REASSERTED); } SwingUtilities.invokeLater(dispatch); // update the titleLabel // MVW: This overrules the icon set initiallly... Why do we need this? if (titleLabel != null) { Icon icon = null; if (t != null) { icon = ResourceLoaderWrapper.getInstance().lookupIcon(t); } if (icon != null) { titleLabel.setIcon(icon); } } } /** * Builds a eventlistenerlist of all targetlisteners that are part of this * container and its children. Components do not need to register * themselves. They are registered implicitly if they implement the * TargetListener interface. * * @param container * the container to search for targetlisteners * @return an EventListenerList with all TargetListeners on this container * and its children. */ private EventListenerList collectTargetListeners(Container container) { Component[] components = container.getComponents(); EventListenerList list = new EventListenerList(); for (int i = 0; i < components.length; i++) { if (components[i] instanceof TargetListener) { list.add(TargetListener.class, (TargetListener) components[i]); } if (components[i] instanceof TargettableModelView) { list.add(TargetListener.class, ((TargettableModelView) components[i]) .getTargettableModel()); } if (components[i] instanceof Container) { EventListenerList list2 = collectTargetListeners( (Container) components[i]); Object[] objects = list2.getListenerList(); for (int j = 1; j < objects.length; j += 2) { list.add(TargetListener.class, (TargetListener) objects[j]); } } } if (container instanceof PropPanel) { /* We presume that the container equals this PropPanel. */ for (TargetListener action : collectTargetListenerActions()) { list.add(TargetListener.class, action); } } return list; } private Collection<TargetListener> collectTargetListenerActions() { Collection<TargetListener> set = new HashSet<TargetListener>(); for (Object obj : actions) { if (obj instanceof TargetListener) { set.add((TargetListener) obj); } } return set; } /* * @see org.argouml.ui.TabTarget#getTarget() */ public final Object getTarget() { return target; } /* * @see org.argouml.ui.TabTarget#refresh() */ public void refresh() { SwingUtilities.invokeLater(new UMLChangeDispatch(this, 0)); } /* * @see org.argouml.ui.TabTarget#shouldBeEnabled(java.lang.Object) */ public boolean shouldBeEnabled(Object t) { t = (t instanceof Fig) ? ((Fig) t).getOwner() : t; return Model.getFacade().isAUMLElement(t); } /** * This method can be overridden in derived Panels where the appropriate * namespace for display may not be the same as the namespace of the target. * * @return the namespace */ protected Object getDisplayNamespace() { Object ns = null; Object theTarget = getTarget(); if (Model.getFacade().isAModelElement(theTarget)) { ns = Model.getFacade().getNamespace(theTarget); } return ns; } /* * @see org.argouml.uml.ui.UMLUserInterfaceContainer#getProfile() */ public ProfileConfiguration getProfile() { return ProjectManager.getManager().getCurrentProject() .getProfileConfiguration(); } /* * @see org.argouml.uml.ui.UMLUserInterfaceContainer#getModelElement() */ public final Object getModelElement() { return modelElement; } /* * @see org.argouml.uml.ui.UMLUserInterfaceContainer#formatElement(java.lang.Object) */ public String formatElement(Object element) { return getProfile().getFormatingStrategy().formatElement(element, getDisplayNamespace()); } /* * @see org.argouml.uml.ui.UMLUserInterfaceContainer#formatNamespace(java.lang.Object) */ public String formatNamespace(Object namespace) { return getProfile().getFormatingStrategy().formatElement(namespace, null); } /* * @see org.argouml.uml.ui.UMLUserInterfaceContainer#formatCollection(java.util.Iterator) */ public String formatCollection(Iterator iter) { Object namespace = getDisplayNamespace(); return getProfile().getFormatingStrategy().formatCollection(iter, namespace); } /** * Get the delete action. * * @return the delete action */ protected final Action getDeleteAction() { return ActionDeleteModelElements.getTargetFollower(); } /** * Check whether this element can be deleted. Currently it only checks * whether we delete the main model. ArgoUML does not like that. * * @since 0.13.2 * @return whether this element can be deleted */ public boolean isRemovableElement() { return ((getTarget() != null) && (getTarget() != (ProjectManager .getManager().getCurrentProject().getModel()))); } /* * @see TargetListener#targetAdded(TargetEvent) */ public void targetAdded(TargetEvent e) { if (listenerList == null) { listenerList = collectTargetListeners(this); } setTarget(e.getNewTarget()); if (isVisible()) { fireTargetAdded(e); } } /* * @see TargetListener#targetRemoved(TargetEvent) */ public void targetRemoved(TargetEvent e) { setTarget(e.getNewTarget()); if (isVisible()) { fireTargetRemoved(e); } } /* * @see TargetListener#targetSet(TargetEvent) */ public void targetSet(TargetEvent e) { setTarget(e.getNewTarget()); if (isVisible()) { fireTargetSet(e); } } private void fireTargetSet(TargetEvent targetEvent) { if (listenerList == null) { listenerList = collectTargetListeners(this); } // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TargetListener.class) { ((TargetListener) listeners[i + 1]).targetSet(targetEvent); } } } private void fireTargetAdded(TargetEvent targetEvent) { if (listenerList == null) { listenerList = collectTargetListeners(this); } // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TargetListener.class) { ((TargetListener) listeners[i + 1]).targetAdded(targetEvent); } } } private void fireTargetRemoved(TargetEvent targetEvent) { if (listenerList == null) { listenerList = collectTargetListeners(this); } // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TargetListener.class) { ((TargetListener) listeners[i + 1]).targetRemoved(targetEvent); } } } /** * @param theTitleLabel * the title of the panel shown at the top */ protected void setTitleLabel(JLabel theTitleLabel) { titleLabel = theTitleLabel; titleLabel.setFont(stdFont); } /** * @return the title of the panel shown at the top */ protected JLabel getTitleLabel() { return titleLabel; } protected final JPanel createBorderPanel(String title) { return new GroupPanel(Translator.localize(title)); } private class GroupPanel extends JPanel { public GroupPanel(String title) { super(new GridLayout2()); TitledBorder border = new TitledBorder(Translator.localize(title)); border.setTitleFont(stdFont); setBorder(border); } public void setEnabled(boolean enabled) { super.setEnabled(enabled); for (final Component component : getComponents()) { component.setEnabled(enabled); } } } /** * If there are no buttons to show in the toolbar, * then set the height to e.g. 18, so that the title * is aligned right by the LabelledLayout. * * @param height the height */ protected void setButtonPanelSize(int height) { /* Set the minimum and preferred equal, * so that the size is fixed for the labelledlayout. */ buttonPanel.setMinimumSize(new Dimension(0, height)); buttonPanel.setPreferredSize(new Dimension(0, height)); } /** * Look up an icon. * * @param name * the resource name. * @return an ImageIcon corresponding to the given resource name */ protected static ImageIcon lookupIcon(String name) { return ResourceLoaderWrapper.lookupIconResource(name); } /* * @see java.awt.event.ComponentListener#componentHidden(java.awt.event.ComponentEvent) */ public void componentHidden(ComponentEvent e) { // TODO: do we want to fire targetRemoved here or is it enough to just // stop updating the targets? } /* * @see java.awt.event.ComponentListener#componentShown(java.awt.event.ComponentEvent) */ public void componentShown(ComponentEvent e) { // Refresh the target for all our children which weren't getting // while not visible fireTargetSet(new TargetEvent( this, TargetEvent.TARGET_SET, null, new Object[] {target})); } /* * @see java.awt.event.ComponentListener#componentMoved(java.awt.event.ComponentEvent) */ public void componentMoved(ComponentEvent e) { // ignored } /* * @see java.awt.event.ComponentListener#componentResized(java.awt.event.ComponentEvent) */ public void componentResized(ComponentEvent e) { // ignored } /** * Create a single row scroll pane backed by a ListModel. * * @param model the ListModel to be used to back the scroll pane * @return a scrollpane with a single row */ protected UMLSingleRowSelector getSingleRowScroll(ListModel model) { UMLSingleRowSelector pane = new UMLSingleRowSelector(model); return pane; } }