001package jmri.jmrit.display.layoutEditor; 002 003import java.awt.Component; 004import java.awt.event.ActionEvent; 005import java.awt.geom.Point2D; 006import java.awt.geom.Rectangle2D; 007import java.util.*; 008 009import javax.annotation.*; 010import javax.swing.*; 011import javax.swing.event.*; 012 013import jmri.util.MathUtil; 014 015/** 016 * A collection of tools to check various things on the layout editor panel. 017 * 018 * @author George Warner Copyright (c) 2017-2018 019 */ 020final public class LayoutEditorChecks { 021 022 private final LayoutEditor layoutEditor; 023 private final JMenu checkMenu = new JMenu(Bundle.getMessage("CheckMenuTitle")); 024 private final JMenuItem checkInProgressMenuItem = new JMenuItem(Bundle.getMessage("CheckInProgressMenuItemTitle")); 025 private final JMenuItem checkNoResultsMenuItem = new JMenuItem(Bundle.getMessage("CheckNoResultsMenuItemTitle")); 026 027 // Check for Un-Connected Tracks 028 private final JMenu checkUnConnectedTracksMenu = new JMenu(Bundle.getMessage("CheckUnConnectedTracksMenuTitle")); 029 030 // Check for Un-Blocked Tracks 031 private final JMenu checkUnBlockedTracksMenu = new JMenu(Bundle.getMessage("CheckUnBlockedTracksMenuTitle")); 032 033 // Check for Non-Contiguous Blocks 034 private final JMenu checkNonContiguousBlocksMenu = new JMenu(Bundle.getMessage("CheckNonContiguousBlocksMenuTitle")); 035 036 // Check for Unnecessary Anchors 037 private final JMenu checkUnnecessaryAnchorsMenu = new JMenu(Bundle.getMessage("CheckUnnecessaryAnchorsMenuTitle")); 038 039 // Check for Linear Bezier Track Segments 040 private final JMenu checkLinearBezierTrackSegmentsMenu = new JMenu(Bundle.getMessage("CheckLinearBezierTrackSegmentsMenuTitle")); 041 042 // Check for Fixed Radius Bezier Track Segments 043 private final JMenu checkFixedRadiusBezierTrackSegmentsMenu = new JMenu(Bundle.getMessage("CheckFixedRadiusBezierTrackSegmentsMenuTitle")); 044 045 /** 046 * The constructor for this class 047 * 048 * @param layoutEditor the layout editor that uses this class 049 */ 050 public LayoutEditorChecks(@Nonnull LayoutEditor layoutEditor) { 051 this.layoutEditor = layoutEditor; 052 } 053 054 /** 055 * set the layout editor checks menu (in the tools menu) 056 * 057 * @param toolsMenu where to add our "Check" menu and sub-menus 058 */ 059 protected void setupChecksMenu(@Nonnull JMenu toolsMenu) { 060 toolsMenu.add(checkMenu); 061 checkMenu.addMenuListener(new MenuListener() { 062 @Override 063 public void menuSelected(@Nonnull MenuEvent menuEvent) { 064 log.debug("menuSelected"); 065 boolean enabled = layoutEditor.isEditable(); 066 checkUnConnectedTracksMenu.setEnabled(enabled); 067 checkUnBlockedTracksMenu.setEnabled(enabled); 068 checkNonContiguousBlocksMenu.setEnabled(enabled); 069 checkUnnecessaryAnchorsMenu.setEnabled(enabled); 070 checkLinearBezierTrackSegmentsMenu.setEnabled(enabled); 071 checkFixedRadiusBezierTrackSegmentsMenu.setEnabled(enabled); 072 } 073 074 @Override 075 076 public void menuDeselected(@Nonnull MenuEvent menuEvent) { 077 log.debug("menuDeselected"); 078 //nothing to see here... move along... 079 } 080 081 @Override 082 public void menuCanceled(@Nonnull MenuEvent menuEvent) { 083 log.debug("menuCanceled"); 084 //nothing to see here... move along... 085 } 086 } 087 ); 088 checkMenu.setEnabled(layoutEditor.isEditable()); 089 checkMenu.setToolTipText(Bundle.getMessage("CheckMenuToolTip")); 090 091 checkNoResultsMenuItem.setToolTipText(Bundle.getMessage("CheckNoResultsMenuItemToolTip")); 092 checkNoResultsMenuItem.setEnabled(false); 093 checkInProgressMenuItem.setToolTipText(Bundle.getMessage("CheckInProgressMenuItemToolTip")); 094 checkInProgressMenuItem.setEnabled(false); 095 096 // 097 // check for tracks with free connections 098 // 099 checkUnConnectedTracksMenu.setToolTipText(Bundle.getMessage("CheckUnConnectedTracksMenuToolTip")); 100 checkUnConnectedTracksMenu.add(checkInProgressMenuItem); 101 checkMenu.add(checkUnConnectedTracksMenu); 102 103 checkUnConnectedTracksMenu.addMenuListener(new MenuListener() { 104 @Override 105 public void menuSelected(@Nonnull MenuEvent menuEvent) { 106 log.debug("menuSelected"); 107 setupCheckUnConnectedTracksMenu(); 108 } 109 110 @Override 111 public void menuDeselected(@Nonnull MenuEvent menuEvent) { 112 log.debug("menuDeselected"); 113 //nothing to see here... move along... 114 } 115 116 @Override 117 public void menuCanceled(@Nonnull MenuEvent menuEvent) { 118 log.debug("menuCanceled"); 119 //nothing to see here... move along... 120 } 121 }); 122 123 // 124 // check for tracks without assigned blocks 125 // 126 checkUnBlockedTracksMenu.setToolTipText(Bundle.getMessage("CheckUnBlockedTracksMenuToolTip")); 127 checkUnBlockedTracksMenu.add(checkInProgressMenuItem); 128 checkMenu.add(checkUnBlockedTracksMenu); 129 130 checkUnBlockedTracksMenu.addMenuListener(new MenuListener() { 131 @Override 132 public void menuSelected(@Nonnull MenuEvent menuEvent) { 133 log.debug("menuSelected"); 134 setupCheckUnBlockedTracksMenu(); 135 } 136 137 @Override 138 public void menuDeselected(@Nonnull MenuEvent menuEvent) { 139 log.debug("menuDeselected"); 140 //nothing to see here... move along... 141 } 142 143 @Override 144 public void menuCanceled(@Nonnull MenuEvent menuEvent) { 145 log.debug("menuCanceled"); 146 //nothing to see here... move along... 147 } 148 }); 149 150 // 151 // check for non-contiguous blocks 152 // 153 checkNonContiguousBlocksMenu.setToolTipText(Bundle.getMessage("CheckNonContiguousBlocksMenuToolTip")); 154 checkNonContiguousBlocksMenu.add(checkInProgressMenuItem); 155 checkMenu.add(checkNonContiguousBlocksMenu); 156 157 checkNonContiguousBlocksMenu.addMenuListener(new MenuListener() { 158 @Override 159 public void menuSelected(@Nonnull MenuEvent menuEvent) { 160 log.debug("menuSelected"); 161 setupCheckNonContiguousBlocksMenu(); 162 } 163 164 @Override 165 public void menuDeselected(@Nonnull MenuEvent menuEvent) { 166 log.debug("menuDeselected"); 167 //nothing to see here... move along... 168 } 169 170 @Override 171 public void menuCanceled(@Nonnull MenuEvent menuEvent) { 172 log.debug("menuCanceled"); 173 //nothing to see here... move along... 174 } 175 }); 176 177 // 178 // Check for Unnecessary Anchors 179 // 180 checkUnnecessaryAnchorsMenu.setToolTipText(Bundle.getMessage("CheckUnnecessaryAnchorsMenuToolTip")); 181 checkUnnecessaryAnchorsMenu.add(checkInProgressMenuItem); 182 checkMenu.add(checkUnnecessaryAnchorsMenu); 183 184 checkUnnecessaryAnchorsMenu.addMenuListener(new MenuListener() { 185 @Override 186 public void menuSelected(@Nonnull MenuEvent menuEvent) { 187 log.debug("menuSelected"); 188 setupCheckUnnecessaryAnchorsMenu(); 189 } 190 191 @Override 192 public void menuDeselected(@Nonnull MenuEvent menuEvent) { 193 log.debug("menuDeselected"); 194 //nothing to see here... move along... 195 } 196 197 @Override 198 public void menuCanceled(@Nonnull MenuEvent menuEvent) { 199 log.debug("menuCanceled"); 200 //nothing to see here... move along... 201 } 202 }); 203 204 // 205 // Check for linear bezier track segments 206 // 207 checkLinearBezierTrackSegmentsMenu.setToolTipText(Bundle.getMessage("CheckLinearBezierTrackSegmentsMenuToolTip")); 208 checkLinearBezierTrackSegmentsMenu.add(checkInProgressMenuItem); 209 checkMenu.add(checkLinearBezierTrackSegmentsMenu); 210 211 checkLinearBezierTrackSegmentsMenu.addMenuListener(new MenuListener() { 212 @Override 213 public void menuSelected(@Nonnull MenuEvent menuEvent) { 214 log.debug("menuSelected"); 215 setupCheckLinearBezierTrackSegmentsMenu(); 216 } 217 218 @Override 219 public void menuDeselected(@Nonnull MenuEvent menuEvent) { 220 log.debug("menuDeselected"); 221 //nothing to see here... move along... 222 } 223 224 @Override 225 public void menuCanceled(@Nonnull MenuEvent menuEvent) { 226 log.debug("menuCanceled"); 227 //nothing to see here... move along... 228 } 229 }); 230 231 // 232 // Check for fixed radius bezier track segments (circle arcs) 233 // 234 checkFixedRadiusBezierTrackSegmentsMenu.setToolTipText(Bundle.getMessage("CheckFixedRadiusBezierTrackSegmentsMenuToolTip")); 235 checkFixedRadiusBezierTrackSegmentsMenu.add(checkInProgressMenuItem); 236 checkMenu.add(checkFixedRadiusBezierTrackSegmentsMenu); 237 238 checkFixedRadiusBezierTrackSegmentsMenu.addMenuListener(new MenuListener() { 239 @Override 240 public void menuSelected(@Nonnull MenuEvent menuEvent) { 241 log.debug("menuSelected"); 242 setupCheckFixedRadiusBezierTrackSegmentsMenu(); 243 } 244 245 @Override 246 public void menuDeselected(@Nonnull MenuEvent menuEvent) { 247 log.debug("menuDeselected"); 248 //nothing to see here... move along... 249 } 250 251 @Override 252 public void menuCanceled(@Nonnull MenuEvent menuEvent) { 253 log.debug("menuCanceled"); 254 //nothing to see here... move along... 255 } 256 }); 257 } 258 259 // 260 // run the un-connected tracks check and populate the checkUnConnectedTracksMenu 261 // 262 private void setupCheckUnConnectedTracksMenu() { 263 log.debug("setupcheckUnConnectedTracksMenu"); 264 265 // collect the names of all menu items with checkmarks 266 Set<String> checkMarkedMenuItemNamesSet = getCheckMarkedMenuItemNames(checkUnConnectedTracksMenu); 267 268 // mark our menu as "in progress..." 269 checkUnConnectedTracksMenu.removeAll(); 270 checkUnConnectedTracksMenu.add(checkInProgressMenuItem); 271 272 // check all tracks for free connections 273 List<String> trackNames = new ArrayList<>(); 274 for (LayoutTrack layoutTrack : layoutEditor.getLayoutTracks()) { 275 List<HitPointType> connections = layoutTrack.checkForFreeConnections(); 276 if (!connections.isEmpty()) { 277 // add this track's name to the list of track names 278 trackNames.add(layoutTrack.getName()); 279 } 280 } 281 282 // clear the "in progress..." menu item 283 checkUnConnectedTracksMenu.removeAll(); 284 285 // for each un-connected track we found... 286 if (trackNames.size() > 0) { 287 for (String trackName : trackNames) { 288 // create a menu item for it 289 JCheckBoxMenuItem jmi = new JCheckBoxMenuItem(trackName); 290 checkUnConnectedTracksMenu.add(jmi); 291 jmi.addActionListener((ActionEvent event) -> doCheckUnConnectedTracksMenuItem(trackName)); 292 293 // if it's in the check marked set then (re-)checkmark it 294 if (checkMarkedMenuItemNamesSet.contains(trackName)) { 295 jmi.setSelected(true); 296 } 297 } 298 } else { 299 checkUnConnectedTracksMenu.add(checkNoResultsMenuItem); 300 } 301 } // setupCheckUnConnectedTracksMenu 302 303 // 304 // action to be performed when checkUnConnectedTracksMenu item is clicked 305 // 306 private void doCheckUnConnectedTracksMenuItem(@Nonnull String menuItemName) { 307 log.debug("docheckUnConnectedTracksMenuItem({})", menuItemName); 308 LayoutTrack layoutTrack = layoutEditor.getFinder().findObjectByName(menuItemName); 309 if (layoutTrack != null) { 310 LayoutTrackView layoutTrackView = layoutEditor.getLayoutTrackView(layoutTrack); 311 Rectangle2D trackBounds = layoutTrackView.getBounds(); 312 layoutEditor.setSelectionRect(trackBounds); 313 314 // setSelectionRect calls createSelectionGroups... 315 // so we have to clear it before amending to it 316 layoutEditor.clearSelectionGroups(); 317 layoutEditor.amendSelectionGroup(layoutTrack); 318 } else { 319 layoutEditor.clearSelectionGroups(); 320 } 321 } // doCheckUnConnectedTracksMenuItem 322 323 // 324 // run the un-blocked tracks check and populate the checkUnBlockedTracksMenu 325 // 326 private void setupCheckUnBlockedTracksMenu() { 327 log.debug("setupCheckUnBlockedTracksMenu"); 328 329 // collect the names of all menu items with checkmarks 330 Set<String> checkMarkedMenuItemNamesSet = getCheckMarkedMenuItemNames(checkUnBlockedTracksMenu); 331 332 // mark our menu as "in progress..." 333 checkUnBlockedTracksMenu.removeAll(); 334 checkUnBlockedTracksMenu.add(checkInProgressMenuItem); 335 336 // check all tracks for un-assigned blocks 337 List<String> trackNames = new ArrayList<>(); 338 for (LayoutTrack layoutTrack : layoutEditor.getLayoutTracks()) { 339 if (!layoutTrack.checkForUnAssignedBlocks()) { 340 // add this track to the list of un-assigned track names 341 trackNames.add(layoutTrack.getName()); 342 } 343 } 344 345 // clear the "in progress..." menu item 346 checkUnBlockedTracksMenu.removeAll(); 347 348 // for each tracks with un-assigned blocks that we found... 349 if (trackNames.size() > 0) { 350 for (String trackName : trackNames) { 351 // create a menu item for it 352 JCheckBoxMenuItem jmi = new JCheckBoxMenuItem(trackName); 353 checkUnBlockedTracksMenu.add(jmi); 354 jmi.addActionListener((ActionEvent event) -> doCheckUnBlockedTracksMenuItem(trackName)); 355 356 // if it's in the check marked set then (re-)checkmark it 357 if (checkMarkedMenuItemNamesSet.contains(trackName)) { 358 jmi.setSelected(true); 359 } 360 } 361 } else { 362 checkUnBlockedTracksMenu.add(checkNoResultsMenuItem); 363 } 364 } // setupCheckUnBlockedTracksMenu 365 366 // 367 // action to be performed when checkUnBlockedTracksMenuItem is clicked 368 // 369 private void doCheckUnBlockedTracksMenuItem(@Nonnull String menuItemName) { 370 log.debug("doCheckUnBlockedTracksMenuItem({})", menuItemName); 371 372 LayoutTrack layoutTrack = layoutEditor.getFinder().findObjectByName(menuItemName); 373 if (layoutTrack != null) { 374 LayoutTrackView layoutTrackView = layoutEditor.getLayoutTrackView(layoutTrack); 375 layoutEditor.setSelectionRect(layoutTrackView.getBounds()); 376 // setSelectionRect calls createSelectionGroups... 377 // so we have to clear it before amending to it 378 layoutEditor.clearSelectionGroups(); 379 layoutEditor.amendSelectionGroup(layoutTrack); 380 381 // temporary call, should be replaced by access through View class 382 jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTrackEditor.makeTrackEditor(layoutTrack, layoutEditor); 383 } else { 384 layoutEditor.clearSelectionGroups(); 385 } 386 } // doCheckUnBlockedTracksMenuItem 387 388 // 389 // run the non-contiguous blocks check and populate the checkNonContiguousBlocksMenu 390 // 391 private void setupCheckNonContiguousBlocksMenu() { 392 log.debug("setupCheckNonContiguousBlocksMenu"); 393 394 // mark our menu as "in progress..." 395 checkNonContiguousBlocksMenu.removeAll(); 396 checkNonContiguousBlocksMenu.add(checkInProgressMenuItem); 397 398 // collect all contiguous blocks 399 HashMap<String, List<Set<String>>> blockNamesToTrackNameSetMaps = new HashMap<>(); 400 for (LayoutTrack layoutTrack : layoutEditor.getLayoutTracks()) { 401 layoutTrack.checkForNonContiguousBlocks(blockNamesToTrackNameSetMaps); 402 } 403 404 // clear the "in progress..." menu item 405 checkNonContiguousBlocksMenu.removeAll(); 406 407 // for each bad block we found... 408 for (Map.Entry<String, List<Set<String>>> entry : blockNamesToTrackNameSetMaps.entrySet()) { 409 String blockName = entry.getKey(); 410 List<Set<String>> trackNameSets = entry.getValue(); 411 if (trackNameSets.size() > 1) { 412 JMenu jmi = new JMenu(blockName); 413 checkNonContiguousBlocksMenu.add(jmi); 414 415 int idx = 1; 416 for (Set<String> trackNameSet : trackNameSets) { 417 JMenuItem subMenuItem = new JMenuItem( 418 Bundle.getMessage("MakeLabel", blockName) + "#" + (idx++)); 419 jmi.add(subMenuItem); 420 subMenuItem.addActionListener((ActionEvent event) -> doCheckNonContiguousBlocksMenuItem(blockName, trackNameSet)); 421 } 422 } 423 } 424 // if we didn't find any... 425 if (checkNonContiguousBlocksMenu.getMenuComponentCount() == 0) { 426 checkNonContiguousBlocksMenu.add(checkNoResultsMenuItem); 427 } 428 } // setupCheckNonContiguousBlocksMenu 429 430 // 431// action to be performed when checkNonContiguousBlocksMenu item is clicked 432 // 433 private void doCheckNonContiguousBlocksMenuItem( 434 @Nonnull String blockName, 435 @CheckForNull Set<String> trackNameSet) { 436 log.debug("doCheckNonContiguousBlocksMenuItem({})", blockName); 437 438 if (trackNameSet != null) { 439 // collect all the bounds... 440 Rectangle2D bounds = null; 441 for (LayoutTrack layoutTrack : layoutEditor.getLayoutTracks()) { 442 if (trackNameSet.contains(layoutTrack.getName())) { 443 LayoutTrackView layoutTrackView = layoutEditor.getLayoutTrackView(layoutTrack); 444 Rectangle2D trackBounds = layoutTrackView.getBounds(); 445 if (bounds == null) { 446 bounds = trackBounds.getBounds2D(); 447 } else { 448 bounds.add(trackBounds); 449 } 450 } 451 } 452 layoutEditor.setSelectionRect(bounds); 453 454 // setSelectionRect calls createSelectionGroups... 455 // so we have to clear it before amending to it 456 layoutEditor.clearSelectionGroups(); 457 458 // amend all tracks in this block to the layout editor selection group 459 for (LayoutTrack layoutTrack : layoutEditor.getLayoutTracks()) { 460 if (trackNameSet.contains(layoutTrack.getName())) { 461 layoutEditor.amendSelectionGroup(layoutTrack); 462 } 463 } 464 } else { 465 layoutEditor.setSelectionRect(MathUtil.zeroRectangle2D); 466 } 467 } // doCheckNonContiguousBlocksMenuItem 468 469 // 470 // run the Unnecessary Anchors check and 471 // populate the CheckUnnecessaryAnchorsMenu 472 // 473 private void setupCheckUnnecessaryAnchorsMenu() { 474 log.debug("setupCheckUnnecessaryAnchorsMenu"); 475 476 // collect the names of all menu items with checkmarks 477 Set<String> checkMarkedMenuItemNamesSet = getCheckMarkedMenuItemNames(checkUnBlockedTracksMenu); 478 // mark our menu as "in progress..." 479 checkUnnecessaryAnchorsMenu.removeAll(); 480 checkUnnecessaryAnchorsMenu.add(checkInProgressMenuItem); 481 482 // check all PositionablePoints 483 List<PositionablePoint> aatzlts = new ArrayList<>(); 484 for (PositionablePoint pp : layoutEditor.getPositionablePoints()) { 485 // has to be an anchor... 486 if (pp.getType() == PositionablePoint.PointType.ANCHOR) { 487 // adjacent track segments must be defined... 488 TrackSegment ts1 = pp.getConnect1(); 489 TrackSegment ts2 = pp.getConnect2(); 490 if ((ts1 != null) && (ts2 != null)) { 491 TrackSegmentView ts1v = layoutEditor.getTrackSegmentView(ts1); 492 TrackSegmentView ts2v = layoutEditor.getTrackSegmentView(ts2); 493 // can't be an arc, circle or bezier... 494 if (!ts1v.isArc() && !ts1v.isCircle() && !ts1v.isBezier() 495 && !ts2v.isArc() && !ts2v.isCircle() && !ts2v.isBezier()) { 496 // must be in same block... 497 String blockName1 = ts1.getBlockName(); 498 String blockName2 = ts2.getBlockName(); 499 if (blockName1.equals(blockName2)) { 500 // if length of ts1v is zero... 501 Rectangle2D bounds1 = ts1v.getBounds(); 502 double length1 = Math.hypot(bounds1.getWidth(), bounds1.getHeight()); 503 if (length1 < 1.0) { 504 aatzlts.add(pp); 505 continue; // so we don't get added again 506 } 507 // if length of ts2v is zero... 508 Rectangle2D bounds = ts2v.getBounds(); 509 double length = Math.hypot(bounds.getWidth(), bounds.getHeight()); 510 if (length < 1.0) { 511 aatzlts.add(pp); 512 continue; // so we don't get added again 513 } 514 // if track segments isMainline's don't match 515 if (ts1.isMainline() != ts2.isMainline()) { 516 continue; // skip it 517 } 518 TrackSegmentView tsv1 = layoutEditor.getTrackSegmentView(ts1); 519 TrackSegmentView tsv2 = layoutEditor.getTrackSegmentView(ts2); 520 // if track segments isHidden's don't match 521 if (tsv1.isHidden() != tsv2.isHidden()) { 522 continue; // skip it 523 } 524 // if either track segment has decorations 525 if (tsv1.hasDecorations() || tsv2.hasDecorations()) { 526 continue; // skip it 527 } 528 // if adjacent tracks are collinear... 529 double dir1 = tsv1.getDirectionRAD(); 530 double dir2 = tsv2.getDirectionRAD(); 531 double diffRAD = MathUtil.absDiffAngleRAD(dir1, dir2); 532 if (MathUtil.equals(diffRAD, 0.0) 533 || MathUtil.equals(diffRAD, Math.PI)) { 534 aatzlts.add(pp); 535 // so we don't get added again 536 } 537 } // if blocknames are equal 538 } // isn't arc, circle or bezier 539 } // isn't null 540 } // is anchor 541 } // for pp 542 543 // clear the "in progress..." menu item 544 checkUnnecessaryAnchorsMenu.removeAll(); 545 546 // for each anchor we found 547 for (PositionablePoint pp : aatzlts) { 548 String anchorName = pp.getName(); 549 JMenuItem jmi = new JMenuItem(anchorName); 550 checkUnnecessaryAnchorsMenu.add(jmi); 551 jmi.addActionListener((ActionEvent event) -> doCheckUnnecessaryAnchorsMenuItem(anchorName)); 552 553 // if it's in the check marked set then (re-)checkmark it 554 if (checkMarkedMenuItemNamesSet.contains(anchorName)) { 555 jmi.setSelected(true); 556 } 557 } 558 // if we didn't find any... 559 if (checkUnnecessaryAnchorsMenu.getMenuComponentCount() == 0) { 560 checkUnnecessaryAnchorsMenu.add(checkNoResultsMenuItem); 561 } 562 } // setupCheckUnnecessaryAnchorsMenu 563 564 // 565 // action to be performed when CheckUnnecessaryAnchorsMenu item is clicked 566 // 567 private void doCheckUnnecessaryAnchorsMenuItem( 568 @Nonnull String anchorName) { 569 log.debug("doCheckUnnecessaryAnchorsMenuItem({})", anchorName); 570 571 LayoutTrack layoutTrack = layoutEditor.getFinder().findObjectByName(anchorName); 572 573 if (layoutTrack != null) { 574 LayoutTrackView layoutTrackView = layoutEditor.getLayoutTrackView(layoutTrack); 575 layoutEditor.setSelectionRect(layoutTrackView.getBounds()); 576 // setSelectionRect calls createSelectionGroups... 577 // so we have to clear it before amending to it 578 layoutEditor.clearSelectionGroups(); 579 layoutEditor.amendSelectionGroup(layoutTrack); 580 // show its popup menu 581 layoutTrackView.showPopup(); 582 } else { 583 layoutEditor.clearSelectionGroups(); 584 } 585 } // doCheckUnnecessaryAnchorsMenuItem 586 587 // 588 // run the linear bezier track segments check and 589 // populate the checkLinearBezierTrackSegmentsMenu 590 // 591 private void setupCheckLinearBezierTrackSegmentsMenu() { 592 log.debug("setupCheckLinearBezierTrackSegmentsMenu"); 593 594 // collect the names of all menu items with checkmarks 595 Set<String> checkMarkedMenuItemNamesSet = getCheckMarkedMenuItemNames(checkLinearBezierTrackSegmentsMenu); 596 // mark our menu as "in progress..." 597 checkLinearBezierTrackSegmentsMenu.removeAll(); 598 checkLinearBezierTrackSegmentsMenu.add(checkInProgressMenuItem); 599 600 // check all TrackSegments 601 List<TrackSegmentView> linearBezierTrackSegmentViews = new ArrayList<>(); 602 for (TrackSegmentView tsv : layoutEditor.getTrackSegmentViews()) { 603 // has to be a bezier 604 if (tsv.isBezier()) { 605 // adjacent connections must be defined... 606 LayoutTrack c1 = tsv.getConnect1(); 607 LayoutTrack c2 = tsv.getConnect2(); 608 if ((c1 != null) && (c2 != null)) { 609 // if length is zero... 610 Point2D end1 = layoutEditor.getCoords(tsv.getConnect1(), tsv.getType1()); 611 Point2D end2 = layoutEditor.getCoords(tsv.getConnect2(), tsv.getType2()); 612 if (MathUtil.distance(end1, end2) <= 4.0) { 613 linearBezierTrackSegmentViews.add(tsv); 614 continue; // so we don't get added again 615 } 616 // if control points are collinear... 617 boolean good = true; 618 for (Point2D cp : tsv.getBezierControlPoints()) { 619 if (Math.abs(MathUtil.distance(end1, end2, cp)) > 1.0) { 620 good = false; 621 break; 622 } 623 } 624 if (good) { 625 linearBezierTrackSegmentViews.add(tsv); 626 } 627 } // c1 & c2 aren't null 628 } // is bezier 629 } // for ts 630 631 // clear the "in progress..." menu item 632 checkLinearBezierTrackSegmentsMenu.removeAll(); 633 // if we didn't find any... 634 if (linearBezierTrackSegmentViews.size() == 0) { 635 checkLinearBezierTrackSegmentsMenu.add(checkNoResultsMenuItem); 636 } else { 637 // for each linear bezier track segment we found 638 for (TrackSegmentView tsv : linearBezierTrackSegmentViews) { 639 String name = tsv.getName(); 640 JMenuItem jmi = new JMenuItem(name); 641 checkLinearBezierTrackSegmentsMenu.add(jmi); 642 jmi.addActionListener((ActionEvent event) -> doCheckLinearBezierTrackSegmentsMenuItem(name)); 643 644 // if it's in the check marked set then (re-)checkmark it 645 if (checkMarkedMenuItemNamesSet.contains(name)) { 646 jmi.setSelected(true); 647 } 648 } 649 } //count == 0 650 } // setupCheckLinearBezierTrackSegmentsMenu 651 652 // 653 // action to be performed when checkLinearBezierTrackSegmentsMenu item is clicked 654 // 655 private void doCheckLinearBezierTrackSegmentsMenuItem( 656 @Nonnull String trackSegmentName) { 657 log.debug("doCheckLinearBezierTrackSegmentsMenuItem({})", trackSegmentName); 658 659 LayoutTrack layoutTrack = layoutEditor.getFinder().findObjectByName(trackSegmentName); 660 661 if (layoutTrack != null) { 662 LayoutTrackView layoutTrackView = layoutEditor.getLayoutTrackView(layoutTrack); 663 layoutEditor.setSelectionRect(layoutTrackView.getBounds()); 664 // setSelectionRect calls createSelectionGroups... 665 // so we have to clear it before amending to it 666 layoutEditor.clearSelectionGroups(); 667 layoutEditor.amendSelectionGroup(layoutTrack); 668 // show its popup menu 669 layoutTrackView.showPopup(); 670 } else { 671 layoutEditor.clearSelectionGroups(); 672 } 673 } // doCheckLinearBezierTrackSegmentsMenuItem 674 675 // 676 // run the linear bezier track segments check and 677 // populate the checkFixedRadiusBezierTrackSegmentsMenu 678 // 679 private void setupCheckFixedRadiusBezierTrackSegmentsMenu() { 680 log.debug("setupCheckFixedRadiusBezierTrackSegmentsMenu"); 681 682 // collect the names of all menu items with checkmarks 683 Set<String> checkMarkedMenuItemNamesSet = getCheckMarkedMenuItemNames(checkFixedRadiusBezierTrackSegmentsMenu); 684 // mark our menu as "in progress..." 685 checkFixedRadiusBezierTrackSegmentsMenu.removeAll(); 686 checkFixedRadiusBezierTrackSegmentsMenu.add(checkInProgressMenuItem); 687 688 // check all TrackSegments 689 List<TrackSegmentView> radiusBezierTrackSegmentViews = new ArrayList<>(); 690 for (TrackSegmentView tsv : layoutEditor.getTrackSegmentViews()) { 691 // has to be a bezier 692 if (tsv.isBezier()) { 693 // adjacent connections must be defined... 694 LayoutTrack c1 = tsv.getConnect1(); 695 LayoutTrack c2 = tsv.getConnect2(); 696 if ((c1 != null) && (c2 != null)) { 697 Point2D end1 = layoutEditor.getCoords(c1, tsv.getType1()); 698 Point2D end2 = layoutEditor.getCoords(c2, tsv.getType2()); 699 double chordLength = MathUtil.distance(end1, end2); 700 if (chordLength <= 4.0) { 701 continue; //skip short segments 702 } 703 704 //get first and last control points 705 int cnt = tsv.getNumberOfBezierControlPoints(); 706 if (cnt > 0) { 707 Point2D cp0 = tsv.getBezierControlPoint(0); 708 Point2D cpN = tsv.getBezierControlPoint(cnt - 1); 709 //calculate orthoginal points 710 Point2D op1 = MathUtil.add(end1, MathUtil.orthogonal(MathUtil.subtract(cp0, end1))); 711 Point2D op2 = MathUtil.subtract(end2, MathUtil.orthogonal(MathUtil.subtract(cpN, end2))); 712 //use them to find center point 713 Point2D ip = MathUtil.intersect(end1, op1, end2, op2); 714 if (ip != null) { //single intersection point found 715 double r1 = MathUtil.distance(ip, end1); 716 double r2 = MathUtil.distance(ip, end2); 717 if (Math.abs(r1 - r2) <= 1.0) { 718 // the sign of the distance tells what side of the line the center point is on 719 double ipSide = Math.signum(MathUtil.distance(end1, end2, ip)); 720 721 // if all control midpoints are equal distance from intersection point 722 boolean good = true; //assume success (optimist!) 723 724 for (int idx = 0; idx < cnt - 1; idx++) { 725 Point2D cp1 = tsv.getBezierControlPoint(idx); 726 Point2D cp2 = tsv.getBezierControlPoint(idx + 1); 727 Point2D mp = MathUtil.midPoint(cp1, cp2); 728 double rM = MathUtil.distance(ip, mp); 729 if (Math.abs(r1 - rM) > 1.0) { 730 good = false; 731 break; 732 } 733 // the sign of the distance tells what side of line the midpoint is on 734 double cpSide = Math.signum(MathUtil.distance(end1, end2, mp)); 735 if (MathUtil.equals(ipSide, cpSide)) { 736 //can't be on same side as center point (if so then not circular) 737 good = false; 738 break; 739 } 740 } 741 if (good) { 742 radiusBezierTrackSegmentViews.add(tsv); 743 } 744 } 745 } 746 } 747 } // c1 & c2 aren't null 748 } // is bezier 749 } // for ts 750 751 // clear the "in progress..." menu item 752 checkFixedRadiusBezierTrackSegmentsMenu.removeAll(); 753 // if we didn't find any... 754 if (radiusBezierTrackSegmentViews.size() == 0) { 755 checkFixedRadiusBezierTrackSegmentsMenu.add(checkNoResultsMenuItem); 756 } else { 757 // for each radius bezier track segment we found 758 for (TrackSegmentView tsv : radiusBezierTrackSegmentViews) { 759 String name = tsv.getName(); 760 JMenuItem jmi = new JMenuItem(name); 761 checkFixedRadiusBezierTrackSegmentsMenu.add(jmi); 762 jmi.addActionListener((ActionEvent event) -> doCheckFixedRadiusBezierTrackSegmentsMenuItem(name)); 763 764 // if it's in the check marked set then (re-)checkmark it 765 if (checkMarkedMenuItemNamesSet.contains(name)) { 766 jmi.setSelected(true); 767 } 768 } 769 } //count == 0 770 } // setupCheckFixedRadiusBezierTrackSegmentsMenu 771 772 // 773 // action to be performed when checkFixedRadiusBezierTrackSegmentsMenu item is clicked 774 // 775 private void doCheckFixedRadiusBezierTrackSegmentsMenuItem( 776 @Nonnull String trackSegmentName) { 777 log.debug("doCheckFixedRadiusBezierTrackSegmentsMenuItem({})", trackSegmentName); 778 779 LayoutTrack layoutTrack = layoutEditor.getFinder().findObjectByName(trackSegmentName); 780 781 if (layoutTrack != null) { 782 LayoutTrackView layoutTrackView = layoutEditor.getLayoutTrackView(layoutTrack); 783 layoutEditor.setSelectionRect(layoutTrackView.getBounds()); 784 // setSelectionRect calls createSelectionGroups... 785 // so we have to clear it before amending to it 786 layoutEditor.clearSelectionGroups(); 787 layoutEditor.amendSelectionGroup(layoutTrack); 788 // show its popup menu 789 layoutTrackView.showPopup(); 790 } else { 791 layoutEditor.clearSelectionGroups(); 792 } 793 } // doCheckFixedRadiusBezierTrackSegmentsMenuItem 794 795 // 796 // collect the names of all checkbox menu items with checkmarks 797 // 798 private Set<String> getCheckMarkedMenuItemNames(@Nonnull JMenu menu) { 799 Set<String> results = new HashSet<>(); 800 for (int idx = 0; idx < menu.getMenuComponentCount(); idx++) { 801 Component menuComponent = menu.getMenuComponent(idx); 802 if (menuComponent instanceof JCheckBoxMenuItem) { 803 JCheckBoxMenuItem checkBoxMenuItem = (JCheckBoxMenuItem) menuComponent; 804 if (checkBoxMenuItem.isSelected()) { 805 results.add(checkBoxMenuItem.getText()); 806 } 807 } 808 } 809 return results; 810 } // getCheckMarkedMenuItemNames 811 812 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutEditorChecks.class); 813}