/* $Id: DetailsPane.java 18455 2010-06-23 16:30:10Z 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: * bobtarling ***************************************************************************** * * 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.ui; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.swing.Icon; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; import org.apache.log4j.Logger; import org.argouml.application.api.AbstractArgoJPanel; import org.argouml.i18n.Translator; import org.argouml.model.Model; import org.argouml.swingext.LeftArrowIcon; import org.argouml.swingext.UpArrowIcon; import org.argouml.ui.ProjectBrowser.Position; import org.argouml.ui.targetmanager.TargetEvent; import org.argouml.ui.targetmanager.TargetListener; import org.argouml.ui.targetmanager.TargetManager; import org.argouml.uml.ui.PropPanel; import org.argouml.uml.ui.TabProps; import org.tigris.swidgets.Orientable; import org.tigris.swidgets.Orientation; /** * The lower-right pane of the main ArgoUML window, which shows * the details of a selected model element. <p> * * This panel has several tabs that show details of the selected * ToDoItem, or the selected model element in the Explorer (NavigatorPane), * or the MultiEditorPane. <p> * * There are requests to have the cursor automatically * be set to the primary field. */ public class DetailsPane extends JPanel implements ChangeListener, MouseListener, Orientable, TargetListener { /** * Logger. */ private static final Logger LOG = Logger.getLogger(DetailsPane.class); /** * The top level pane, which is a tabbed pane. */ private JTabbedPane topLevelTabbedPane = new JTabbedPane(); /** * The current target. */ private Object currentTarget; /** * The list of all the tabs, which are JPanels, in the JTabbedPane tabs. */ private List<JPanel> tabPanelList = new ArrayList<JPanel>(); /** * index of the selected tab in the JTabbedPane. */ private int lastNonNullTab = -1; /** * The list with targetlisteners, this are the property panels * managed by TabProps It should only contain one listener at a * time. */ private EventListenerList listenerList = new EventListenerList(); private Orientation orientation; private boolean hasTabs = false; /** * Adds a listener. * @param listener the listener to add */ private void addTargetListener(TargetListener listener) { listenerList.add(TargetListener.class, listener); } /** * Removes a target listener. * @param listener the listener to remove */ private void removeTargetListener(TargetListener listener) { listenerList.remove(TargetListener.class, listener); } /** * Gets all of the tabPanels from the ConfigLoader, then * adds them to the JTabbedPane.<p> * * Sets the target to null.<p> * * Registers listeners.<p> * * @param compassPoint the position for which to build the pane * @param theOrientation is the orientation. */ public DetailsPane(String compassPoint, Orientation theOrientation) { LOG.info("making DetailsPane(" + compassPoint + ")"); orientation = theOrientation; loadTabs(compassPoint, theOrientation); setOrientation(orientation); setLayout(new BorderLayout()); setFont(new Font("Dialog", Font.PLAIN, 10)); add(topLevelTabbedPane, BorderLayout.CENTER); setTarget(null, true); topLevelTabbedPane.addMouseListener(this); topLevelTabbedPane.addChangeListener(this); } /* TODO: Some parts of ArgoUML have preliminary support for multiple * details panels, but we currently only support * the default South (bottom) panel */ private void loadTabs(String direction, Orientation theOrientation) { if (Position.South.toString().equalsIgnoreCase(direction) // Special case for backward compatibility || "detail".equalsIgnoreCase(direction)) { /* The south panel always has tabs - but they are * added (later) elsewhere. */ hasTabs = true; } } boolean hasTabs() { return hasTabs; } /** * Returns the JTabbedPane that contains all details panels. * @return the JTabbedPane. */ JTabbedPane getTabs() { return topLevelTabbedPane; } /** * @param p the panel to be added * @param atEnd true = add the panel at the end, * false = at the beginning * @deprecated by Bob Tarling in 0.29.3 use addTab(JPanel, boolean) */ @Deprecated public void addTab(AbstractArgoJPanel p, boolean atEnd) { Icon icon = p.getIcon(); String title = Translator.localize(p.getTitle()); if (atEnd) { topLevelTabbedPane.addTab(title, icon, p); tabPanelList.add(p); } else { topLevelTabbedPane.insertTab(title, icon, p, null, 0); tabPanelList.add(0, p); } } /** * @param p the panel to be added * @param atEnd true = add the panel at the end, * false = at the beginning */ public void addTab(JPanel p, boolean atEnd) { final Icon icon; final String title; if (p instanceof AbstractArgoJPanel) { icon = ((AbstractArgoJPanel) p).getIcon(); title = Translator.localize(((AbstractArgoJPanel) p).getTitle()); } else { icon = null; title = p.getName(); } if (atEnd) { topLevelTabbedPane.addTab(title, icon, p); tabPanelList.add(p); } else { topLevelTabbedPane.insertTab(title, icon, p, null, 0); tabPanelList.add(0, p); } } /** * @param p the panel to be removed */ public void removeTab(JPanel p) { topLevelTabbedPane.remove(p); tabPanelList.remove(p); } /** * Selects the to do tab, and sets the target of that tab. * * @param item the selected todo item * @return true if todo tab is really selected. * @deprecated for 0.25.5 by tfmorris. Send an event that TabToDoTargets * listen to. */ @Deprecated public boolean setToDoItem(Object item) { enableTabs(item); for (JPanel t : tabPanelList) { if (t instanceof TabToDoTarget) { ((TabToDoTarget) t).setTarget(item); topLevelTabbedPane.setSelectedComponent(t); return true; } } return false; } /** * Selects the prop tab if it is appropriate for the target. * * @param target * the target object * @return true if props tab is really selected */ private boolean selectPropsTab(Object target) { if (getTabProps().shouldBeEnabled(target)) { int indexOfPropPanel = topLevelTabbedPane .indexOfComponent(getTabProps()); topLevelTabbedPane.setSelectedIndex(indexOfPropPanel); lastNonNullTab = indexOfPropPanel; return true; } return false; } /** * Sets the target of the Details pane to either be a * selected model element or * the owner(model element) of a selected fig.<p> * * Decides which panels to enable. * * @param target the target object */ private void setTarget(Object target, boolean defaultToProperties) { enableTabs(target); if (target != null) { boolean tabSelected = false; // Always select properties panel if defaultToProperties is true, // and if properties panel is appropriate for selected perspective if (defaultToProperties || lastNonNullTab < 0) { tabSelected = selectPropsTab(target); } else { // Select prop panel if current panel is not appropriate // for selected target Component selectedTab = topLevelTabbedPane .getComponentAt(lastNonNullTab); if (selectedTab instanceof TabTarget) { if (((TabTarget) selectedTab).shouldBeEnabled(target)) { topLevelTabbedPane.setSelectedIndex(lastNonNullTab); tabSelected = true; } else { tabSelected = selectPropsTab(target); } } } if (!tabSelected) { for (int i = lastNonNullTab + 1; i < topLevelTabbedPane.getTabCount(); i++) { Component tab = topLevelTabbedPane.getComponentAt(i); if (tab instanceof TabTarget) { if (((TabTarget) tab).shouldBeEnabled(target)) { topLevelTabbedPane.setSelectedIndex(i); ((TabTarget) tab).setTarget(target); lastNonNullTab = i; tabSelected = true; break; } } } } // default tab todo if (!tabSelected) { JPanel tab = tabPanelList.get(0); if (!(tab instanceof TabToDoTarget)) { for (JPanel panel : tabPanelList) { if (panel instanceof TabToDoTarget) { tab = panel; break; } } } if (tab instanceof TabToDoTarget) { topLevelTabbedPane.setSelectedComponent(tab); ((TabToDoTarget) tab).setTarget(target); lastNonNullTab = topLevelTabbedPane.getSelectedIndex(); } } } else { // default tab todo JPanel tab = tabPanelList.isEmpty() ? null : (JPanel) tabPanelList.get(0); if (!(tab instanceof TabToDoTarget)) { Iterator it = tabPanelList.iterator(); while (it.hasNext()) { Object o = it.next(); if (o instanceof TabToDoTarget) { tab = (JPanel) o; break; } } } if (tab instanceof TabToDoTarget) { topLevelTabbedPane.setSelectedComponent(tab); ((TabToDoTarget) tab).setTarget(target); } else { topLevelTabbedPane.setSelectedIndex(-1); } } currentTarget = target; } /** * Returns the current model target. * @return the current model target */ public Object getTarget() { return currentTarget; } /* * @see java.awt.Component#getMinimumSize() */ @Override public Dimension getMinimumSize() { return new Dimension(100, 100); } /** * Get the index of the tab with the given name. * * @param tabName the name of the required tab * @return index of the tab of the given name */ public int getIndexOfNamedTab(String tabName) { for (int i = 0; i < tabPanelList.size(); i++) { String title = topLevelTabbedPane.getTitleAt(i); if (title != null && title.equals(tabName)) { return i; } } return -1; } /** * Get the number of tabs. * * @return the number of tab pages */ public int getTabCount() { return tabPanelList.size(); } /** * Selects a tab by given name. * @param tabName the given name * @return true if the named tab has been found */ public boolean selectTabNamed(String tabName) { int index = getIndexOfNamedTab(tabName); if (index != -1) { topLevelTabbedPane.setSelectedIndex(index); return true; } return false; } /** * Helper method to add a Property panel for a given class. * * @param c the given class * @param p the given property panel */ public void addToPropTab(Class c, PropPanel p) { for (JPanel panel : tabPanelList) { if (panel instanceof TabProps) { ((TabProps) panel).addPanel(c, p); } } } /** * returns the Property panel in the Details Pane. * * @return the property panel */ public TabProps getTabProps() { for (JPanel tab : tabPanelList) { if (tab instanceof TabProps) { return (TabProps) tab; } } return null; } /** * Returns the tab instance of the specified class. * * @param tabClass the given class * @return the tab instance for the given class */ public AbstractArgoJPanel getTab( Class< ? extends AbstractArgoJPanel> tabClass) { for (JPanel tab : tabPanelList) { if (tab.getClass().equals(tabClass)) { return (AbstractArgoJPanel) tab; } } return null; } /** * Reacts to a change in the selected tab by calling * refresh() for TabToDoTarget's * & * setTarget on a TabModelTarget or TabFigTarget instance. <p> * * Called when the user selects a new tab, by clicking or * otherwise. * * {@inheritDoc} */ public void stateChanged(ChangeEvent e) { LOG.debug("DetailsPane state changed"); Component sel = topLevelTabbedPane.getSelectedComponent(); // update the previously selected tab if (lastNonNullTab >= 0) { JPanel tab = tabPanelList.get(lastNonNullTab); if (tab instanceof TargetListener) { // not visible any more - so remove as listener removeTargetListener((TargetListener) tab); } } Object target = TargetManager.getInstance().getSingleTarget(); // If sel is the ToDo Tab (i.e. is an instance of TabToDoTarget), we // don't need to do anything, because the ToDo Tab is already dealt // with by it's own listener. TabProps is also deals with itself. // TODO: Do we really need this for anything? if (!(sel instanceof TabToDoTarget) && !(sel instanceof TabProps)) { // The other tabs need to be updated depending on the selection. if (sel instanceof TabTarget) { ((TabTarget) sel).setTarget(target); } else if (sel instanceof TargetListener) { removeTargetListener((TargetListener) sel); addTargetListener((TargetListener) sel); // Newly selected tab may have stale target info, so generate // a new set target event for it to refresh it ((TargetListener) sel).targetSet(new TargetEvent(this, TargetEvent.TARGET_SET, new Object[] {}, new Object[] { target })); } } if (target != null && Model.getFacade().isAUMLElement(target) && topLevelTabbedPane.getSelectedIndex() > 0) { lastNonNullTab = topLevelTabbedPane.getSelectedIndex(); } } /** * no action currently executed here. * called when the user clicks once on a tab. * * @param tab the index of the clicked tab */ public void mySingleClick(int tab) { //TODO: should fire its own event and ProjectBrowser //should register a listener LOG.debug("single: " + topLevelTabbedPane.getComponentAt(tab).toString()); } /** * Spawns a new tab. * called when the user clicks twice on a tab. * * @param tab the index of the clicked tab */ public void myDoubleClick(int tab) { //TODO: should fire its own event and ProjectBrowser //should register a listener LOG.debug("double: " + topLevelTabbedPane.getComponentAt(tab).toString()); // JPanel t = (JPanel) tabPanelList.elementAt(tab); // Currently this feature is disabled for ArgoUML. // if (t instanceof AbstractArgoJPanel) // ((AbstractArgoJPanel) t).spawn(); } /* * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent) */ public void mousePressed(MouseEvent me) { // empty implementation - we only handle mouseClicked } /* * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent) */ public void mouseReleased(MouseEvent me) { // empty implementation - we only handle mouseClicked } /* * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent) */ public void mouseEntered(MouseEvent me) { // empty implementation - we only handle mouseClicked } /* * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent) */ public void mouseExited(MouseEvent me) { // empty implementation - we only handle mouseClicked } /* * if(the mouse click is not in the bounds of the tabbed panel) * then call mySingleClick() or myDoubleClick(). * * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent) */ public void mouseClicked(MouseEvent me) { int tab = topLevelTabbedPane.getSelectedIndex(); if (tab != -1) { Rectangle tabBounds = topLevelTabbedPane.getBoundsAt(tab); if (!tabBounds.contains(me.getX(), me.getY())) { return; } if (me.getClickCount() == 1) { mySingleClick(tab); } else if (me.getClickCount() >= 2) { myDoubleClick(tab); } } } /** * Graphic that goes on the tab label. */ private Icon upArrowIcon = new UpArrowIcon(); /** * Graphic that goes on the tab label. */ private Icon leftArrowIcon = new LeftArrowIcon(); /* * @see org.tigris.swidgets.Orientable#setOrientation(org.tigris.swidgets.Orientation) */ public void setOrientation(Orientation newOrientation) { for (JPanel t : tabPanelList) { if (t instanceof Orientable) { Orientable o = (Orientable) t; o.setOrientation(newOrientation); } } } /* * @see TargetListener#targetAdded(TargetEvent) */ public void targetAdded(TargetEvent e) { setTarget(e.getNewTarget(), false); fireTargetAdded(e); } /* * @see TargetListener#targetRemoved(TargetEvent) */ public void targetRemoved(TargetEvent e) { setTarget(e.getNewTarget(), false); fireTargetRemoved(e); } /* * @see TargetListener#targetSet(TargetEvent) */ public void targetSet(TargetEvent e) { setTarget(e.getNewTarget(), false); fireTargetSet(e); } /** * Enables/disables the tabs on the tabbed card. Also selects the tab to * show. * * @param target the target object */ private void enableTabs(Object target) { // TODO: Quick return here for target == null? - tfm // iterate through the tabbed panels to determine whether they // should be enabled. for (int i = 0; i < tabPanelList.size(); i++) { JPanel tab = tabPanelList.get(i); boolean shouldEnable = false; if (tab instanceof TargetListener) { if (tab instanceof TabTarget) { shouldEnable = ((TabTarget) tab).shouldBeEnabled(target); } else { if (tab instanceof TabToDoTarget) { shouldEnable = true; } } // TODO: Do we want all enabled tabs to listen or only the one // that is selected/visible? - tfm removeTargetListener((TargetListener) tab); if (shouldEnable) { addTargetListener((TargetListener) tab); } topLevelTabbedPane.setEnabledAt(i, shouldEnable); } } } private void fireTargetSet(TargetEvent targetEvent) { // 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) { // Lazily create the event: ((TargetListener) listeners[i + 1]).targetSet(targetEvent); } } } private void fireTargetAdded(TargetEvent targetEvent) { // 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) { // Lazily create the event: ((TargetListener) listeners[i + 1]).targetAdded(targetEvent); } } } private void fireTargetRemoved(TargetEvent targetEvent) { // 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) { // Lazily create the event: ((TargetListener) listeners[i + 1]).targetRemoved(targetEvent); } } } }