Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
FigStereotypesGroup |
|
| 3.4210526315789473;3.421 |
1 | /* $Id: FigStereotypesGroup.java 17741 2010-01-10 03:50:08Z bobtarling $ | |
2 | ******************************************************************************* | |
3 | * Copyright (c) 2009-2010 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 | * Bob Tarling | |
11 | ******************************************************************************* | |
12 | * | |
13 | * Some portions of this file was previously release using the BSD License: | |
14 | */ | |
15 | // $Id: FigStereotypesGroup.java 17741 2010-01-10 03:50:08Z bobtarling $ | |
16 | // Copyright (c) 1996-2009 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.diagram.ui; | |
40 | ||
41 | import java.awt.Dimension; | |
42 | import java.awt.Image; | |
43 | import java.awt.Rectangle; | |
44 | import java.beans.PropertyChangeEvent; | |
45 | import java.util.ArrayList; | |
46 | import java.util.Collection; | |
47 | import java.util.List; | |
48 | ||
49 | import org.apache.log4j.Logger; | |
50 | import org.argouml.kernel.Project; | |
51 | import org.argouml.model.AddAssociationEvent; | |
52 | import org.argouml.model.Model; | |
53 | import org.argouml.model.RemoveAssociationEvent; | |
54 | import org.argouml.uml.diagram.DiagramSettings; | |
55 | import org.tigris.gef.presentation.Fig; | |
56 | import org.tigris.gef.presentation.FigRect; | |
57 | import org.tigris.gef.presentation.FigText; | |
58 | ||
59 | /** | |
60 | * A Fig designed to be the child of some FigNode or FigEdge to display the | |
61 | * stereotypes of the model element represented by the parent Fig. | |
62 | * Currently, multiple stereotypes are shown stacked one on top of the other, | |
63 | * each enclosed by guillemets.<p> | |
64 | * | |
65 | * The minimum width of this fig is the largest minimum width of its child | |
66 | * figs. The minimum height of this fig is the total minimum height of its child | |
67 | * figs.<p> | |
68 | * | |
69 | * The owner of this Fig is the UML element that is extended | |
70 | * with the stereotypes. We are listening to changes to the model: | |
71 | * addition and removal of stereotypes. <p> | |
72 | * | |
73 | * This fig supports showing one keyword | |
74 | * as the first "stereotype" in the list. <p> | |
75 | * | |
76 | * There is no way to remove a keyword fig, once added. <p> | |
77 | * | |
78 | * TODO: Allow for UML2 style display where all stereotypes are displayed in | |
79 | * the same guillemet pair and are delimited by commas. The style should be | |
80 | * changeable by calling getOrientation(Orientation). The swidget Orientation | |
81 | * class can be used for this. | |
82 | * @author Bob Tarling | |
83 | */ | |
84 | public class FigStereotypesGroup extends ArgoFigGroup { | |
85 | ||
86 | private Fig bigPort; | |
87 | ||
88 | /** | |
89 | * Logger. | |
90 | */ | |
91 | 0 | private static final Logger LOG = |
92 | Logger.getLogger(FigStereotypesGroup.class); | |
93 | ||
94 | /** | |
95 | * One UML keyword is allowed. These are not strictly stereotypes but are | |
96 | * displayed as such. e.g. <<interface>> | |
97 | */ | |
98 | private String keyword; | |
99 | ||
100 | 0 | private int stereotypeCount = 0; |
101 | ||
102 | 0 | private boolean hidingStereotypesWithIcon = false; |
103 | ||
104 | private void constructFigs(int x, int y, int w, int h) { | |
105 | 0 | bigPort = new FigRect(x, y, w, h, LINE_COLOR, FILL_COLOR); |
106 | 0 | addFig(bigPort); |
107 | 0 | bigPort.setFilled(false); |
108 | ||
109 | /* Do not show border line, make transparent: */ | |
110 | 0 | setLineWidth(0); |
111 | 0 | setFilled(false); |
112 | 0 | } |
113 | ||
114 | /** | |
115 | * The constructor. | |
116 | * | |
117 | * @param owner owning UML element | |
118 | * @param bounds position and size | |
119 | * @param settings render settings | |
120 | */ | |
121 | public FigStereotypesGroup(Object owner, Rectangle bounds, | |
122 | DiagramSettings settings) { | |
123 | 0 | super(owner, settings); |
124 | 0 | constructFigs(bounds.x, bounds.y, bounds.width, bounds.height); |
125 | 0 | Model.getPump().addModelEventListener(this, owner, "stereotype"); |
126 | 0 | populate(); |
127 | 0 | } |
128 | ||
129 | ||
130 | /* | |
131 | * @see org.tigris.gef.presentation.Fig#removeFromDiagram() | |
132 | */ | |
133 | @Override | |
134 | public void removeFromDiagram() { | |
135 | /* Remove all items in the group, | |
136 | * otherwise the model event listeners remain: | |
137 | * TODO: Why does a FigGroup not do this? */ | |
138 | 0 | for (Object f : getFigs()) { |
139 | 0 | ((Fig) f).removeFromDiagram(); |
140 | } | |
141 | 0 | super.removeFromDiagram(); |
142 | 0 | Model.getPump() |
143 | .removeModelEventListener(this, getOwner(), "stereotype"); | |
144 | 0 | } |
145 | ||
146 | /** | |
147 | * @return the bigport | |
148 | * @deprecated for 0.27.2. For backward compatibility only. The visibility | |
149 | * of this method will be changed to private in the next release | |
150 | * when FigStereotypesCompartment is removed. | |
151 | */ | |
152 | @Deprecated | |
153 | protected Fig getBigPort() { | |
154 | 0 | return bigPort; |
155 | } | |
156 | ||
157 | @Override | |
158 | public void propertyChange(PropertyChangeEvent event) { | |
159 | 0 | if (event instanceof AddAssociationEvent) { |
160 | 0 | AddAssociationEvent aae = (AddAssociationEvent) event; |
161 | 0 | if (event.getPropertyName().equals("stereotype")) { |
162 | 0 | Object stereotype = aae.getChangedValue(); |
163 | 0 | if (findFig(stereotype) == null) { |
164 | 0 | FigText stereotypeTextFig = |
165 | new FigStereotype(stereotype, | |
166 | getBoundsForNextStereotype(), | |
167 | getSettings()); | |
168 | 0 | stereotypeCount++; |
169 | 0 | addFig(stereotypeTextFig); |
170 | 0 | reorderStereotypeFigs(); |
171 | 0 | damage(); |
172 | } | |
173 | 0 | } else { |
174 | 0 | LOG.warn("Unexpected property " + event.getPropertyName()); |
175 | } | |
176 | } | |
177 | 0 | if (event instanceof RemoveAssociationEvent) { |
178 | 0 | if (event.getPropertyName().equals("stereotype")) { |
179 | 0 | RemoveAssociationEvent rae = (RemoveAssociationEvent) event; |
180 | 0 | Object stereotype = rae.getChangedValue(); |
181 | 0 | Fig f = findFig(stereotype); |
182 | 0 | if (f != null) { |
183 | 0 | removeFig(f); |
184 | 0 | f.removeFromDiagram(); // or vice versa? |
185 | 0 | --stereotypeCount; |
186 | } | |
187 | 0 | } else { |
188 | 0 | LOG.warn("Unexpected property " + event.getPropertyName()); |
189 | } | |
190 | } | |
191 | 0 | } |
192 | ||
193 | /** | |
194 | * Keep the Figs which are likely invisible at the end of the list. | |
195 | */ | |
196 | private void reorderStereotypeFigs() { | |
197 | 0 | List<Fig> allFigs = getFigs(); |
198 | 0 | List<Fig> figsWithIcon = new ArrayList<Fig>(); |
199 | 0 | List<Fig> figsWithOutIcon = new ArrayList<Fig>(); |
200 | 0 | List<Fig> others = new ArrayList<Fig>(); |
201 | ||
202 | // TODO: This doesn't do anything special with keywords. | |
203 | // They should probably go first. | |
204 | 0 | for (Fig f : allFigs) { |
205 | 0 | if (f instanceof FigStereotype) { |
206 | 0 | FigStereotype s = (FigStereotype) f; |
207 | 0 | if (getIconForStereotype(s) != null) { |
208 | 0 | figsWithIcon.add(s); |
209 | } else { | |
210 | 0 | figsWithOutIcon.add(s); |
211 | } | |
212 | 0 | } else { |
213 | 0 | others.add(f); |
214 | } | |
215 | } | |
216 | ||
217 | 0 | List<Fig> n = new ArrayList<Fig>(); |
218 | ||
219 | 0 | n.addAll(others); |
220 | 0 | n.addAll(figsWithOutIcon); |
221 | 0 | n.addAll(figsWithIcon); |
222 | ||
223 | 0 | setFigs(n); |
224 | 0 | } |
225 | ||
226 | private FigStereotype findFig(Object stereotype) { | |
227 | 0 | for (Object f : getFigs()) { |
228 | 0 | if (f instanceof FigStereotype) { |
229 | 0 | FigStereotype fs = (FigStereotype) f; |
230 | 0 | if (fs.getOwner() == stereotype) { |
231 | 0 | return fs; |
232 | } | |
233 | 0 | } |
234 | } | |
235 | 0 | return null; |
236 | } | |
237 | ||
238 | /** | |
239 | * Get all the child figs that represent the individual stereotypes | |
240 | * @return a List of the stereotype Figs | |
241 | */ | |
242 | List<FigStereotype> getStereotypeFigs() { | |
243 | 0 | final List<FigStereotype> stereotypeFigs = |
244 | new ArrayList<FigStereotype>(); | |
245 | 0 | for (Object f : getFigs()) { |
246 | 0 | if (f instanceof FigStereotype) { |
247 | 0 | FigStereotype fs = (FigStereotype) f; |
248 | 0 | stereotypeFigs.add(fs); |
249 | 0 | } |
250 | } | |
251 | 0 | return stereotypeFigs; |
252 | } | |
253 | ||
254 | private FigKeyword findFigKeyword() { | |
255 | 0 | for (Object f : getFigs()) { |
256 | 0 | if (f instanceof FigKeyword) { |
257 | 0 | return (FigKeyword) f; |
258 | } | |
259 | } | |
260 | 0 | return null; |
261 | } | |
262 | ||
263 | /** | |
264 | * TODO: This should become private and only called from constructor | |
265 | * | |
266 | * @see org.argouml.uml.diagram.ui.FigCompartment#populate() | |
267 | */ | |
268 | public void populate() { | |
269 | ||
270 | 0 | stereotypeCount = 0; |
271 | 0 | Object modelElement = getOwner(); |
272 | 0 | if (modelElement == null) { |
273 | // TODO: This block can be removed after issue 4075 is tackled | |
274 | 0 | LOG.debug("Cannot populate the stereotype compartment " |
275 | + "unless the parent has an owner."); | |
276 | 0 | return; |
277 | } | |
278 | ||
279 | 0 | if (LOG.isDebugEnabled()) { |
280 | 0 | LOG.debug("Populating stereotypes compartment for " |
281 | + Model.getFacade().getName(modelElement)); | |
282 | } | |
283 | ||
284 | /* This will contain the Figs that we do not need anymore: */ | |
285 | 0 | Collection<Fig> removeCollection = new ArrayList<Fig>(getFigs()); |
286 | ||
287 | //There is one fig more in the group than (stereotypes + keyword): | |
288 | 0 | if (keyword != null) { |
289 | 0 | FigKeyword keywordFig = findFigKeyword(); |
290 | 0 | if (keywordFig == null) { |
291 | // The keyword fig does not exist yet. | |
292 | // Let's create one: | |
293 | 0 | keywordFig = |
294 | new FigKeyword(keyword, | |
295 | getBoundsForNextStereotype(), | |
296 | getSettings()); | |
297 | // bounds not relevant here | |
298 | 0 | addFig(keywordFig); |
299 | } else { | |
300 | // The keyword fig already exists. | |
301 | 0 | removeCollection.remove(keywordFig); |
302 | } | |
303 | 0 | ++stereotypeCount; |
304 | } | |
305 | ||
306 | 0 | for (Object stereo : Model.getFacade().getStereotypes(modelElement)) { |
307 | 0 | FigStereotype stereotypeTextFig = findFig(stereo); |
308 | 0 | if (stereotypeTextFig == null) { |
309 | 0 | stereotypeTextFig = |
310 | new FigStereotype(stereo, | |
311 | getBoundsForNextStereotype(), | |
312 | getSettings()); | |
313 | // bounds not relevant here | |
314 | 0 | addFig(stereotypeTextFig); |
315 | } else { | |
316 | // The stereotype fig already exists. | |
317 | 0 | removeCollection.remove(stereotypeTextFig); |
318 | } | |
319 | 0 | ++stereotypeCount; |
320 | 0 | } |
321 | ||
322 | //cleanup of unused FigText's | |
323 | 0 | for (Fig f : removeCollection) { |
324 | 0 | if (f instanceof FigStereotype || f instanceof FigKeyword) { |
325 | 0 | removeFig(f); |
326 | } | |
327 | } | |
328 | ||
329 | 0 | reorderStereotypeFigs(); |
330 | ||
331 | // remove all stereotypes that have a graphical icon | |
332 | 0 | updateHiddenStereotypes(); |
333 | ||
334 | 0 | } |
335 | ||
336 | /** | |
337 | * Get the number of stereotypes contained in this group | |
338 | * @return the number of stereotypes in this group | |
339 | */ | |
340 | public int getStereotypeCount() { | |
341 | 0 | return stereotypeCount; |
342 | } | |
343 | ||
344 | private Rectangle getBoundsForNextStereotype() { | |
345 | 0 | return new Rectangle( |
346 | bigPort.getX() + 1, | |
347 | bigPort.getY() + 1 | |
348 | + (stereotypeCount | |
349 | * ROWHEIGHT), | |
350 | 0, | |
351 | ROWHEIGHT - 2); | |
352 | } | |
353 | ||
354 | private void updateHiddenStereotypes() { | |
355 | 0 | List<Fig> figs = getFigs(); |
356 | 0 | for (Fig f : figs) { |
357 | 0 | if (f instanceof FigStereotype) { |
358 | 0 | FigStereotype fs = (FigStereotype) f; |
359 | 0 | fs.setVisible(getIconForStereotype(fs) == null |
360 | || !isHidingStereotypesWithIcon()); | |
361 | 0 | } |
362 | } | |
363 | 0 | } |
364 | ||
365 | private Image getIconForStereotype(FigStereotype fs) { | |
366 | // TODO: Find a way to replace this dependency on Project | |
367 | 0 | Project project = getProject(); |
368 | 0 | if (project == null) { |
369 | 0 | LOG.warn("getProject() returned null"); |
370 | 0 | return null; |
371 | } | |
372 | 0 | Object owner = fs.getOwner(); |
373 | 0 | if (owner == null) { |
374 | // keywords which look like a stereotype (e.g. <<interface>>) have | |
375 | // no owner | |
376 | 0 | return null; |
377 | } else { | |
378 | 0 | return project.getProfileConfiguration().getFigNodeStrategy() |
379 | .getIconForStereotype(owner); | |
380 | } | |
381 | } | |
382 | ||
383 | /* | |
384 | * @see org.tigris.gef.presentation.Fig#setBoundsImpl(int, int, int, int) | |
385 | */ | |
386 | @Override | |
387 | protected void setBoundsImpl(int x, int y, int w, int h) { | |
388 | 0 | Rectangle oldBounds = getBounds(); |
389 | ||
390 | 0 | Dimension minimumSize = getMinimumSize(); |
391 | 0 | int newW = Math.max(w, minimumSize.width); |
392 | 0 | int newH = Math.max(h, minimumSize.height); |
393 | ||
394 | 0 | int yy = y; |
395 | 0 | for (Fig fig : (Collection<Fig>) getFigs()) { |
396 | 0 | if (fig != bigPort) { |
397 | 0 | fig.setBounds(x, yy, newW, |
398 | fig.getMinimumSize().height); | |
399 | 0 | yy += fig.getMinimumSize().height; |
400 | } | |
401 | } | |
402 | 0 | bigPort.setBounds(x, y, newW, newH); |
403 | 0 | calcBounds(); |
404 | 0 | firePropChange("bounds", oldBounds, getBounds()); |
405 | 0 | } |
406 | ||
407 | /** | |
408 | * Allows a parent Fig to specify some keyword text to display amongst the | |
409 | * stereotypes. | |
410 | * An example of this usage is to display <<interface>> | |
411 | * on FigInterface. | |
412 | * @param word the text of the pseudo stereotype | |
413 | */ | |
414 | public void setKeyword(String word) { | |
415 | 0 | keyword = word; |
416 | 0 | populate(); |
417 | 0 | } |
418 | ||
419 | /** | |
420 | * @return true if textual stereotypes are being hidden in preference to | |
421 | * displaying icon. | |
422 | */ | |
423 | public boolean isHidingStereotypesWithIcon() { | |
424 | 0 | return hidingStereotypesWithIcon; |
425 | } | |
426 | ||
427 | /** | |
428 | * Turn on/off textual stereotype display in preference to icon. | |
429 | * | |
430 | * @param hideStereotypesWithIcon true to hide textual stereotypes and | |
431 | * show icon instead. | |
432 | */ | |
433 | public void setHidingStereotypesWithIcon(boolean hideStereotypesWithIcon) { | |
434 | 0 | this.hidingStereotypesWithIcon = hideStereotypesWithIcon; |
435 | 0 | updateHiddenStereotypes(); |
436 | 0 | } |
437 | ||
438 | @Override | |
439 | public Dimension getMinimumSize() { | |
440 | // if there are no stereotypes, we return (0,0), preventing | |
441 | // double lines in the class (see issue 4939) | |
442 | 0 | Dimension dim = null; |
443 | 0 | Object modelElement = getOwner(); |
444 | ||
445 | 0 | if (modelElement != null) { |
446 | 0 | List<FigStereotype> stereos = getStereotypeFigs(); |
447 | 0 | if (stereos.size() > 0 || keyword != null) { |
448 | 0 | int minWidth = 0; |
449 | 0 | int minHeight = 0; |
450 | //set new bounds for all included figs | |
451 | 0 | for (Fig fig : (Collection<Fig>) getFigs()) { |
452 | 0 | if (fig.isVisible() && fig != bigPort) { |
453 | 0 | int fw = fig.getMinimumSize().width; |
454 | 0 | if (fw > minWidth) { |
455 | 0 | minWidth = fw; |
456 | } | |
457 | 0 | minHeight += fig.getMinimumSize().height; |
458 | 0 | } |
459 | } | |
460 | ||
461 | 0 | minHeight += 2; // 2 Pixel padding after compartment |
462 | 0 | dim = new Dimension(minWidth, minHeight); |
463 | } | |
464 | } | |
465 | 0 | if (dim == null) { |
466 | 0 | dim = new Dimension(0, 0); |
467 | } | |
468 | 0 | return dim; |
469 | } | |
470 | } |