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}