Coverage Report - org.argouml.uml.ui.TabStyle
 
Classes in this File Line Coverage Branch Coverage Complexity
TabStyle
10%
18/173
2%
2/78
3.619
 
 1  
 /* $Id: TabStyle.java 17881 2010-01-12 21:09:28Z linus $
 2  
  *****************************************************************************
 3  
  * Copyright (c) 2009 Contributors - see below
 4  
  * All rights reserved. This program and the accompanying materials
 5  
  * are made available under the terms of the Eclipse Public License v1.0
 6  
  * which accompanies this distribution, and is available at
 7  
  * http://www.eclipse.org/legal/epl-v10.html
 8  
  *
 9  
  * Contributors:
 10  
  *    thn
 11  
  *****************************************************************************
 12  
  *
 13  
  * Some portions of this file was previously release using the BSD License:
 14  
  */
 15  
 
 16  
 // Copyright (c) 1996-2008 The Regents of the University of California. All
 17  
 // Rights Reserved. Permission to use, copy, modify, and distribute this
 18  
 // software and its documentation without fee, and without a written
 19  
 // agreement is hereby granted, provided that the above copyright notice
 20  
 // and this paragraph appear in all copies. This software program and
 21  
 // documentation are copyrighted by The Regents of the University of
 22  
 // California. The software program and documentation are supplied "AS
 23  
 // IS", without any accompanying services from The Regents. The Regents
 24  
 // does not warrant that the operation of the program will be
 25  
 // uninterrupted or error-free. The end-user understands that the program
 26  
 // was developed for research purposes and is advised not to rely
 27  
 // exclusively on the program for any reason. IN NO EVENT SHALL THE
 28  
 // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
 29  
 // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
 30  
 // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 31  
 // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
 32  
 // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
 33  
 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 34  
 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
 35  
 // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
 36  
 // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
 37  
 // UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 38  
 
 39  
 package org.argouml.uml.ui;
 40  
 
 41  
 import java.awt.BorderLayout;
 42  
 import java.beans.PropertyChangeEvent;
 43  
 import java.beans.PropertyChangeListener;
 44  
 import java.util.Collection;
 45  
 import java.util.Hashtable;
 46  
 
 47  
 import javax.swing.JPanel;
 48  
 import javax.swing.SwingUtilities;
 49  
 import javax.swing.event.EventListenerList;
 50  
 
 51  
 import org.apache.log4j.Logger;
 52  
 import org.argouml.application.api.AbstractArgoJPanel;
 53  
 import org.argouml.kernel.DelayedChangeNotify;
 54  
 import org.argouml.kernel.DelayedVChangeListener;
 55  
 import org.argouml.kernel.Project;
 56  
 import org.argouml.kernel.ProjectManager;
 57  
 import org.argouml.model.Model;
 58  
 import org.argouml.swingext.UpArrowIcon;
 59  
 import org.argouml.ui.StylePanel;
 60  
 import org.argouml.ui.TabFigTarget;
 61  
 import org.argouml.ui.targetmanager.TargetEvent;
 62  
 import org.argouml.ui.targetmanager.TargetListener;
 63  
 import org.argouml.uml.diagram.ArgoDiagram;
 64  
 import org.argouml.uml.diagram.DiagramUtils;
 65  
 import org.argouml.uml.diagram.ui.FigAssociationClass;
 66  
 import org.argouml.uml.diagram.ui.FigClassAssociationClass;
 67  
 import org.argouml.uml.util.namespace.Namespace;
 68  
 import org.argouml.uml.util.namespace.StringNamespace;
 69  
 import org.argouml.uml.util.namespace.StringNamespaceElement;
 70  
 import org.tigris.gef.presentation.Fig;
 71  
 import org.tigris.gef.presentation.FigEdge;
 72  
 
 73  
 /**
 74  
  * Provides support for changing the appearance of a diagram element. For each
 75  
  * class of a diagram element, the TabStyle class attempts to find an according
 76  
  * class of StylePanel which contains the attributes to be modified in terms of
 77  
  * style.
 78  
  * <p>
 79  
  * The constructor of TabStyle takes an array argument which contains possible
 80  
  * base names for these style panels, or by default StylePanel and SP,
 81  
  * alternating between these two prefixes and the namespace of the Fig class or
 82  
  * <code>org.argouml.ui</code>. With this configuration, the stylepanel for
 83  
  * e.g. <code>org.argouml.uml.diagram.static.structure.ui.FigClass</code>,
 84  
  * will be looked at in the following places:
 85  
  * <ul>
 86  
  * <li>org.argouml.uml.diagram.static_structure.ui.StylePanelFigClass
 87  
  * <li>org.argouml.uml.diagram.static_structure.ui.SPFigClass
 88  
  * <li>org.argouml.ui.StylePanelFigClass
 89  
  * <li>org.argouml.ui.SPFigClass
 90  
  * </ul>
 91  
  * It continues to traverse the superclass structure until a matching class has
 92  
  * been found, e.g.
 93  
  * <ul>
 94  
  * <li>org.argouml.uml.diagram.ui.StylePanelFigNodeModelElement
 95  
  * <li>org.argouml.uml.diagram.ui.SPFigNodeModelElement
 96  
  * <li>org.argouml.ui.StylePanelFigNodeModelElement
 97  
  * <li>org.argouml.ui.SPFigNodeModelElement
 98  
  * </ul>
 99  
  * If a stylepanel had been found, it will be stored in a cache.<p>
 100  
  *
 101  
  * According the decision taken in issue 502, this tab is renamed "Presentation"
 102  
  * for the user. And the Presentation tab shall contain presentation options,
 103  
  * and no semantic UML properties (which belong in the "Properties" panel).
 104  
  * In contrast, the diagram pop-up menu for a model element
 105  
  * may access both presentation options as well as semantic UML properties. <p>
 106  
  *
 107  
  * Note also that the semantic properties of a UML model element exist in one
 108  
  * copy only but the presentation options exist in one copy per diagram
 109  
  * that the model element is showing in. E.g. a class could have
 110  
  * attributes hidden in one diagram and showing in another. So, for the user
 111  
  * it would be very logical to separate these 2 kinds of settings
 112  
  * on different tabs.
 113  
  *
 114  
  */
 115  
 public class TabStyle extends AbstractArgoJPanel implements TabFigTarget,
 116  
         PropertyChangeListener, DelayedVChangeListener {
 117  
 
 118  900
     private static final Logger LOG = Logger.getLogger(TabStyle.class);
 119  
 
 120  
     private Fig target;
 121  
 
 122  900
     private boolean shouldBeEnabled = false;
 123  
 
 124  900
     private JPanel blankPanel = new JPanel();
 125  
 
 126  900
     private Hashtable<Class, TabFigTarget> panels = 
 127  
         new Hashtable<Class, TabFigTarget>();
 128  
 
 129  900
     private JPanel lastPanel = null;
 130  
 
 131  
     /**
 132  
      * The stylepanel shown by the tab style.
 133  
      */
 134  900
     private StylePanel stylePanel = null;
 135  
 
 136  
     private String[] stylePanelNames;
 137  
 
 138  900
     private EventListenerList listenerList = new EventListenerList();
 139  
 
 140  
     /**
 141  
      * The constructor.
 142  
      *
 143  
      * @param tabName the name of the tab
 144  
      * @param spn style panel names
 145  
      */
 146  
     public TabStyle(String tabName, String[] spn) {
 147  900
         super(tabName);
 148  900
         this.stylePanelNames = spn;
 149  900
         setIcon(new UpArrowIcon());
 150  900
         setLayout(new BorderLayout());
 151  900
     }
 152  
 
 153  
     /**
 154  
      * Construct a default stylepanel with basenames <code>StylePanel</code>
 155  
      * and <code>SP</code>, resulting in the lookup order described above.
 156  
      */
 157  
     public TabStyle() {
 158  900
         this("tab.style", new String[] {"StylePanel", "SP"});
 159  900
     }
 160  
 
 161  
     /**
 162  
      * Adds a style panel to the internal list. This allows a plugin to add and
 163  
      * register a new style panel at run-time. This property style will then be
 164  
      * displayed in the details pane whenever an element of the given metaclass
 165  
      * is selected.
 166  
      *
 167  
      * @param c
 168  
      *            the metaclass whose details show be displayed in the property
 169  
      *            panel p
 170  
      * @param s
 171  
      *            an instance of the style panel for the metaclass m
 172  
      */
 173  
     public void addPanel(Class c, StylePanel s) {
 174  0
         panels.put(c, s);
 175  0
     }
 176  
 
 177  
     /**
 178  
      * Sets the target of the style tab.
 179  
      * 
 180  
      * @param t
 181  
      *            is the new target
 182  
      */
 183  
     public void setTarget(Object t) {
 184  0
         if (target != null) {
 185  0
             target.removePropertyChangeListener(this);
 186  0
             if (target instanceof FigEdge) {
 187  
                 // In this case, the bounds are determined by the FigEdge
 188  0
                 ((FigEdge) target).getFig().removePropertyChangeListener(this);
 189  
             }
 190  0
             if (target instanceof FigAssociationClass) {
 191  
                 // In this case, the bounds (of the box) are determined 
 192  
                 // by the FigClassAssociationClass
 193  0
                 FigClassAssociationClass ac = 
 194  
                     ((FigAssociationClass) target).getAssociationClass();
 195  
                 // A newly created AssociationClass may not have all its parts
 196  
                 // created by the time we are called
 197  0
                 if (ac != null) {
 198  0
                     ac.removePropertyChangeListener(this);
 199  
                 }
 200  
             }
 201  
         }
 202  
 
 203  
         // TODO: Defer most of this work if the panel isn't visible - tfm
 204  
 
 205  
         // the responsibility of determining if the given target is a
 206  
         // correct one for this tab has been moved from the
 207  
         // DetailsPane to the member tabs of the details pane. Reason for
 208  
         // this is that the details pane is configurable and cannot
 209  
         // know what's the correct target for some tab.
 210  0
         if (!(t instanceof Fig)) {
 211  0
             if (Model.getFacade().isAModelElement(t)) {
 212  0
                 ArgoDiagram diagram = DiagramUtils.getActiveDiagram();
 213  0
                 if (diagram != null) {
 214  0
                     t = diagram.presentationFor(t);
 215  
                 }
 216  0
                 if (!(t instanceof Fig)) {
 217  0
                     Project p = ProjectManager.getManager().getCurrentProject();
 218  0
                     Collection col = p.findFigsForMember(t);
 219  0
                     if (col == null || col.isEmpty()) {
 220  0
                         return;
 221  
                     }
 222  0
                     t = col.iterator().next();
 223  
                 }
 224  0
                 if (!(t instanceof Fig)) {
 225  0
                     return;
 226  
                 }
 227  0
             } else {
 228  0
                 return;
 229  
             }
 230  
 
 231  
         }
 232  
 
 233  0
         target = (Fig) t;
 234  0
         if (target != null) {
 235  0
             target.addPropertyChangeListener(this);
 236  
             // TODO: This shouldn't know about the specific type of Fig that 
 237  
             // is being displayed.  That couples it too strongly to things it
 238  
             // shouldn't need to know about - tfm - 20070924
 239  0
             if (target instanceof FigEdge) {
 240  
                 // In this case, the bounds are determined by the FigEdge
 241  0
                 ((FigEdge) target).getFig().addPropertyChangeListener(this);
 242  
             }
 243  0
             if (target instanceof FigAssociationClass) {
 244  
                 // In this case, the bounds (of the box) are determined 
 245  
                 // by the FigClassAssociationClass
 246  0
                 FigClassAssociationClass ac = 
 247  
                     ((FigAssociationClass) target).getAssociationClass();
 248  
                 // A newly created AssociationClass may not have all its parts
 249  
                 // created by the time we are called
 250  0
                 if (ac != null) {
 251  0
                     ac.addPropertyChangeListener(this);
 252  
                 }
 253  
             }
 254  
         }
 255  0
         if (lastPanel != null) {
 256  0
             remove(lastPanel);
 257  0
             if (lastPanel instanceof TargetListener) {
 258  0
                 removeTargetListener((TargetListener) lastPanel);
 259  
             }
 260  
         }
 261  0
         if (t == null) {
 262  0
             add(blankPanel, BorderLayout.NORTH);
 263  0
             shouldBeEnabled = false;
 264  0
             lastPanel = blankPanel;
 265  0
             return;
 266  
         }
 267  0
         shouldBeEnabled = true;
 268  0
         stylePanel = null;
 269  0
         Class targetClass = t.getClass();
 270  
 
 271  0
         stylePanel = findPanelFor(targetClass);
 272  
 
 273  0
         if (stylePanel != null) {
 274  0
             removeTargetListener(stylePanel);
 275  0
             addTargetListener(stylePanel);
 276  0
             stylePanel.setTarget(target);
 277  0
             add(stylePanel, BorderLayout.NORTH);
 278  0
             shouldBeEnabled = true;
 279  0
             lastPanel = stylePanel;
 280  
         } else {
 281  0
             add(blankPanel, BorderLayout.NORTH);
 282  0
             shouldBeEnabled = false;
 283  0
             lastPanel = blankPanel;
 284  
         }
 285  0
         validate();
 286  0
         repaint();
 287  0
     }
 288  
 
 289  
     /*
 290  
      * @see org.argouml.ui.TabTarget#refresh()
 291  
      */
 292  
     public void refresh() {
 293  0
         setTarget(target);
 294  0
     }
 295  
 
 296  
     /**
 297  
      * Find the stylepanel for a given target class.
 298  
      *
 299  
      * @param targetClass
 300  
      *            the target class
 301  
      * @return a Stylepanel object or <code>null</code> on error
 302  
      */
 303  
     public StylePanel findPanelFor(Class targetClass) {
 304  0
         Class panelClass = null;
 305  0
         TabFigTarget p = panels.get(targetClass);
 306  0
         if (p == null) {
 307  0
             Class newClass = targetClass;
 308  0
             while (newClass != null && panelClass == null) {
 309  0
                 panelClass = panelClassFor(newClass);
 310  0
                 newClass = newClass.getSuperclass();
 311  
             }
 312  0
             if (panelClass == null) {
 313  0
                 return null;
 314  
             }
 315  
             try {
 316  0
                 p = (TabFigTarget) panelClass.newInstance();
 317  0
             } catch (IllegalAccessException ignore) {
 318  0
                 LOG.error(ignore);
 319  0
                 return null;
 320  0
             } catch (InstantiationException ignore) {
 321  0
                 LOG.error(ignore);
 322  0
                 return null;
 323  0
             }
 324  0
             panels.put(targetClass, p);
 325  
         }
 326  0
         LOG.debug("found style for " + targetClass.getName() + "("
 327  
                 + p.getClass() + ")");
 328  0
         return (StylePanel) p;
 329  
 
 330  
     }
 331  
 
 332  
     /**
 333  
      * Get the class for the required stylepanel.
 334  
      *
 335  
      * @param targetClass the class of the current selected target.
 336  
      * @return the panel class for the class given or
 337  
      * null if none available.
 338  
      */
 339  
     public Class panelClassFor(Class targetClass) {
 340  0
         if (targetClass == null) {
 341  0
             return null;
 342  
         }
 343  
 
 344  0
         StringNamespace classNs = (StringNamespace) StringNamespace
 345  
                 .parse(targetClass);
 346  
 
 347  0
         StringNamespace baseNs = (StringNamespace) StringNamespace.parse(
 348  
                 "org.argouml.ui.", Namespace.JAVA_NS_TOKEN);
 349  
 
 350  0
         StringNamespaceElement targetClassElement =
 351  
                 (StringNamespaceElement) classNs.peekNamespaceElement();
 352  
 
 353  0
         LOG.debug("Attempt to find style panel for: " + classNs);
 354  
 
 355  0
         classNs.popNamespaceElement();
 356  
 
 357  0
         String[] bases = new String[] {
 358  
                 classNs.toString(), baseNs.toString() 
 359  
         };
 360  0
         for (String stylePanelName : stylePanelNames) {
 361  0
             for (String baseName : bases) {
 362  0
                 String name = baseName + "." + stylePanelName
 363  
                         + targetClassElement;
 364  0
                 Class cls = loadClass(name);
 365  0
                 if (cls != null) {
 366  0
                     return cls;
 367  
                 }
 368  
             }
 369  
         }
 370  0
         return null;
 371  
     }
 372  
 
 373  
     private Class loadClass(String name) {
 374  
         try {
 375  0
             Class cls = Class.forName(name);
 376  0
             return cls;
 377  0
         } catch (ClassNotFoundException ignore) {
 378  0
             LOG.debug("ClassNotFoundException. Could not find class:"
 379  
                     + name);
 380  
         }
 381  0
         return null;
 382  
     }
 383  
 
 384  
     /**
 385  
      * @return the style panel names
 386  
      */
 387  
     protected String[] getStylePanelNames() {
 388  0
         return stylePanelNames;
 389  
     }
 390  
 
 391  
     /*
 392  
      * @see org.argouml.ui.TabTarget#getTarget()
 393  
      */
 394  
     public Object getTarget() {
 395  0
         return target;
 396  
     }
 397  
 
 398  
     /*
 399  
      * @see org.argouml.ui.TabTarget#shouldBeEnabled(java.lang.Object)
 400  
      */
 401  
     public boolean shouldBeEnabled(Object targetItem) {
 402  
 
 403  1327
         if (!(targetItem instanceof Fig)) {
 404  1327
             if (Model.getFacade().isAModelElement(targetItem)) {
 405  0
                 ArgoDiagram diagram = DiagramUtils.getActiveDiagram();
 406  0
                 if (diagram == null) {
 407  0
                     shouldBeEnabled = false;
 408  0
                     return false;
 409  
                 }
 410  
 
 411  0
                 Fig f = diagram.presentationFor(targetItem);
 412  0
                 if (f == null) {
 413  0
                     shouldBeEnabled = false;
 414  0
                     return false;
 415  
                 }
 416  0
                 targetItem = f;
 417  0
             } else {
 418  1327
                 shouldBeEnabled = false;
 419  1327
                 return false;
 420  
             }
 421  
         }
 422  
 
 423  0
         shouldBeEnabled = true;
 424  
 
 425  
         // TODO: It would be better to defer this initialization until the panel
 426  
         // actually needs to be displayed. Perhaps optimistically always return
 427  
         // true and figure out later if we've got something to display - tfm -
 428  
         // 20070110
 429  0
         Class targetClass = targetItem.getClass();
 430  0
         stylePanel = findPanelFor(targetClass);
 431  0
         targetClass = targetClass.getSuperclass();
 432  
 
 433  0
         if (stylePanel == null) {
 434  0
             shouldBeEnabled = false;
 435  
         }
 436  
 
 437  0
         return shouldBeEnabled;
 438  
     }
 439  
 
 440  
     /*
 441  
      * @see org.argouml.ui.targetmanager.TargetListener#targetAdded(org.argouml.ui.targetmanager.TargetEvent)
 442  
      */
 443  
     public void propertyChange(PropertyChangeEvent pce) {
 444  0
         DelayedChangeNotify delayedNotify = new DelayedChangeNotify(this, pce);
 445  0
         SwingUtilities.invokeLater(delayedNotify);
 446  0
     }
 447  
 
 448  
     /*
 449  
      * @see org.argouml.kernel.DelayedVChangeListener#delayedVetoableChange(java.beans.PropertyChangeEvent)
 450  
      */
 451  
     public void delayedVetoableChange(PropertyChangeEvent pce) {
 452  0
         if (stylePanel != null) {
 453  0
             stylePanel.refresh(pce);
 454  
         }
 455  0
     }
 456  
 
 457  
     /*
 458  
      * @see TargetListener#targetAdded(TargetEvent)
 459  
      */
 460  
     public void targetAdded(TargetEvent e) {
 461  0
         setTarget(e.getNewTarget());
 462  0
         fireTargetAdded(e);
 463  0
     }
 464  
 
 465  
     /*
 466  
      * @see TargetListener#targetRemoved(TargetEvent)
 467  
      */
 468  
     public void targetRemoved(TargetEvent e) {
 469  
         // how to handle empty target lists?
 470  
         // probably the TabProps should only show an empty pane in that
 471  
         // case
 472  0
         setTarget(e.getNewTarget());
 473  0
         fireTargetRemoved(e);
 474  0
     }
 475  
 
 476  
     /*
 477  
      * @see TargetListener#targetSet(TargetEvent)
 478  
      */
 479  
     public void targetSet(TargetEvent e) {
 480  0
         setTarget(e.getNewTarget());
 481  0
         fireTargetSet(e);
 482  0
     }
 483  
 
 484  
     /**
 485  
      * Adds a listener.
 486  
      *
 487  
      * @param listener
 488  
      *            the listener to add
 489  
      */
 490  
     private void addTargetListener(TargetListener listener) {
 491  0
         listenerList.add(TargetListener.class, listener);
 492  0
     }
 493  
 
 494  
     /**
 495  
      * Removes a target listener.
 496  
      *
 497  
      * @param listener
 498  
      *            the listener to remove
 499  
      */
 500  
     private void removeTargetListener(TargetListener listener) {
 501  0
         listenerList.remove(TargetListener.class, listener);
 502  0
     }
 503  
 
 504  
     /**
 505  
      * @param targetEvent
 506  
      */
 507  
     private void fireTargetSet(TargetEvent targetEvent) {
 508  
         //          Guaranteed to return a non-null array
 509  0
         Object[] listeners = listenerList.getListenerList();
 510  0
         for (int i = listeners.length - 2; i >= 0; i -= 2) {
 511  0
             if (listeners[i] == TargetListener.class) {
 512  
                 // Lazily create the event:
 513  0
                 ((TargetListener) listeners[i + 1]).targetSet(targetEvent);
 514  
             }
 515  
         }
 516  0
     }
 517  
 
 518  
     /**
 519  
      * @param targetEvent
 520  
      */
 521  
     private void fireTargetAdded(TargetEvent targetEvent) {
 522  
         // Guaranteed to return a non-null array
 523  0
         Object[] listeners = listenerList.getListenerList();
 524  
 
 525  0
         for (int i = listeners.length - 2; i >= 0; i -= 2) {
 526  0
             if (listeners[i] == TargetListener.class) {
 527  
                 // Lazily create the event:
 528  0
                 ((TargetListener) listeners[i + 1]).targetAdded(targetEvent);
 529  
             }
 530  
         }
 531  0
     }
 532  
 
 533  
     /**
 534  
      * @param targetEvent
 535  
      */
 536  
     private void fireTargetRemoved(TargetEvent targetEvent) {
 537  
         // Guaranteed to return a non-null array
 538  0
         Object[] listeners = listenerList.getListenerList();
 539  0
         for (int i = listeners.length - 2; i >= 0; i -= 2) {
 540  0
             if (listeners[i] == TargetListener.class) {
 541  
                 // Lazily create the event:
 542  0
                 ((TargetListener) listeners[i + 1]).targetRemoved(targetEvent);
 543  
             }
 544  
         }
 545  0
     }
 546  
 
 547  
 }
 548