| 1 | /********************************************************************** |
| 2 | * Copyright (c) 2005, 2014 IBM Corporation, Ericsson |
| 3 | * All rights reserved. This program and the accompanying materials |
| 4 | * are made available under the terms of the Eclipse Public License v1.0 |
| 5 | * which accompanies this distribution, and is available at |
| 6 | * http://www.eclipse.org/legal/epl-v10.html |
| 7 | * |
| 8 | * Contributors: |
| 9 | * IBM - Initial API and implementation |
| 10 | * Bernd Hufmann - Updated for TMF |
| 11 | **********************************************************************/ |
| 12 | |
| 13 | package org.eclipse.tracecompass.tmf.ui.views.uml2sd.core; |
| 14 | |
| 15 | import java.util.ArrayList; |
| 16 | import java.util.List; |
| 17 | |
| 18 | import org.eclipse.tracecompass.tmf.ui.views.uml2sd.drawings.IColor; |
| 19 | import org.eclipse.tracecompass.tmf.ui.views.uml2sd.drawings.IGC; |
| 20 | import org.eclipse.tracecompass.tmf.ui.views.uml2sd.drawings.IImage; |
| 21 | import org.eclipse.tracecompass.tmf.ui.views.uml2sd.preferences.ISDPreferences; |
| 22 | import org.eclipse.tracecompass.tmf.ui.views.uml2sd.preferences.SDViewPref; |
| 23 | |
| 24 | /** |
| 25 | * Lifeline is the UML2 lifeline graphical representation.<br> |
| 26 | * Each lifeline owns a set of event occurrences. An event occurrence is the base element in UML2 to set an event in a |
| 27 | * sequence diagram.<br> |
| 28 | * Event occurrence define the drawing order of graph node along a lifeline. In this lifeline implementation, event |
| 29 | * occurrences are just integer index. The event occurrences with the same value on different lifelines will correspond |
| 30 | * the same y coordinate value. |
| 31 | * |
| 32 | * @version 1.0 |
| 33 | * @author sveyrier |
| 34 | * |
| 35 | */ |
| 36 | public class Lifeline extends GraphNode { |
| 37 | // ------------------------------------------------------------------------ |
| 38 | // Constants |
| 39 | // ------------------------------------------------------------------------ |
| 40 | /** |
| 41 | * The life line tag. |
| 42 | */ |
| 43 | public static final String LIFELINE_TAG = "Lifeline"; //$NON-NLS-1$ |
| 44 | |
| 45 | // ------------------------------------------------------------------------ |
| 46 | // Attribute |
| 47 | // ------------------------------------------------------------------------ |
| 48 | /** |
| 49 | * The lifeline position in the containing frame |
| 50 | */ |
| 51 | private int fIndexInFrame = 0; |
| 52 | /** |
| 53 | * The frame where the lifeline is drawn |
| 54 | */ |
| 55 | private Frame fFrame = null; |
| 56 | /** |
| 57 | * The current event occurrence created in the lifeline |
| 58 | */ |
| 59 | private int fEventOccurrence = 0; |
| 60 | /** |
| 61 | * The lifeline category. |
| 62 | */ |
| 63 | private int fCategory = -1; |
| 64 | /** |
| 65 | * Flag whether lifeline has time information available or not |
| 66 | */ |
| 67 | private boolean fHasTimeInfo = false; |
| 68 | |
| 69 | // ------------------------------------------------------------------------ |
| 70 | // Constructors |
| 71 | // ------------------------------------------------------------------------ |
| 72 | /** |
| 73 | * Default constructor |
| 74 | */ |
| 75 | public Lifeline() { |
| 76 | setColorPrefId(ISDPreferences.PREF_LIFELINE); |
| 77 | } |
| 78 | |
| 79 | // ------------------------------------------------------------------------ |
| 80 | // Methods |
| 81 | // ------------------------------------------------------------------------ |
| 82 | |
| 83 | @Override |
| 84 | public int getX() { |
| 85 | return Metrics.FRAME_H_MARGIN + Metrics.LIFELINE_H_MAGIN + (fIndexInFrame - 1) * Metrics.swimmingLaneWidth(); |
| 86 | } |
| 87 | |
| 88 | @Override |
| 89 | public int getY() { |
| 90 | return 2 * Metrics.FRAME_NAME_H_MARGIN + Metrics.LIFELINE_VT_MAGIN / 2 + Metrics.getFrameFontHeigth() + Metrics.getLifelineHeaderFontHeigth() + Metrics.FRAME_V_MARGIN + 2 * Metrics.LIFELINE_HEARDER_TEXT_V_MARGIN; |
| 91 | } |
| 92 | |
| 93 | @Override |
| 94 | public int getWidth() { |
| 95 | return Metrics.getLifelineWidth(); |
| 96 | } |
| 97 | |
| 98 | @Override |
| 99 | public int getHeight() { |
| 100 | // Set room for two text lines |
| 101 | return Metrics.getLifelineFontHeigth()/** 2 */ |
| 102 | + 2 * Metrics.LIFELINE_NAME_H_MARGIN; |
| 103 | } |
| 104 | |
| 105 | /** |
| 106 | * Set the lifeline category for this lifeline. |
| 107 | * |
| 108 | * @param arrayIndex the index of the category to use |
| 109 | * @see Frame#setLifelineCategories(LifelineCategories[]) |
| 110 | */ |
| 111 | public void setCategory(int arrayIndex) { |
| 112 | fCategory = arrayIndex; |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * Gets the lifeline category for this lifeline. |
| 117 | * |
| 118 | * @return arrayIndex the index of the category to use |
| 119 | */ |
| 120 | public int getCategory() { |
| 121 | return fCategory; |
| 122 | } |
| 123 | |
| 124 | /** |
| 125 | * Returns the tooltip text for the lifeline. It is the combination between the category name(if any) and the |
| 126 | * lifeline name |
| 127 | * |
| 128 | * @return the tooltip text |
| 129 | */ |
| 130 | public String getToolTipText() { |
| 131 | if (fCategory >= 0) { |
| 132 | LifelineCategories[] categories = fFrame.getLifelineCategories(); |
| 133 | if (fCategory < categories.length) { |
| 134 | return categories[fCategory].getName() + " " + getName(); //$NON-NLS-1$ |
| 135 | } |
| 136 | } |
| 137 | return ""; //$NON-NLS-1$ |
| 138 | } |
| 139 | |
| 140 | /** |
| 141 | * Returns the index of the first visible Execution Occurrence in the execution occurrence array.<br> |
| 142 | * Execution Occurrences are Y ordered in this array |
| 143 | * |
| 144 | * @return the first visible Execution Occurrence |
| 145 | */ |
| 146 | public int getExecOccurrenceDrawIndex() { |
| 147 | if (!hasChildren()) { |
| 148 | return 0; |
| 149 | } |
| 150 | if (getIndexes().get(BasicExecutionOccurrence.EXEC_OCC_TAG) != null) { |
| 151 | return getIndexes().get(BasicExecutionOccurrence.EXEC_OCC_TAG).intValue(); |
| 152 | } |
| 153 | return 0; |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * Set the frame on which this lifeline must be drawn |
| 158 | * |
| 159 | * @param parentFrame |
| 160 | * Parent frame |
| 161 | */ |
| 162 | protected void setFrame(Frame parentFrame) { |
| 163 | fFrame = parentFrame; |
| 164 | if (fHasTimeInfo) { |
| 165 | fFrame.setHasTimeInfo(true); |
| 166 | } |
| 167 | if (fFrame.getMaxEventOccurrence() < getEventOccurrence() + 1) { |
| 168 | fFrame.setMaxEventOccurrence(getEventOccurrence() + 1); |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | /** |
| 173 | * Returns the frame which this lifeline is drawn |
| 174 | * |
| 175 | * @return the Frame |
| 176 | */ |
| 177 | protected Frame getFrame() { |
| 178 | return fFrame; |
| 179 | } |
| 180 | |
| 181 | /** |
| 182 | * Set the lifeline position index in the containing frame |
| 183 | * |
| 184 | * @param index the lifeline X position |
| 185 | */ |
| 186 | protected void setIndex(int index) { |
| 187 | fIndexInFrame = index; |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * Returns the lifeline position in de the containing frame |
| 192 | * |
| 193 | * @return the X position |
| 194 | */ |
| 195 | public int getIndex() { |
| 196 | return fIndexInFrame; |
| 197 | } |
| 198 | |
| 199 | /** |
| 200 | * Set the lifeline event occurrence to the value given in parameter This only change the current event occurrence, |
| 201 | * greater event created on this lifeline are still valid and usable. This also need to inform the frame of the |
| 202 | * operation mostly to store in the frame the greater event found in the diagram (used to determine the frame |
| 203 | * height) |
| 204 | * |
| 205 | * @param eventOcc the new current event occurrence |
| 206 | */ |
| 207 | public void setCurrentEventOccurrence(int eventOcc) { |
| 208 | if ((fFrame != null) && (fFrame.getMaxEventOccurrence() < eventOcc)) { |
| 209 | fFrame.setMaxEventOccurrence(eventOcc); |
| 210 | } |
| 211 | fEventOccurrence = eventOcc; |
| 212 | } |
| 213 | |
| 214 | /** |
| 215 | * Returns the last created event occurrence along the lifeline. |
| 216 | * |
| 217 | * @return the current event occurrence |
| 218 | */ |
| 219 | public int getEventOccurrence() { |
| 220 | return fEventOccurrence; |
| 221 | } |
| 222 | |
| 223 | /** |
| 224 | * Creates a new event occurrence along the lifeline. |
| 225 | * |
| 226 | * @return the new created event occurrence |
| 227 | */ |
| 228 | public int getNewEventOccurrence() { |
| 229 | setCurrentEventOccurrence(fEventOccurrence + 1); |
| 230 | return fEventOccurrence; |
| 231 | } |
| 232 | |
| 233 | /** |
| 234 | * Adds the execution occurrence given in parameter to the lifeline.<br> |
| 235 | * A Execution occurrence is never drawn in the frame instead it is added to a lifeline |
| 236 | * |
| 237 | * @param exec the execution occurrence to add |
| 238 | */ |
| 239 | public void addExecution(BasicExecutionOccurrence exec) { |
| 240 | exec.setLifeline(this); |
| 241 | addNode(exec); |
| 242 | if ((fFrame != null) && (fFrame.getMaxEventOccurrence() < exec.getEndOccurrence())) { |
| 243 | fFrame.setMaxEventOccurrence(exec.getEndOccurrence()); |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | /** |
| 248 | * Set whether lifeline has time information available or not. |
| 249 | * @param value The value to set |
| 250 | */ |
| 251 | protected void setTimeInfo(boolean value) { |
| 252 | fHasTimeInfo = value; |
| 253 | if ((fFrame != null) && value) { |
| 254 | fFrame.setHasTimeInfo(value); |
| 255 | } |
| 256 | } |
| 257 | |
| 258 | /** |
| 259 | * Returns true if at least one execution occurrence has time info. |
| 260 | * |
| 261 | * @return true if at least one execution occurrence has time info |
| 262 | */ |
| 263 | public boolean hasTimeInfo() { |
| 264 | return fHasTimeInfo; |
| 265 | } |
| 266 | |
| 267 | /** |
| 268 | * Returns the list of execution occurrence on this lifeline. |
| 269 | * |
| 270 | * @return the execution occurrence list |
| 271 | */ |
| 272 | public List<GraphNode> getExecutions() { |
| 273 | if (hasChildren()) { |
| 274 | return getNodeMap().get(BasicExecutionOccurrence.EXEC_OCC_TAG); |
| 275 | } |
| 276 | return new ArrayList<>(); |
| 277 | } |
| 278 | |
| 279 | @Override |
| 280 | public boolean contains(int xValue, int yValue) { |
| 281 | int x = getX(); |
| 282 | int y = getY(); |
| 283 | int width = getWidth(); |
| 284 | int height = getHeight(); |
| 285 | |
| 286 | if (fFrame == null) { |
| 287 | return false; |
| 288 | } |
| 289 | if (GraphNode.contains(x, y, width, height, xValue, yValue)) { |
| 290 | return true; |
| 291 | } |
| 292 | if (GraphNode.contains(x + Metrics.getLifelineWidth() / 2 - Metrics.EXECUTION_OCCURRENCE_WIDTH / 2, y + height, Metrics.EXECUTION_OCCURRENCE_WIDTH, (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * fFrame.getMaxEventOccurrence() |
| 293 | + Metrics.LIFELINE_VB_MAGIN - 4, xValue, yValue)) { |
| 294 | return true; |
| 295 | } |
| 296 | |
| 297 | height = Metrics.getLifelineFontHeigth() + 2 * Metrics.LIFELINE_HEARDER_TEXT_V_MARGIN; |
| 298 | int hMargin = (Metrics.LIFELINE_VT_MAGIN - height) / 2; |
| 299 | |
| 300 | if (hMargin >= 2) { |
| 301 | if (fFrame.getVisibleAreaY() < y - height - hMargin) { |
| 302 | if (GraphNode.contains(x - Metrics.LIFELINE_SPACING / 2 + 1, y - height - hMargin, Metrics.swimmingLaneWidth() - 2, height + 1, xValue, yValue)) { |
| 303 | return true; |
| 304 | } |
| 305 | } else { |
| 306 | if (GraphNode.contains(x - Metrics.LIFELINE_SPACING / 2 + 1, fFrame.getVisibleAreaY(), Metrics.swimmingLaneWidth() - 2, height, xValue, yValue)) { |
| 307 | return true; |
| 308 | } |
| 309 | } |
| 310 | } |
| 311 | if (getNodeAt(xValue, yValue) != null) { |
| 312 | return true; |
| 313 | } |
| 314 | return false; |
| 315 | } |
| 316 | |
| 317 | /** |
| 318 | * Returns the lifeline visibility for the given visible area |
| 319 | * |
| 320 | * @param vx The x coordinate of the visible area |
| 321 | * @param vy The y coordinate of the visible area |
| 322 | * @param vwidth The width of the visible area |
| 323 | * @param vheight The height of the visible area |
| 324 | * @return true if visible false otherwise |
| 325 | */ |
| 326 | @Override |
| 327 | public boolean isVisible(int vx, int vy, int vwidth, int vheight) { |
| 328 | int x = getX(); |
| 329 | int width = getWidth(); |
| 330 | if (((x >= vx) && (x <= vx + vwidth)) || ((x + width >= vx) && (x <= vx))) { |
| 331 | return true; |
| 332 | } |
| 333 | return false; |
| 334 | } |
| 335 | |
| 336 | /** |
| 337 | * Draws the name within the graphical context. |
| 338 | * |
| 339 | * @param context The graphical context. |
| 340 | */ |
| 341 | protected void drawName(IGC context) { |
| 342 | ISDPreferences pref = SDViewPref.getInstance(); |
| 343 | |
| 344 | int x = getX(); |
| 345 | int y = getY(); |
| 346 | int height = Metrics.getLifelineHeaderFontHeigth() + 2 * Metrics.LIFELINE_HEARDER_TEXT_V_MARGIN; |
| 347 | int hMargin = Metrics.LIFELINE_VT_MAGIN / 4;// (Metrics.LIFELINE_NAME_H_MARGIN)/2; |
| 348 | |
| 349 | context.setLineStyle(context.getLineSolidStyle()); |
| 350 | context.setBackground(pref.getBackGroundColor(ISDPreferences.PREF_LIFELINE_HEADER)); |
| 351 | context.setForeground(pref.getForeGroundColor(ISDPreferences.PREF_LIFELINE_HEADER)); |
| 352 | context.setFont(pref.getFont(ISDPreferences.PREF_LIFELINE_HEADER)); |
| 353 | if (hMargin >= 0) { |
| 354 | if (fFrame.getVisibleAreaY() < y - height - hMargin) { |
| 355 | context.fillRectangle(x - Metrics.LIFELINE_SPACING / 2 + 1, y - height - hMargin, Metrics.swimmingLaneWidth() - 2, height); |
| 356 | context.drawRectangle(x - Metrics.LIFELINE_SPACING / 2 + 1, y - height - hMargin, Metrics.swimmingLaneWidth() - 2, height); |
| 357 | context.setForeground(pref.getFontColor(ISDPreferences.PREF_LIFELINE_HEADER)); |
| 358 | context.drawTextTruncatedCentred(getName(), x + Metrics.LIFELINE_NAME_V_MARGIN - Metrics.LIFELINE_SPACING / 2 + 1, y - height - hMargin, Metrics.swimmingLaneWidth() - 2 * Metrics.LIFELINE_NAME_V_MARGIN - 2, height, true); |
| 359 | } else { |
| 360 | context.fillRectangle(x - Metrics.LIFELINE_SPACING / 2 + 1, fFrame.getVisibleAreaY(), Metrics.swimmingLaneWidth() - 2, height); |
| 361 | context.drawRectangle(x - Metrics.LIFELINE_SPACING / 2 + 1, fFrame.getVisibleAreaY(), Metrics.swimmingLaneWidth() - 2, height); |
| 362 | context.setForeground(pref.getFontColor(ISDPreferences.PREF_LIFELINE_HEADER)); |
| 363 | context.drawTextTruncatedCentred(getName(), x - Metrics.LIFELINE_SPACING / 2 + Metrics.LIFELINE_NAME_V_MARGIN + 1, fFrame.getVisibleAreaY(), Metrics.swimmingLaneWidth() - 2 * Metrics.LIFELINE_NAME_V_MARGIN - 2, height, true); |
| 364 | } |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | /** |
| 369 | * Force the lifeline to be drawn at the given coordinate |
| 370 | * |
| 371 | * @param context - the context to draw into |
| 372 | * @param x - the x coordinate |
| 373 | * @param y - the y coordinate |
| 374 | */ |
| 375 | public void draw(IGC context, int x, int y) { |
| 376 | |
| 377 | ISDPreferences pref = SDViewPref.getInstance(); |
| 378 | |
| 379 | // Set the draw color depending if the lifeline must be selected or not |
| 380 | context.setLineWidth(Metrics.NORMAL_LINE_WIDTH); |
| 381 | if (isSelected()) { |
| 382 | if (pref.useGradienColor()) { |
| 383 | context.setGradientColor(pref.getBackGroundColor(ISDPreferences.PREF_LIFELINE)); |
| 384 | } |
| 385 | context.setBackground(pref.getBackGroundColorSelection()); |
| 386 | context.setForeground(pref.getForeGroundColorSelection()); |
| 387 | } else { |
| 388 | if (pref.useGradienColor()) { |
| 389 | context.setGradientColor(pref.getBackGroundColor(ISDPreferences.PREF_LIFELINE)); |
| 390 | context.setBackground(pref.getBackGroundColor(ISDPreferences.PREF_FRAME)); |
| 391 | } else { |
| 392 | context.setBackground(pref.getBackGroundColor(ISDPreferences.PREF_LIFELINE)); |
| 393 | } |
| 394 | context.setForeground(pref.getForeGroundColor(ISDPreferences.PREF_LIFELINE)); |
| 395 | } |
| 396 | // Store the lifeline coordinates to save some calls |
| 397 | int width = getWidth(); |
| 398 | int height = getHeight(); |
| 399 | |
| 400 | // Draw the rectangle which contain the lifeline name |
| 401 | if (pref.useGradienColor()) { |
| 402 | context.fillGradientRectangle(x, y, width, height / 2 - 7, true); |
| 403 | context.fillRectangle(x, y + height / 2 - 8, width, +height / 2 - 5); |
| 404 | context.fillGradientRectangle(x, y + height, width, -height / 2 + 6, true); |
| 405 | } else { |
| 406 | context.fillRectangle(x, y, width, height); |
| 407 | } |
| 408 | context.drawRectangle(x, y, width, height); |
| 409 | |
| 410 | if (fCategory >= 0) { |
| 411 | LifelineCategories[] categories = fFrame.getLifelineCategories(); |
| 412 | if (fCategory < categories.length) { |
| 413 | IImage image = categories[fCategory].getImage(); |
| 414 | if (image != null) { |
| 415 | context.drawImage(image, x, y, width, height); |
| 416 | } |
| 417 | } |
| 418 | } |
| 419 | |
| 420 | // Draw the lifeline label into the rectangle |
| 421 | // The label is truncated if it cannot fit |
| 422 | IColor temp = context.getForeground(); |
| 423 | context.setFont(pref.getFont(ISDPreferences.PREF_LIFELINE)); |
| 424 | context.setForeground(pref.getFontColor(ISDPreferences.PREF_LIFELINE)); |
| 425 | context.drawTextTruncatedCentred(getName(), x + Metrics.LIFELINE_NAME_V_MARGIN, y, Metrics.getLifelineWidth() - 2 * Metrics.LIFELINE_NAME_V_MARGIN, height, true); |
| 426 | |
| 427 | context.setLineStyle(context.getLineDashStyle()); |
| 428 | context.setForeground(temp); |
| 429 | int oldStyle = context.getLineStyle(); |
| 430 | |
| 431 | // Now draw the lifeline vertical line |
| 432 | // this line height depends on a stop assignment |
| 433 | // if there is no stop the line is drawn to the bottom of the frame |
| 434 | |
| 435 | // by default set the height to reach the frame bottom |
| 436 | int dashedLineEnd = y + height + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * fFrame.getMaxEventOccurrence() + Metrics.LIFELINE_VB_MAGIN; |
| 437 | /* |
| 438 | * if (stop != null) { dashedLineEnd = stop.getY(); } |
| 439 | */ |
| 440 | |
| 441 | if (isSelected()) { |
| 442 | context.setForeground(pref.getBackGroundColorSelection()); |
| 443 | context.setLineWidth(5); |
| 444 | context.drawLine(x + Metrics.getLifelineWidth() / 2, y + height, x + Metrics.getLifelineWidth() / 2, dashedLineEnd - 4); |
| 445 | context.setForeground(pref.getForeGroundColorSelection()); |
| 446 | } |
| 447 | |
| 448 | context.setLineWidth(Metrics.NORMAL_LINE_WIDTH); |
| 449 | context.drawLine(x + Metrics.getLifelineWidth() / 2, y + height, x + Metrics.getLifelineWidth() / 2, dashedLineEnd - 4); |
| 450 | context.drawLine(x + Metrics.getLifelineWidth() / 2, y + height, x + Metrics.getLifelineWidth() / 2, dashedLineEnd - 4); |
| 451 | context.setLineStyle(oldStyle); |
| 452 | |
| 453 | context.setLineStyle(context.getLineSolidStyle()); |
| 454 | |
| 455 | if (hasFocus()) { |
| 456 | drawFocus(context); |
| 457 | } |
| 458 | |
| 459 | super.drawChildenNodes(context); |
| 460 | } |
| 461 | |
| 462 | /** |
| 463 | * Draws the select execution occurrence region using the given color |
| 464 | * |
| 465 | * @param context the graphical context |
| 466 | * @param startEvent the region start |
| 467 | * @param nbEvent the region height |
| 468 | * @param color the color to use |
| 469 | */ |
| 470 | public void highlightExecOccurrenceRegion(IGC context, int startEvent, int nbEvent, IColor color) { |
| 471 | IColor backupColor = context.getBackground(); |
| 472 | context.setBackground(color); |
| 473 | int x = getX() + Metrics.getLifelineWidth() / 2 - Metrics.EXECUTION_OCCURRENCE_WIDTH / 2; |
| 474 | int y = getY() + getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * startEvent; |
| 475 | int width = Metrics.EXECUTION_OCCURRENCE_WIDTH; |
| 476 | int height = ((Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing())) * nbEvent; |
| 477 | context.fillRectangle(x, y, width, height); |
| 478 | context.setBackground(backupColor); |
| 479 | } |
| 480 | |
| 481 | @Override |
| 482 | public void draw(IGC context) { |
| 483 | draw(context, getX(), getY()); |
| 484 | } |
| 485 | |
| 486 | @Override |
| 487 | public String getArrayId() { |
| 488 | return LIFELINE_TAG; |
| 489 | } |
| 490 | |
| 491 | @Override |
| 492 | public boolean positiveDistanceToPoint(int x, int y) { |
| 493 | if (getX() > x - Metrics.swimmingLaneWidth()) { |
| 494 | return true; |
| 495 | } |
| 496 | return false; |
| 497 | } |
| 498 | |
| 499 | @Override |
| 500 | public GraphNode getNodeAt(int x, int y) { |
| 501 | int vy = 0; |
| 502 | int vh = 0; |
| 503 | if (getFrame() != null) { |
| 504 | vy = getFrame().getVisibleAreaY(); |
| 505 | vh = getFrame().getVisibleAreaHeight(); |
| 506 | } else { |
| 507 | return null; |
| 508 | } |
| 509 | if (getExecutions() == null) { |
| 510 | return null; |
| 511 | } |
| 512 | for (int i = getExecOccurrenceDrawIndex(); i < getExecutions().size(); i++) { |
| 513 | GraphNode node = getExecutions().get(i); |
| 514 | if (node.getHeight() < 0) { |
| 515 | if (node.getY() + node.getHeight() > vy + vh) { |
| 516 | break; |
| 517 | } |
| 518 | } else { |
| 519 | if (node.getY() > vy + vh) { |
| 520 | break; |
| 521 | } |
| 522 | } |
| 523 | if (node.contains(x, y)) { |
| 524 | GraphNode internal = node.getNodeAt(x, y); |
| 525 | if (internal != null) { |
| 526 | return internal; |
| 527 | } |
| 528 | return node; |
| 529 | } |
| 530 | } |
| 531 | return null; |
| 532 | } |
| 533 | } |