Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
UMLComboBoxModel2 |
|
| 3.4444444444444446;3.444 | ||||
UMLComboBoxModel2$1 |
|
| 3.4444444444444446;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 | } |