Coverage Report - org.argouml.uml.ui.UMLComboBoxModel2
 
Classes in this File Line Coverage Branch Coverage Complexity
UMLComboBoxModel2
46%
122/261
35%
71/198
3.444
UMLComboBoxModel2$1
0%
0/11
0%
0/2
3.444
 
 1  
 /* $Id: UMLComboBoxModel2.java 18650 2010-08-17 07:55:17Z bobtarling $
 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  
  *    tfmorris
 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.beans.PropertyChangeEvent;
 42  
 import java.beans.PropertyChangeListener;
 43  
 import java.util.ArrayList;
 44  
 import java.util.Collection;
 45  
 import java.util.LinkedList;
 46  
 import java.util.List;
 47  
 
 48  
 import javax.swing.AbstractListModel;
 49  
 import javax.swing.ComboBoxModel;
 50  
 import javax.swing.JComboBox;
 51  
 import javax.swing.SwingUtilities;
 52  
 import javax.swing.event.PopupMenuEvent;
 53  
 import javax.swing.event.PopupMenuListener;
 54  
 
 55  
 import org.apache.log4j.Logger;
 56  
 import org.argouml.model.AddAssociationEvent;
 57  
 import org.argouml.model.AssociationChangeEvent;
 58  
 import org.argouml.model.AttributeChangeEvent;
 59  
 import org.argouml.model.DeleteInstanceEvent;
 60  
 import org.argouml.model.InvalidElementException;
 61  
 import org.argouml.model.Model;
 62  
 import org.argouml.model.RemoveAssociationEvent;
 63  
 import org.argouml.model.UmlChangeEvent;
 64  
 import org.argouml.ui.targetmanager.TargetEvent;
 65  
 import org.argouml.ui.targetmanager.TargetListener;
 66  
 import org.argouml.uml.diagram.ArgoDiagram;
 67  
 import org.tigris.gef.presentation.Fig;
 68  
 
 69  
 /**
 70  
  * ComboBox Model for UML modelelements. <p>
 71  
  *
 72  
  * This combobox allows selecting no value, if so indicated
 73  
  * at construction time of this class. I.e. it is "clearable".
 74  
  * @deprecated by Bob Tarling in 0.31.4 - the property panel module is now
 75  
  * responsible for property panel controls and models
 76  
  */
 77  900
 @Deprecated
 78  0
 public abstract class UMLComboBoxModel2 extends AbstractListModel
 79  
         implements PropertyChangeListener, 
 80  
         ComboBoxModel, TargetListener, PopupMenuListener {
 81  
 
 82  900
     private static final Logger LOG = Logger.getLogger(UMLComboBoxModel2.class);
 83  
 
 84  
     /**
 85  
      * The string that represents a null or cleared choice.
 86  
      */
 87  
     // TODO: I18N
 88  
     // Don't use the empty string for this or it won't show in the list
 89  
     protected static final String CLEARED = "<none>";
 90  
     
 91  
     /**
 92  
      * The target of the comboboxmodel. This is some UML modelelement
 93  
      */
 94  3189
     private Object comboBoxTarget = null;
 95  
 
 96  
     /**
 97  
      * The list with objects that should be shown in the combobox.
 98  
      * TODO: Using a list here forces a linear search when we're trying to add
 99  
      * a new element to the model which can be very slow for large models.
 100  
      */
 101  3189
     private List objects = new LinkedList();
 102  
 
 103  
     /**
 104  
      * The selected object.
 105  
      */
 106  3189
     private Object selectedObject = null;
 107  
 
 108  
     /**
 109  
      * Flag to indicate if the user may select the special CLEARED choice
 110  
      * ("<none>") as value in the combobox. If true the attribute that is shown
 111  
      * by this combobox may be set to null. Makes sure that there is always an
 112  
      * entry in the list with objects so the user has the opportunity to select
 113  
      * this to clear the attribute.
 114  
      */
 115  3189
     private boolean isClearable = false;
 116  
 
 117  
     /**
 118  
      * The name of the property that we will use to listen for change events
 119  
      * associated with this model element.
 120  
      */
 121  
     private String propertySetName;
 122  
 
 123  
     /**
 124  
      * Flag to indicate whether list events should be fired.
 125  
      */
 126  3189
     private boolean fireListEvents = true;
 127  
 
 128  
     /**
 129  
      * Flag to indicate whether the model is being build.
 130  
      */
 131  3189
     protected boolean buildingModel = false;
 132  
     
 133  
     /**
 134  
      * Flag needed to prevent infinite recursion during processing of
 135  
      * popup visibility notification event.
 136  
      */
 137  3189
     private boolean processingWillBecomeVisible = false;
 138  
 
 139  
     private boolean modelValid;
 140  
 
 141  
 
 142  
     /**
 143  
      * Constructs a model for a combobox. The container given is used to
 144  
      * retrieve the target that is manipulated through this combobox. If
 145  
      * clearable is true, the user can select null in the combobox and thereby
 146  
      * clear the attribute in the model.
 147  
      * 
 148  
      * @param name The name of the property change event that must be fired to
 149  
      *            set the selected item programmatically (via changing the
 150  
      *            model)
 151  
      * @param clearable Flag to indicate if the user may select the special
 152  
      *            CLEARED value (<none>) as value in the combobox. If true the
 153  
      *            attribute that is shown by this combobox may be set to null.
 154  
      *            Makes sure that there is always an entry for this in the list
 155  
      *            with objects so the user has the opportunity to select this to
 156  
      *            clear the attribute.
 157  
      * @throws IllegalArgumentException if one of the arguments is null
 158  
      */
 159  
     public UMLComboBoxModel2(String name, boolean clearable) {
 160  3189
         super();
 161  3189
         if (name == null || name.equals("")) {
 162  0
             throw new IllegalArgumentException("one of the arguments is null");
 163  
         }
 164  
         // It would be better if we didn't need the container to get
 165  
         // the target. This constructor can have zero parameters as
 166  
         // soon as we improve targetChanged.
 167  3189
         isClearable = clearable;
 168  3189
         propertySetName = name;
 169  3189
     }
 170  
 
 171  
     public final void propertyChange(final PropertyChangeEvent pve) {
 172  0
         if (pve instanceof UmlChangeEvent) {
 173  0
             final UmlChangeEvent event = (UmlChangeEvent) pve;
 174  
 
 175  0
             Runnable doWorkRunnable = new Runnable() {
 176  
                 public void run() {
 177  
                     try {
 178  0
                         modelChanged(event);
 179  0
                     } catch (InvalidElementException e) {
 180  0
                         if (LOG.isDebugEnabled()) {
 181  0
                             LOG.debug("event = "
 182  
                                     + event.getClass().getName());
 183  0
                             LOG.debug("source = " + event.getSource());
 184  0
                             LOG.debug("old = " + event.getOldValue());
 185  0
                             LOG.debug("name = " + event.getPropertyName());
 186  0
                             LOG.debug("updateLayout method accessed "
 187  
                                     + "deleted element ", e);
 188  
                         }
 189  0
                     }
 190  0
                 }  
 191  
             };
 192  0
             SwingUtilities.invokeLater(doWorkRunnable);
 193  
         }
 194  0
     }
 195  
     
 196  
     
 197  
     
 198  
     /**
 199  
      * If the property that this comboboxmodel depicts is changed in the UML
 200  
      * model, this method will make sure that the changes will be 
 201  
      * done in the combobox-model equally. <p>
 202  
      * TODO: This function is not yet completely written!
 203  
      * 
 204  
      * {@inheritDoc}
 205  
      * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
 206  
      */
 207  
     public void modelChanged(UmlChangeEvent evt) {
 208  0
         buildingModel = true;
 209  0
         if (evt instanceof AttributeChangeEvent) {
 210  0
             if (evt.getPropertyName().equals(propertySetName)) {
 211  0
                 if (evt.getSource() == getTarget()
 212  
                         && (isClearable || getChangedElement(evt) != null)) {
 213  0
                     Object elem = getChangedElement(evt);
 214  0
                     if (elem != null && !contains(elem)) {
 215  0
                         addElement(elem);
 216  
                     }
 217  
                     /* MVW: for this case, I had to move the 
 218  
                      * call to setSelectedItem() outside the "buildingModel", 
 219  
                      * otherwise the combo does not update 
 220  
                      * with the new selection. See issue 5418. 
 221  
                      **/
 222  0
                     buildingModel = false;
 223  0
                     setSelectedItem(elem);
 224  0
                 }
 225  
             }
 226  0
         } else if (evt instanceof DeleteInstanceEvent) {
 227  0
             if (contains(getChangedElement(evt))) {
 228  0
                 Object o = getChangedElement(evt);
 229  0
                 removeElement(o);
 230  0
             }
 231  0
         } else if (evt instanceof AddAssociationEvent) {
 232  0
             if (getTarget() != null && isValidEvent(evt)) {
 233  0
                 if (evt.getPropertyName().equals(propertySetName) 
 234  
                     && (evt.getSource() == getTarget())) {
 235  0
                     Object elem = evt.getNewValue();
 236  
                     /* TODO: Here too? */
 237  0
                     setSelectedItem(elem);
 238  0
                 } else {
 239  0
                     Object o = getChangedElement(evt);
 240  0
                     addElement(o);
 241  0
                 }
 242  
             }
 243  0
         } else if (evt instanceof RemoveAssociationEvent && isValidEvent(evt)) {
 244  0
             if (evt.getPropertyName().equals(propertySetName) 
 245  
                     && (evt.getSource() == getTarget())) {
 246  0
                 if (evt.getOldValue() == internal2external(getSelectedItem())) {
 247  
                     /* TODO: Here too? */
 248  0
                     setSelectedItem(external2internal(evt.getNewValue()));
 249  
                 }
 250  
             } else {
 251  0
                 Object o = getChangedElement(evt);
 252  0
                 if (contains(o)) {
 253  0
                     removeElement(o);
 254  
                 }
 255  0
             }
 256  
         }
 257  0
         else if (evt.getSource() instanceof ArgoDiagram
 258  
                 && evt.getPropertyName().equals(propertySetName)) {
 259  
             /* This should not be necessary, but let's be sure: */
 260  0
             addElement(evt.getNewValue());
 261  
             /* MVW: for this case, I have to move the 
 262  
              * call to setSelectedItem() outside the "buildingModel", otherwise
 263  
              * the combo does not update with the new selection. 
 264  
              * The same does probably apply to the cases above! */
 265  0
             buildingModel = false;
 266  0
             setSelectedItem(evt.getNewValue());
 267  
         }
 268  0
         buildingModel = false;
 269  0
     }
 270  
 
 271  
     /**
 272  
      * Returns true if the given element is valid.<p>
 273  
      *
 274  
      * It is valid if it may be added to the list of elements.
 275  
      *
 276  
      * @param element the given element
 277  
      * @return true if the given element is valid
 278  
      */
 279  
     protected abstract boolean isValidElement(Object element);
 280  
 
 281  
     /**
 282  
      * Builds the list of elements and sets the selectedIndex to the currently
 283  
      * selected item if there is one. Called from targetChanged every time the
 284  
      * target of the proppanel is changed.
 285  
      */
 286  
     protected abstract void buildModelList();
 287  
 
 288  
     /**
 289  
      * @param obj an UML object
 290  
      * @return its name or "" (if it was not named or deleted)
 291  
      */
 292  
     protected String getName(Object obj) {
 293  
         try {
 294  0
             Object n = Model.getFacade().getName(obj);
 295  0
             String name = (n != null ? (String) n : "");
 296  0
             return name;
 297  0
         } catch (InvalidElementException e) {
 298  0
             return "";
 299  
         }
 300  
     }
 301  
 
 302  
     /**
 303  
      * Utility method to change all elements in the list with modelelements
 304  
      * at once.  A minimal update strategy is used to minimize event firing
 305  
      * for unchanged elements.
 306  
      *
 307  
      * @param elements the given elements
 308  
      */
 309  
     protected void setElements(Collection elements) {
 310  2949
         if (elements != null) {
 311  2949
             ArrayList toBeRemoved = new ArrayList();
 312  2949
             for (Object o : objects) {
 313  660
                 if (!elements.contains(o)
 314  
                         && !(isClearable 
 315  
                                 // Check against "" is needed for backward 
 316  
                                 // compatibility.  Don't remove without 
 317  
                                 // checking subclasses and warning downstream
 318  
                                 // developers - tfm - 20081211
 319  
                                 && ("".equals(o) || CLEARED.equals(o)))) {
 320  372
                     toBeRemoved.add(o);
 321  
                 }
 322  
             }
 323  2949
             removeAll(toBeRemoved);
 324  2949
             addAll(elements);
 325  
             
 326  2949
             if (isClearable && !elements.contains(CLEARED)) {
 327  0
                 addElement(CLEARED);
 328  
             }
 329  2949
             if (!objects.contains(selectedObject)) {
 330  2661
                 selectedObject = null;
 331  
             }
 332  2949
         } else {
 333  0
             throw new IllegalArgumentException("In setElements: may not set "
 334  
                                                + "elements to null collection");
 335  
         }
 336  2949
     }
 337  
 
 338  
     /**
 339  
      * Utility method to get the target.
 340  
      *
 341  
      * @return  the ModelElement
 342  
      */
 343  
     protected Object getTarget() {
 344  5898
         return comboBoxTarget;
 345  
     }
 346  
 
 347  
     /**
 348  
      * Utility method to remove a collection of elements from the model.
 349  
      *
 350  
      * @param col the elements to be removed
 351  
      */
 352  
     protected void removeAll(Collection col) {
 353  2949
         int first = -1;
 354  2949
         int last = -1;
 355  2949
         fireListEvents = false;
 356  2949
         for (Object o : col) {
 357  372
             int index = getIndexOf(o);
 358  372
             removeElement(o);
 359  372
             if (first == -1) { // start of interval
 360  372
                 first = index;
 361  372
                 last = index;
 362  
             } else {
 363  0
                 if (index  != last + 1) { // end of interval
 364  0
                     fireListEvents = true;
 365  0
                     fireIntervalRemoved(this, first, last);
 366  0
                     fireListEvents = false;
 367  0
                     first = index;
 368  0
                     last = index;
 369  
                 } else { // in middle of interval
 370  0
                     last++;
 371  
                 }
 372  
             }
 373  372
         }
 374  2949
         fireListEvents = true;
 375  2949
     }
 376  
 
 377  
     /**
 378  
      * Utility method to add a collection of elements to the model.
 379  
      *
 380  
      * @param col the elements to be addd
 381  
      */
 382  
     protected void addAll(Collection col) {
 383  2949
         Object selected = getSelectedItem();
 384  2949
         fireListEvents = false;
 385  2949
         int oldSize = objects.size();
 386  2949
         for (Object o : col) {
 387  2949
             addElement(o);
 388  
         }
 389  2949
         setSelectedItem(external2internal(selected));
 390  2949
         fireListEvents = true;
 391  2949
         if (objects.size() != oldSize) {
 392  2661
             fireIntervalAdded(this, oldSize == 0 ? 0 : oldSize - 1, 
 393  
                     objects.size() - 1);
 394  
         }
 395  2949
     }
 396  
 
 397  
     /**
 398  
      * Utility method to get the changed element from some event e.
 399  
      *
 400  
      * @param e the given event
 401  
      * @return Object the changed element
 402  
      */
 403  
     protected Object getChangedElement(PropertyChangeEvent e) {
 404  0
         if (e instanceof AssociationChangeEvent) {
 405  0
             return ((AssociationChangeEvent) e).getChangedValue();
 406  
         }
 407  0
         return e.getNewValue();
 408  
     }
 409  
 
 410  
     /**
 411  
      * Sets the target. If the old target is a ModelElement, it also removes
 412  
      * the model from the element listener list of the target. If the new target
 413  
      * is a ModelElement, the model is added as element listener to the new
 414  
      * target. <p>
 415  
      * 
 416  
      * This function is called when the user changes the target. 
 417  
      * Hence, this shall not result in any UML model changes.
 418  
      * Hence, we block firing list events completely by setting 
 419  
      * buildingModel to true for the duration of this function. <p>
 420  
      * 
 421  
      * This function looks a lot like the one in UMLModelElementListModel2.
 422  
      * 
 423  
      * @param theNewTarget the target
 424  
      */
 425  
     public void setTarget(Object theNewTarget) {
 426  5623
         if (theNewTarget != null && theNewTarget.equals(comboBoxTarget)) {
 427  1515
             LOG.debug("Ignoring duplicate setTarget request " + theNewTarget);
 428  1515
             return;
 429  
         }
 430  4108
         modelValid = false;
 431  4108
         LOG.debug("setTarget target :  " + theNewTarget);
 432  4108
         theNewTarget = theNewTarget instanceof Fig 
 433  
             ? ((Fig) theNewTarget).getOwner() : theNewTarget;
 434  4108
         if (Model.getFacade().isAModelElement(theNewTarget) 
 435  
                 || theNewTarget instanceof ArgoDiagram) {
 436  
             
 437  
             /* Remove old listeners: */
 438  2949
             if (Model.getFacade().isAModelElement(comboBoxTarget)) {
 439  0
                 Model.getPump().removeModelEventListener(this, comboBoxTarget,
 440  
                         propertySetName);
 441  
                 // Allow listening to other elements:
 442  0
                 removeOtherModelEventListeners(comboBoxTarget);
 443  2949
             } else if (comboBoxTarget instanceof ArgoDiagram) {
 444  510
                 ((ArgoDiagram) comboBoxTarget).removePropertyChangeListener(
 445  
                         ArgoDiagram.NAMESPACE_KEY, this);
 446  
             }
 447  
 
 448  
             /* Add new listeners: */
 449  2949
             if (Model.getFacade().isAModelElement(theNewTarget)) {
 450  0
                 comboBoxTarget = theNewTarget;
 451  0
                 Model.getPump().addModelEventListener(this, comboBoxTarget,
 452  
                         propertySetName);
 453  
                 // Allow listening to other elements:
 454  0
                 addOtherModelEventListeners(comboBoxTarget);
 455  
                 
 456  0
                 buildingModel = true;
 457  0
                 buildMinimalModelList();
 458  
                 // Do not set buildingModel = false here, 
 459  
                 // otherwise the action for selection is performed.
 460  0
                 setSelectedItem(external2internal(getSelectedModelElement()));
 461  0
                 buildingModel = false;
 462  
 
 463  0
                 if (getSize() > 0) {
 464  0
                     fireIntervalAdded(this, 0, getSize() - 1);
 465  
                 }
 466  2949
             } else if (theNewTarget instanceof ArgoDiagram) {
 467  2949
                 comboBoxTarget = theNewTarget;
 468  2949
                 ArgoDiagram diagram = (ArgoDiagram) theNewTarget;
 469  2949
                 diagram.addPropertyChangeListener(
 470  
                         ArgoDiagram.NAMESPACE_KEY, this);
 471  2949
                 buildingModel = true;
 472  2949
                 buildMinimalModelList();
 473  2949
                 setSelectedItem(external2internal(getSelectedModelElement()));
 474  2949
                 buildingModel = false;
 475  2949
                 if (getSize() > 0) {
 476  2949
                     fireIntervalAdded(this, 0, getSize() - 1);
 477  
                 }
 478  2949
             } else { /*  MVW: This can never happen, isn't it? */
 479  0
                 comboBoxTarget = null;
 480  0
                 removeAllElements();
 481  
             }
 482  2949
             if (getSelectedItem() != null && isClearable) {
 483  0
                 addElement(CLEARED); // makes sure we can select 'none'
 484  
             }
 485  
         }
 486  4108
     }
 487  
     
 488  
     /**
 489  
      * Build the minimal number of items in the model for the edit box
 490  
      * to be populated. By default this calls buildModelList but it
 491  
      * can be overridden in subclasses to delay population of the list
 492  
      * till the list is displayed. <p>
 493  
      * 
 494  
      * If this lazy list building is used, do call setModelInvalid() here!
 495  
      */
 496  
     protected void buildMinimalModelList() {
 497  0
         buildModelListTimed();
 498  0
     }
 499  
 
 500  
     private void buildModelListTimed() {
 501  0
         long startTime = System.currentTimeMillis();
 502  
         try {
 503  0
             buildModelList();
 504  0
             long endTime = System.currentTimeMillis();
 505  0
             LOG.debug("buildModelList took " + (endTime - startTime)
 506  
                     + " msec. for " + this.getClass().getName());
 507  0
         } catch (InvalidElementException e) {
 508  0
             LOG.warn("buildModelList attempted to operate on "
 509  
                     + "deleted element");
 510  0
         }
 511  0
     }
 512  
 
 513  
     /**
 514  
      * This function allows subclasses to listen to more modelelements.
 515  
      * The given target is guaranteed to be a UML modelelement.
 516  
      * 
 517  
      * @param oldTarget the UML modelelement
 518  
      */
 519  
     protected void removeOtherModelEventListeners(Object oldTarget) {
 520  
         /* Do nothing by default. */
 521  0
     }
 522  
 
 523  
     /**
 524  
      * This function allows subclasses to listen to more modelelements.
 525  
      * The given target is guaranteed to be a UML modelelement.
 526  
      * 
 527  
      * @param newTarget the UML modelelement
 528  
      */
 529  
     protected void addOtherModelEventListeners(Object newTarget) {
 530  
         /* Do nothing by default. */
 531  0
     }
 532  
 
 533  
     /**
 534  
      * Gets the modelelement that is selected in the UML model. For
 535  
      * example, say that this ComboBoxmodel contains all namespaces
 536  
      * (as in UMLNamespaceComboBoxmodel) , this method should return
 537  
      * the namespace that owns the target then.
 538  
      *
 539  
      * @return Object
 540  
      */
 541  
     protected abstract Object getSelectedModelElement();
 542  
 
 543  
     /*
 544  
      * @see javax.swing.ListModel#getElementAt(int)
 545  
      */
 546  
     public Object getElementAt(int index) {
 547  7371
         if (index >= 0 && index < objects.size()) {
 548  7371
             return objects.get(index);
 549  
         }
 550  0
         return null;
 551  
     }
 552  
 
 553  
     /*
 554  
      * @see javax.swing.ListModel#getSize()
 555  
      */
 556  
     public int getSize() {
 557  50537
         return objects.size();
 558  
     }
 559  
 
 560  
     /**
 561  
      * @param o the given element
 562  
      * @return the index of the given element
 563  
      */
 564  
     public int getIndexOf(Object o) {
 565  372
         return objects.indexOf(o);
 566  
     }
 567  
 
 568  
     /**
 569  
      * @param o the element to be added
 570  
      */
 571  
     public void addElement(Object o) {
 572  
         // TODO: For large lists, this is doing a linear search of literally thousands of elements
 573  2949
         if (!objects.contains(o)) {
 574  2661
             objects.add(o);
 575  2661
             fireIntervalAdded(this, objects.size() - 1, objects.size() - 1);
 576  
         }
 577  2949
     }
 578  
 
 579  
     /*
 580  
      * @see javax.swing.ComboBoxModel#setSelectedItem(java.lang.Object)
 581  
      */
 582  
     public void setSelectedItem(Object o) {
 583  6270
         if ((selectedObject != null && !selectedObject.equals(o))
 584  
                 || (selectedObject == null && o != null)) {
 585  3033
             selectedObject = o;
 586  3033
             fireContentsChanged(this, -1, -1);
 587  
         }
 588  6270
     }
 589  
 
 590  
     /**
 591  
      * @param o the element to be removed
 592  
      */
 593  
     public void removeElement(Object o) {
 594  372
         int index = objects.indexOf(o);
 595  372
         if (getElementAt(index) == selectedObject) {
 596  372
             if (!isClearable) {
 597  372
                 if (index == 0) {
 598  372
                     setSelectedItem(getSize() == 1
 599  
                                     ? null
 600  
                                     : getElementAt(index + 1));
 601  
                 } else {
 602  0
                     setSelectedItem(getElementAt(index - 1));
 603  
                 }
 604  
             }
 605  
         }
 606  372
         if (index >= 0) {
 607  372
             objects.remove(index);
 608  372
             fireIntervalRemoved(this, index, index);
 609  
         }
 610  372
     }
 611  
 
 612  
     /**
 613  
      * Remove all elements.
 614  
      */
 615  
     public void removeAllElements() {
 616  0
         int startIndex = 0;
 617  0
         int endIndex = Math.max(0, objects.size() - 1);
 618  0
         objects.clear();
 619  0
         selectedObject = null;
 620  0
         fireIntervalRemoved(this, startIndex, endIndex);
 621  0
     }
 622  
 
 623  
     /*
 624  
      * @see javax.swing.ComboBoxModel#getSelectedItem()
 625  
      */
 626  
     public Object getSelectedItem() {
 627  33396
         return selectedObject;
 628  
     }
 629  
     
 630  
     private Object external2internal(Object o) {
 631  5898
         return o == null && isClearable ? CLEARED : o;
 632  
     }
 633  
 
 634  
     private Object internal2external(Object o) {
 635  0
         return isClearable && CLEARED.equals(o) ? null : o;
 636  
     }
 637  
     
 638  
     /**
 639  
      * Returns true if some object elem is contained by the list of choices.
 640  
      *
 641  
      * @param elem the given element
 642  
      * @return boolean true if it is in the selection
 643  
      */
 644  
     public boolean contains(Object elem) {
 645  5238
         if (objects.contains(elem)) {
 646  2949
             return true;
 647  
         }
 648  2289
         if (elem instanceof Collection) {
 649  0
             for (Object o : (Collection) elem) {
 650  0
                 if (!objects.contains(o)) {
 651  0
                     return false;
 652  
                 }
 653  
             }
 654  0
             return true;
 655  
         }
 656  2289
         return false;
 657  
     }
 658  
 
 659  
     /**
 660  
      * Returns true if some event is valid. An event is valid if the
 661  
      * element changed in the event is valid. This is determined via a
 662  
      * call to isValidElement.  This method can be overriden by
 663  
      * subclasses if they cannot determine if it is a valid event just
 664  
      * by checking the changed element.
 665  
      *
 666  
      * @param e the event
 667  
      * @return boolean true if the event is valid
 668  
      */
 669  
     protected boolean isValidEvent(PropertyChangeEvent e) {
 670  0
         boolean valid = false;
 671  0
         if (!(getChangedElement(e) instanceof Collection)) {
 672  0
             if ((e.getNewValue() == null && e.getOldValue() != null)
 673  
                     // Don't try to test this if we're removing the element
 674  
                     || isValidElement(getChangedElement(e))) {
 675  0
                 valid = true; // we tried to remove a value
 676  
             }
 677  
         } else {
 678  0
             Collection col = (Collection) getChangedElement(e);
 679  0
             if (!col.isEmpty()) {
 680  0
                 valid = true;
 681  0
                 for (Object o : col) {
 682  0
                     if (!isValidElement(o)) {
 683  0
                         valid = false;
 684  0
                         break;
 685  
                     }
 686  
                 }
 687  
             } else {
 688  0
                 if (e.getOldValue() instanceof Collection
 689  
                     && !((Collection) e.getOldValue()).isEmpty()) {
 690  0
                     valid = true;
 691  
                 }
 692  
             }
 693  
         }
 694  0
         return valid;
 695  
     }
 696  
 
 697  
     /*
 698  
      * @see javax.swing.AbstractListModel#fireContentsChanged(
 699  
      *          Object, int, int)
 700  
      */
 701  
     @Override
 702  
     protected void fireContentsChanged(Object source, int index0, int index1) {
 703  3033
         if (fireListEvents && !buildingModel) {
 704  0
             super.fireContentsChanged(source, index0, index1);
 705  
         }
 706  3033
     }
 707  
 
 708  
     /*
 709  
      * @see javax.swing.AbstractListModel#fireIntervalAdded(
 710  
      *          Object, int, int)
 711  
      */
 712  
     @Override
 713  
     protected void fireIntervalAdded(Object source, int index0, int index1) {
 714  8271
         if (fireListEvents && !buildingModel) {
 715  2949
             super.fireIntervalAdded(source, index0, index1);
 716  
         }
 717  8271
     }
 718  
 
 719  
     /*
 720  
      * @see javax.swing.AbstractListModel#fireIntervalRemoved(
 721  
      *          Object, int, int)
 722  
      */
 723  
     @Override
 724  
     protected void fireIntervalRemoved(Object source, int index0, int index1) {
 725  372
         if (fireListEvents && !buildingModel) {
 726  0
             super.fireIntervalRemoved(source, index0, index1);
 727  
         }
 728  372
     }
 729  
 
 730  
     /*
 731  
      * @see TargetListener#targetAdded(TargetEvent)
 732  
      */
 733  
     public void targetAdded(TargetEvent e) {
 734  0
         LOG.debug("targetAdded targetevent :  " + e);
 735  0
         setTarget(e.getNewTarget());
 736  0
     }
 737  
 
 738  
     /*
 739  
      * @see TargetListener#targetRemoved(TargetEvent)
 740  
      */
 741  
     public void targetRemoved(TargetEvent e) {
 742  176
         LOG.debug("targetRemoved targetevent :  " + e);
 743  176
         Object currentTarget = comboBoxTarget;
 744  176
         Object oldTarget =
 745  
             e.getOldTargets().length > 0
 746  
             ? e.getOldTargets()[0] : null;
 747  176
         if (oldTarget instanceof Fig) {
 748  0
             oldTarget = ((Fig) oldTarget).getOwner();
 749  
         }
 750  176
         if (oldTarget == currentTarget) {
 751  176
             if (Model.getFacade().isAModelElement(currentTarget)) {
 752  0
                 Model.getPump().removeModelEventListener(this,
 753  
                         currentTarget, propertySetName);
 754  
             }
 755  176
             comboBoxTarget = e.getNewTarget();
 756  
         }
 757  176
         setTarget(e.getNewTarget());
 758  176
     }
 759  
 
 760  
     /*
 761  
      * @see TargetListener#targetSet(TargetEvent)
 762  
      */
 763  
     public void targetSet(TargetEvent e) {
 764  4547
         LOG.debug("targetSet targetevent :  " + e);
 765  4547
         setTarget(e.getNewTarget());
 766  
 
 767  4547
     }
 768  
 
 769  
     /**
 770  
      * Return boolean indicating whether combo allows empty string.  This 
 771  
      * flag can only be specified in the constructor, so it will never change.
 772  
      * The flag is checked directly internally, so overriding this method will
 773  
      * have no effect.
 774  
      * 
 775  
      * @return state of isClearable flag
 776  
      */
 777  
     protected boolean isClearable() {
 778  0
         return isClearable;
 779  
     }
 780  
 
 781  
     /**
 782  
      * @return name of property registered with event listener
 783  
      */
 784  
     protected String getPropertySetName() {
 785  0
         return propertySetName;
 786  
     }
 787  
 
 788  
     /**
 789  
      * @return Returns the fireListEvents.
 790  
      */
 791  
     protected boolean isFireListEvents() {
 792  0
         return fireListEvents;
 793  
     }
 794  
 
 795  
     /**
 796  
      * @param events The fireListEvents to set.
 797  
      */
 798  
     protected void setFireListEvents(boolean events) {
 799  0
         this.fireListEvents = events;
 800  0
     }
 801  
     
 802  
     protected boolean isLazy() {
 803  0
         return false;
 804  
     }
 805  
     
 806  
     /**
 807  
      * Indicate that the model has to be rebuild. 
 808  
      * For a lazy model, this suffices to get the model rebuild 
 809  
      * the next time the user opens the combo.
 810  
      */
 811  
     protected void setModelInvalid() {
 812  2949
         assert isLazy(); // catch callers attempting to use one without other
 813  2949
         modelValid = false;
 814  2949
     }
 815  
 
 816  
     public void popupMenuCanceled(PopupMenuEvent e) {
 817  0
     }
 818  
 
 819  
     public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
 820  0
     }
 821  
 
 822  
     public void popupMenuWillBecomeVisible(PopupMenuEvent ev) {
 823  0
         if (isLazy() && !modelValid && !processingWillBecomeVisible) {
 824  0
             buildModelListTimed();
 825  0
             modelValid = true;
 826  
             // We should be able to just do the above, but Swing has already
 827  
             // computed the size of the popup menu.  The rest of this is
 828  
             // a workaround for Swing bug 
 829  
             // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4743225
 830  0
             JComboBox list = (JComboBox) ev.getSource();
 831  0
             processingWillBecomeVisible = true;
 832  
             try {
 833  0
                 list.getUI().setPopupVisible( list, true );
 834  
             } finally {
 835  0
                 processingWillBecomeVisible = false;
 836  0
             }
 837  
         }
 838  0
     }
 839  
 }