001package jmri.jmrit.display.layoutEditor;
002
003import java.awt.*;
004import java.awt.event.ActionEvent;
005import java.awt.geom.*;
006import static java.lang.Float.POSITIVE_INFINITY;
007import java.text.MessageFormat;
008import java.util.List;
009import java.util.*;
010
011import javax.annotation.CheckForNull;
012import javax.annotation.Nonnull;
013import javax.swing.*;
014
015import jmri.*;
016import jmri.jmrit.display.layoutEditor.LayoutTraverser.SlotTrack;
017import jmri.util.MathUtil;
018import jmri.util.swing.JmriMouseEvent;
019
020/**
021 * MVC View component for the LayoutTraverser class.
022 *
023 * @author Bob Jacobsen  Copyright (c) 2020
024 * @author Dave Sand Copyright (c) 2024
025 */
026public class LayoutTraverserView extends LayoutTrackView {
027
028    // defined constants
029    // operational instance variables (not saved between sessions)
030    private final jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTraverserEditor editor;
031
032    /**
033     * Constructor method.
034     * @param traverser the layout traverser to create view for.
035     * @param c            where to put it
036     * @param layoutEditor what layout editor panel to put it in
037     */
038    public LayoutTraverserView(@Nonnull LayoutTraverser traverser,
039                @Nonnull Point2D c,
040                @Nonnull LayoutEditor layoutEditor) {
041        super(traverser, c, layoutEditor);
042        this.traverser = traverser;
043
044        editor = new jmri.jmrit.display.layoutEditor.LayoutEditorDialogs.LayoutTraverserEditor(layoutEditor);
045    }
046
047    private final LayoutTraverser traverser;
048
049    public final LayoutTraverser getTraverser() { return traverser; }
050
051    /**
052     * Get a string that represents this object. This should only be used for
053     * debugging.
054     *
055     * @return the string
056     */
057    @Override
058    public String toString() {
059        return "LayoutTraverser " + getName();
060    }
061
062    //
063    // Accessor methods
064    //
065    public double getDeckLength() { return traverser.getDeckLength(); }
066    public int getOrientation() { return traverser.getOrientation(); }
067    public void setOrientation(int o) { traverser.setOrientation(o); }
068
069    /**
070     * @return the layout block name
071     */
072    @Nonnull
073    public String getBlockName() {
074        return traverser.getBlockName();
075    }
076
077    /**
078     * @return the layout block
079     */
080    public LayoutBlock getLayoutBlock() {
081        return traverser.getLayoutBlock();
082    }
083
084    /**
085     * Set up a LayoutBlock for this LayoutTraverser.
086     *
087     * @param newLayoutBlock the LayoutBlock to set
088     */
089    public void setLayoutBlock(@CheckForNull LayoutBlock newLayoutBlock) {
090        traverser.setLayoutBlock(newLayoutBlock);
091    }
092
093    /**
094     * Set up a LayoutBlock for this LayoutTraverser.
095     *
096     * @param name the name of the new LayoutBlock
097     */
098    public void setLayoutBlockByName(@CheckForNull String name) {
099        traverser.setLayoutBlockByName(name);
100    }
101
102    /*
103     * non-accessor methods
104     */
105    /**
106     * @return the bounds of this traverser.
107     */
108    @Override
109    public Rectangle2D getBounds() {
110        Point2D center = getCoordsCenter();
111        double length = getDeckLength();
112        double width = traverser.getDeckWidth();
113        if (getOrientation() == LayoutTraverser.HORIZONTAL) {
114            return new Rectangle2D.Double(center.getX() - width / 2, center.getY() - length / 2, width, length);
115        } else {
116            return new Rectangle2D.Double(center.getX() - length / 2, center.getY() - width / 2, length, width);
117        }
118    }
119
120    /**
121     * Get the center of the control point, which is on an edge of the traverser.
122     * @return The center point for the control circles.
123     */
124    private Point2D getControlPointCenter() {
125        Point2D center = getCoordsCenter();
126        Rectangle2D bounds = getBounds();
127        if (getOrientation() == LayoutTraverser.HORIZONTAL) {
128            // Top edge for a horizontal (tall) traverser
129            return new Point2D.Double(center.getX(), bounds.getMinY());
130        } else {
131            // Left edge for a vertical (wide) traverser
132            return new Point2D.Double(bounds.getMinX(), center.getY());
133        }
134    }
135    public TrackSegment getSlotConnectIndexed(int index) {
136        return traverser.getSlotConnectIndexed(index);
137    }
138
139    public TrackSegment getSlotConnectOrdered(int i) {
140        return traverser.getSlotConnectOrdered(i);
141    }
142
143    public void setSlotConnect(TrackSegment ts, int index) {
144        traverser.setSlotConnect(ts, index);
145    }
146
147    public List<SlotTrack> getSlotList() {
148        return traverser.getSlotList();
149    }
150
151    public int getNumberSlots() {
152        return traverser.getNumberSlots();
153    }
154
155    public int getSlotIndex(int i) {
156        return traverser.getSlotIndex(i);
157    }
158
159    public double getSlotOffsetValue(int i) {
160        return traverser.getSlotOffsetValue(i);
161    }
162
163    public void setSlotTurnout(int index, String turnoutName, int state) {
164        traverser.setSlotTurnout(index, turnoutName, state);
165    }
166
167    public String getSlotTurnoutName(int i) {
168        return traverser.getSlotTurnoutName(i);
169    }
170
171    public Turnout getSlotTurnout(int i) {
172        return traverser.getSlotTurnout(i);
173    }
174
175    public int getSlotTurnoutState(int i) {
176        return traverser.getSlotTurnoutState(i);
177    }
178
179    public boolean isSlotDisabled(int i) {
180        return traverser.isSlotDisabled(i);
181    }
182
183    public void setSlotDisabled(int i, boolean boo) {
184        traverser.setSlotDisabled(i, boo);
185    }
186
187    public boolean isSlotDisabledWhenOccupied(int i) {
188        return traverser.isSlotDisabledWhenOccupied(i);
189    }
190
191    public void setSlotDisabledWhenOccupied(int i, boolean boo) {
192        traverser.setSlotDisabledWhenOccupied(i, boo);
193    }
194
195    public Point2D getSlotCoordsIndexed(int index) {
196        Point2D center = getCoordsCenter();
197        double deckWidth = traverser.getDeckWidth();
198        double anchorOffset = traverser.getSlotOffset() * 0.5;
199        for (int i=0; i<traverser.slotList.size(); i++) {
200            SlotTrack st = traverser.slotList.get(i);
201            if (st.getConnectionIndex() == index) {
202                double offset = st.getOffset();
203                boolean sideA = (i % 2 == 0);
204                if (getOrientation() == LayoutTraverser.HORIZONTAL) { // Tall
205                    double y = center.getY() + offset;
206                    double x = center.getX() + (sideA ? (-deckWidth / 2.0) - anchorOffset : (deckWidth / 2.0) + anchorOffset);
207                    return new Point2D.Double(x, y);
208                } else { // Wide
209                    double x = center.getX() + offset;
210                    double y = center.getY() + (sideA ? (-deckWidth / 2.0) - anchorOffset : (deckWidth / 2.0) + anchorOffset);
211                    return new Point2D.Double(x, y);
212                }
213            }
214        }
215        return MathUtil.zeroPoint2D;
216    }
217
218    public Point2D getSlotCoordsOrdered(int i) {
219        if (i < traverser.slotList.size()) {
220            SlotTrack st = traverser.slotList.get(i);
221            if (st != null) {
222                Point2D center = getCoordsCenter();
223                double deckWidth = traverser.getDeckWidth();
224                double anchorOffset = traverser.getSlotOffset() * 0.5;
225                double offset = st.getOffset();
226                boolean sideA = (i % 2 == 0);
227
228                if (getOrientation() == LayoutTraverser.HORIZONTAL) { // Tall
229                    double y = center.getY() + offset;
230                    double x = center.getX() + (sideA ? (-deckWidth / 2.0) - anchorOffset : (deckWidth / 2.0) + anchorOffset);
231                    return new Point2D.Double(x, y);
232                } else { // Wide
233                    double x = center.getX() + offset;
234                    double y = center.getY() + (sideA ? (-deckWidth / 2.0) - anchorOffset : (deckWidth / 2.0) + anchorOffset);
235                    return new Point2D.Double(x, y);
236                }
237            }
238        }
239        return MathUtil.zeroPoint2D;
240    }
241
242    private Point2D getSlotEdgePointOrdered(int i) {
243        if (i < traverser.slotList.size()) {
244            SlotTrack st = traverser.slotList.get(i);
245            if (st != null) {
246                Point2D center = getCoordsCenter();
247                double deckWidth = traverser.getDeckWidth();
248                double offset = st.getOffset();
249                boolean sideA = (i % 2 == 0);
250
251                if (getOrientation() == LayoutTraverser.HORIZONTAL) { // Tall
252                    double y = center.getY() + offset;
253                    double x = center.getX() + (sideA ? -deckWidth / 2.0 : deckWidth / 2.0);
254                    return new Point2D.Double(x, y);
255                } else { // Wide
256                    double x = center.getX() + offset;
257                    double y = center.getY() + (sideA ? -deckWidth / 2.0 : deckWidth / 2.0);
258                    return new Point2D.Double(x, y);
259                }
260            }
261        }
262        return MathUtil.zeroPoint2D;
263    }
264
265    public void setSlotCoordsIndexed(double x, double y, int index) {
266        for (SlotTrack st : traverser.slotList) {
267            if (st.getConnectionIndex() == index) {
268                if (getOrientation() == LayoutTraverser.HORIZONTAL) {
269                    st.setOffset(x - getCoordsCenter().getX());
270                } else {
271                    st.setOffset(y - getCoordsCenter().getY());
272                }
273                break;
274            }
275        }
276    }
277
278    public void setSlotCoordsIndexed(Point2D point, int index) {
279        setSlotCoordsIndexed(point.getX(), point.getY(), index);
280    }
281
282    @Override
283    public Point2D getCoordsForConnectionType(HitPointType connectionType) {
284        Point2D result = getCoordsCenter();
285        if (HitPointType.TRAVERSER_CENTER == connectionType) {
286            // This is correct
287        } else if (HitPointType.isTraverserSlotHitType(connectionType)) {
288            result = getSlotCoordsIndexed(connectionType.traverserTrackIndex());
289        } else {
290            log.error("{}.getCoordsForConnectionType({}); Invalid connection type",
291                    getName(), connectionType); // NOI18N
292        }
293        return result;
294    }
295
296    @Override
297    public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException {
298        if (HitPointType.isTraverserSlotHitType(connectionType)) {
299            return getSlotConnectIndexed(connectionType.traverserTrackIndex());
300        } else {
301            String errString = MessageFormat.format("{0}.getCoordsForConnectionType({1}); Invalid connection type",
302                    getName(), connectionType); // NOI18N
303            log.error("will throw {}", errString); // NOI18N
304            throw new jmri.JmriException(errString);
305        }
306    }
307
308    @Override
309    public void setConnection(HitPointType connectionType, LayoutTrack o, HitPointType type) throws jmri.JmriException {
310        if ((type != HitPointType.TRACK) && (type != HitPointType.NONE)) {
311            String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid type",
312                    getName(), connectionType, (o == null) ? "null" : o.getName(), type); // NOI18N
313            log.error("will throw {}", errString); // NOI18N
314            throw new jmri.JmriException(errString);
315        }
316        if (HitPointType.isTraverserSlotHitType(connectionType)) {
317            if ((o == null) || (o instanceof TrackSegment)) {
318                setSlotConnect((TrackSegment) o, connectionType.traverserTrackIndex());
319            } else {
320                String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid object: {4}",
321                        getName(), connectionType, o.getName(),
322                        type, o.getClass().getName()); // NOI18N
323                log.error("will throw {}", errString); // NOI18N
324                throw new jmri.JmriException(errString);
325            }
326        } else {
327            String errString = MessageFormat.format("{0}.setConnection({1}, {2}, {3}); Invalid connection type",
328                    getName(), connectionType, (o == null) ? "null" : o.getName(), type); // NOI18N
329            log.error("will throw {}", errString); // NOI18N
330            throw new jmri.JmriException(errString);
331        }
332    }
333
334    public boolean isMainlineIndexed(int index) {
335        return traverser.isMainlineIndexed(index);
336    }
337
338    public boolean isMainlineOrdered(int i) {
339        return traverser.isMainlineOrdered(i);
340    }
341
342    @Override
343    public void scaleCoords(double xFactor, double yFactor) {
344        Point2D factor = new Point2D.Double(xFactor, yFactor);
345        super.setCoordsCenter(MathUtil.granulize(MathUtil.multiply(getCoordsCenter(), factor), 1.0));
346        traverser.setSlotOffset(traverser.getSlotOffset() * Math.min(xFactor, yFactor));
347    }
348
349    @Override
350    public void translateCoords(double xFactor, double yFactor) {
351        Point2D factor = new Point2D.Double(xFactor, yFactor);
352        super.setCoordsCenter(MathUtil.add(getCoordsCenter(), factor));
353    }
354
355    @Override
356    public void rotateCoords(double angleDEG) {
357        // For now, we only support 90-degree rotations
358        if (angleDEG == 90 || angleDEG == -270) {
359            setOrientation(1 - getOrientation()); // Toggle between HORIZONTAL and VERTICAL
360        } else if (angleDEG == -90 || angleDEG == 270) {
361            setOrientation(1 - getOrientation()); // Toggle between HORIZONTAL and VERTICAL
362        }
363    }
364
365    @Override
366    protected HitPointType findHitPointType(Point2D hitPoint, boolean useRectangles, boolean requireUnconnected) {
367        HitPointType result = HitPointType.NONE;  // assume point not on connection
368        Point2D p;
369//        Point2D minPoint = MathUtil.zeroPoint2D;
370
371        double circleRadius = LayoutEditor.SIZE * layoutEditor.getTurnoutCircleSize();
372        double slotControlRadius = traverser.getSlotOffset() * 0.25;
373        double distance, minDistance = POSITIVE_INFINITY;
374
375        // check the center point
376        if (!requireUnconnected) {
377            p = getControlPointCenter();
378            distance = MathUtil.distance(p, hitPoint);
379            if (distance < minDistance) {
380                minDistance = distance;
381//                minPoint = p;
382                result = HitPointType.TRAVERSER_CENTER;
383            }
384        }
385
386        for (int k = 0; k < getNumberSlots(); k++) {
387            if (!isSlotDisabled(k) && (!requireUnconnected || (getSlotConnectOrdered(k) == null))) {
388                p = getSlotCoordsOrdered(k);
389                distance = MathUtil.distance(p, hitPoint);
390                if (distance < minDistance) {
391                    minDistance = distance;
392//                    minPoint = p;
393                    result = HitPointType.traverserTrackIndexedValue(getSlotIndex(k));
394                }
395            }
396        }
397
398        if (result == HitPointType.TRAVERSER_CENTER) {
399            if (minDistance > circleRadius) {
400                result = HitPointType.NONE;
401            }
402        } else if (HitPointType.isTraverserSlotHitType(result)) {
403            if (minDistance > slotControlRadius) {
404                result = HitPointType.NONE;
405            }
406        } else {
407            result = HitPointType.NONE;
408        }
409        return result;
410    }
411
412    public String tLayoutBlockName = "";
413
414    public boolean isTurnoutControlled() {
415        return traverser.isTurnoutControlled();
416    }
417
418    public void setTurnoutControlled(boolean boo) {
419        traverser.setTurnoutControlled(boo);
420    }
421
422    private JPopupMenu popupMenu = null;
423
424    @Override
425    @Nonnull
426    protected JPopupMenu showPopup(@Nonnull JmriMouseEvent mouseEvent) {
427        if (popupMenu != null) {
428            popupMenu.removeAll();
429        } else {
430            popupMenu = new JPopupMenu();
431        }
432
433        JMenuItem jmi = popupMenu.add(Bundle.getMessage("MakeLabel", traverser.getTypeName()) + getName());
434        jmi.setEnabled(false);
435
436        LayoutBlock lb = getLayoutBlock();
437        if (lb == null) {
438            jmi = popupMenu.add(Bundle.getMessage("NoBlock"));
439        } else {
440            String displayName = lb.getDisplayName();
441            jmi = popupMenu.add(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameBlock")) + displayName);
442        }
443        jmi.setEnabled(false);
444
445        if (!traverser.slotList.isEmpty()) {
446            JMenu connectionsMenu = new JMenu(Bundle.getMessage("Connections"));
447            traverser.slotList.forEach((rt) -> {
448                TrackSegment ts = rt.getConnect();
449                if (ts != null) {
450                    TrackSegmentView tsv = layoutEditor.getTrackSegmentView(ts);
451                    connectionsMenu.add(new AbstractAction(Bundle.getMessage("MakeLabel", "" + rt.getConnectionIndex()) + ts.getName()) {
452                        @Override
453                        public void actionPerformed(ActionEvent e) {
454                            layoutEditor.setSelectionRect(tsv.getBounds());
455                            tsv.showPopup();
456                        }
457                    });
458                }
459            });
460            popupMenu.add(connectionsMenu);
461        }
462
463        popupMenu.add(new JSeparator(JSeparator.HORIZONTAL));
464
465        popupMenu.add(new AbstractAction(Bundle.getMessage("ButtonEdit")) {
466            @Override
467            public void actionPerformed(ActionEvent e) {
468                editor.editLayoutTrack(LayoutTraverserView.this);
469            }
470        });
471        popupMenu.add(new AbstractAction(Bundle.getMessage("ButtonDelete")) {
472            @Override
473            public void actionPerformed(ActionEvent e) {
474                if (removeInlineLogixNG() && layoutEditor.removeTraverser(traverser)) {
475                    remove();
476                    dispose();
477                }
478            }
479        });
480        layoutEditor.setShowAlignmentMenu(popupMenu);
481        addCommonPopupItems(mouseEvent, popupMenu);
482        popupMenu.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
483        return popupMenu;
484    }
485
486    private JPopupMenu slotPopup = null;
487
488    protected void showSlotPopUp(JmriMouseEvent e, int index) {
489        if (slotPopup != null) {
490            slotPopup.removeAll();
491        } else {
492            slotPopup = new JPopupMenu();
493        }
494
495        for (SlotTrack rt : traverser.slotList) {
496            if (rt.getConnectionIndex() == index) {
497                JMenuItem jmi = slotPopup.add("Traverser Slot " + index);
498                jmi.setEnabled(false);
499
500                slotPopup.add(new AbstractAction(
501                        Bundle.getMessage("MakeLabel",
502                                Bundle.getMessage("Connected"))
503                        + rt.getConnect().getName()) {
504                    @Override
505                    public void actionPerformed(ActionEvent e) {
506                        LayoutEditorFindItems lf = layoutEditor.getFinder();
507                        LayoutTrack lt = lf.findObjectByName(rt.getConnect().getName());
508
509                        if (lt != null) {
510                            LayoutTrackView ltv = layoutEditor.getLayoutTrackView(lt);
511                            layoutEditor.setSelectionRect(ltv.getBounds());
512                            ltv.showPopup();
513                        }
514                    }
515                });
516
517                if (rt.getTurnout() != null) {
518                    String info = rt.getTurnout().getDisplayName();
519                    String stateString = getTurnoutStateString(rt.getTurnoutState());
520                    if (!stateString.isEmpty()) {
521                        info += " (" + stateString + ")";
522                    }
523                    jmi = slotPopup.add(info);
524                    jmi.setEnabled(false);
525
526                    slotPopup.add(new JSeparator(JSeparator.HORIZONTAL));
527
528                    JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(Bundle.getMessage("Disabled"));
529                    cbmi.setSelected(rt.isDisabled());
530                    slotPopup.add(cbmi);
531                    cbmi.addActionListener((java.awt.event.ActionEvent e2) -> {
532                        JCheckBoxMenuItem o = (JCheckBoxMenuItem) e2.getSource();
533                        rt.setDisabled(o.isSelected());
534                    });
535
536                    cbmi = new JCheckBoxMenuItem(Bundle.getMessage("DisabledWhenOccupied"));
537                    cbmi.setSelected(rt.isDisabledWhenOccupied());
538                    slotPopup.add(cbmi);
539                    cbmi.addActionListener((java.awt.event.ActionEvent e3) -> {
540                        JCheckBoxMenuItem o = (JCheckBoxMenuItem) e3.getSource();
541                        rt.setDisabledWhenOccupied(o.isSelected());
542                    });
543                }
544                slotPopup.show(e.getComponent(), e.getX(), e.getY());
545                break;
546            }
547        }
548    }
549
550    public void setPosition(int index) {
551        traverser.setPosition(index);
552    }
553
554    public int getPosition() {
555        return traverser.getPosition();
556    }
557
558    public void dispose() {
559        if (popupMenu != null) {
560            popupMenu.removeAll();
561        }
562        popupMenu = null;
563        traverser.slotList.forEach((rt) -> {
564            rt.dispose();
565        });
566    }
567
568    public void remove() {
569        active = false;
570    }
571
572    private boolean active = true;
573
574    public boolean isActive() {
575        return active;
576    }
577
578    @Override
579    protected void drawDecorations(Graphics2D g2) {}
580
581    @Override
582    protected void draw1(Graphics2D g2, boolean isMain, boolean isBlock) {
583        float trackWidth = 2.F;
584
585
586        // Only draw in the appropriate pass (mainline or sideline)
587        if (isMain != traverser.isMainline()) {
588            return;
589        }
590
591        // Save original stroke and color
592        Stroke originalStroke = g2.getStroke();
593        Color originalColor = g2.getColor();
594
595        // Draw pit outline and control circles - these are static and belong to the traverser itself
596        if (!isBlock && (isMain == traverser.isMainline())) {
597            // Draw the outer pit rectangle with a thin line
598            g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
599            g2.setColor(layoutEditor.getDefaultTrackColorColor());
600            g2.draw(getBounds());
601
602            // Draw the control circles with the standard track width
603            
604            double circleRadius = Math.max(traverser.getDeckWidth() / 8.f, trackWidth * 2);
605            double circleDiameter = circleRadius * 2.f;
606            Point2D center = getControlPointCenter();
607            g2.setStroke(new BasicStroke(trackWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
608            g2.draw(new Ellipse2D.Double(center.getX() - circleRadius, center.getY() - circleRadius, circleDiameter, circleDiameter));
609            g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
610            g2.draw(trackControlCircleAt(center));
611
612            // Restore original stroke and color for subsequent drawing
613            g2.setStroke(originalStroke);
614            g2.setColor(originalColor);
615        }
616
617        // Draw slot tracks
618        for (int i = 0; i < getNumberSlots(); i++) { // Loop through all slots
619            if (!traverser.isSlotDisabled(i)) { // Only draw if the slot is NOT disabled
620                Color slotColor = null;
621                TrackSegment ts = getSlotConnectOrdered(i);
622//                // A slot is mainline if the bridge is, or if the connected track is.
623//                boolean slotIsMain = false;
624//                if (ts != null) {
625//                    slotIsMain = ts.isMainline();
626//                }
627                // Set color for block, if any
628                if (isBlock) {
629                    if (ts == null) {
630                        g2.setColor(layoutEditor.getDefaultTrackColorColor());
631                    } else {
632                        LayoutBlock lb = ts.getLayoutBlock();
633                        if (lb != null) {
634                            slotColor = g2.getColor();
635                            setColorForTrackBlock(g2, lb);
636                        }
637                    }
638                }
639
640                //LayoutTrackDrawingOptions ltdo = layoutEditor.getLayoutTrackDrawingOptions();
641                //float width = isMain ? ltdo.getMainBlockLineWidth() : ltdo.getSideBlockLineWidth();
642                //g2.setStroke(new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
643                Point2D edgePoint = getSlotEdgePointOrdered(i);
644                Point2D anchorPoint = getSlotCoordsOrdered(i);
645                g2.draw(new Line2D.Double(edgePoint, anchorPoint));
646
647                // Restore color if changed
648                if (slotColor != null) {
649                    g2.setColor(slotColor);
650                }
651            }
652        }
653
654        // Draw the sliding bridge
655        int currentPositionConnectionIndex = getPosition();
656        int orderedIndex = -1;
657        if (currentPositionConnectionIndex != -1) {
658            for (int i = 0; i < getNumberSlots(); i++) {
659                if (getSlotIndex(i) == currentPositionConnectionIndex) {
660                    orderedIndex = i;
661                    break;
662                }
663            }
664        }
665        // Only draw the bridge if a slot is selected, it's not disabled, and its mainline status matches the current pass
666        if (orderedIndex != -1 && !traverser.isSlotDisabled(orderedIndex) && (isMain == traverser.isMainline())) {
667            // Set color for bridge
668            if (isBlock) {
669                LayoutBlock lb = getLayoutBlock();
670                if (lb != null) {
671                    setColorForTrackBlock(g2, lb);
672                } else {
673                    g2.setColor(layoutEditor.getDefaultTrackColorColor());
674                }
675            } else {
676                g2.setColor(traverser.isMainline() ? layoutEditor.getLayoutTrackDrawingOptions().getMainRailColor()
677                        : layoutEditor.getLayoutTrackDrawingOptions().getSideRailColor());
678            }
679            // Set the stroke for the bridge
680            //LayoutTrackDrawingOptions ltdo = layoutEditor.getLayoutTrackDrawingOptions();
681            //float width = traverser.isMainline() ? ltdo.getMainBlockLineWidth() : ltdo.getSideBlockLineWidth();
682            //g2.setStroke(new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
683
684            Point2D center = getCoordsCenter();
685            Rectangle2D pit = getBounds();
686            double offset = getSlotOffsetValue(orderedIndex);
687            double gap = traverser.getDeckWidth() * 0.2;
688            boolean sideA = (orderedIndex % 2 == 0);
689
690            if (getOrientation() == LayoutTraverser.HORIZONTAL) {
691                double y = center.getY() + offset;
692                if (sideA) {
693                    g2.draw(new Line2D.Double(pit.getMinX(), y, pit.getMaxX() - gap, y));
694                } else {
695                    g2.draw(new Line2D.Double(pit.getMinX() + gap, y, pit.getMaxX(), y));
696                }
697            } else { // VERTICAL
698                double x = center.getX() + offset;
699                if (sideA) {
700                    g2.draw(new Line2D.Double(x, pit.getMinY(), x, pit.getMaxY() - gap));
701                } else {
702                    g2.draw(new Line2D.Double(x, pit.getMinY() + gap, x, pit.getMaxY()));
703                }
704            }
705        }
706        if (!layoutEditor.isEditable()) {
707            drawTurnoutControls(g2);
708        }
709
710        // Restore original stroke and color
711        g2.setStroke(originalStroke);
712        g2.setColor(originalColor);
713    }
714
715    @Override
716    protected void draw2(Graphics2D g2, boolean isMain, float railDisplacement) {
717        // For now, just draw a simple line for the rails
718        if (!isMain) return;
719
720        Rectangle2D pit = getBounds();
721        if (getOrientation() == LayoutTraverser.HORIZONTAL) {
722            g2.draw(new Line2D.Double(pit.getMinX(), pit.getMinY() + railDisplacement, pit.getMaxX(), pit.getMinY() + railDisplacement));
723            g2.draw(new Line2D.Double(pit.getMinX(), pit.getMaxY() - railDisplacement, pit.getMaxX(), pit.getMaxY() - railDisplacement));
724        } else { // VERTICAL
725            g2.draw(new Line2D.Double(pit.getMinX() + railDisplacement, pit.getMinY(), pit.getMinX() + railDisplacement, pit.getMaxY()));
726            g2.draw(new Line2D.Double(pit.getMaxX() - railDisplacement, pit.getMinY(), pit.getMaxX() - railDisplacement, pit.getMaxY()));
727        }
728    }
729
730    @Override
731    protected void highlightUnconnected(Graphics2D g2, HitPointType specificType) {
732        for (int j = 0; j < getNumberSlots(); j++) {
733            if (!traverser.isSlotDisabled(j) && ((specificType == HitPointType.NONE) || (specificType == (HitPointType.traverserTrackIndexedValue(j))))) {
734                if (getSlotConnectOrdered(j) == null) {
735                    Point2D pt = getSlotCoordsOrdered(j);
736                    g2.fill(trackControlCircleAt(pt));
737                }
738            }
739        }
740    }
741
742    @Override
743    protected void drawTurnoutControls(Graphics2D g2) {
744        if (isTurnoutControlled()) {
745            for (int j = 0; j < getNumberSlots(); j++) {
746                if (getPosition() != getSlotIndex(j)) {
747                    SlotTrack rt = traverser.slotList.get(j); // Get the SlotTrack object
748                    if (!rt.isDisabled() && !(rt.isDisabledWhenOccupied() && rt.isOccupied())) {
749                        Point2D pt = getSlotCoordsOrdered(j);
750                        g2.draw(trackControlCircleAt(pt));
751                    }
752                }
753            }
754        }
755    }
756
757    @Override
758    protected void drawEditControls(Graphics2D g2) {
759        for (int j = 0; j < getNumberSlots(); j++) {
760            if (!isSlotDisabled(j)) {
761                Point2D pt = getSlotCoordsOrdered(j);
762
763                if (getSlotConnectOrdered(j) == null) {
764                    g2.setColor(Color.red);
765                } else {
766                    g2.setColor(Color.green);
767                }
768                g2.draw(layoutEditor.layoutEditorControlRectAt(pt));
769            }
770        }
771    }
772
773    @Override
774    protected void reCheckBlockBoundary() {}
775
776    @Override
777    protected List<LayoutConnectivity> getLayoutConnectivity() {
778        return null;
779    }
780
781    @Override
782    public List<HitPointType> checkForFreeConnections() {
783        List<HitPointType> result = new ArrayList<>();
784        for (int k = 0; k < getNumberSlots(); k++) {
785            if (getSlotConnectOrdered(k) == null) {
786                result.add(HitPointType.traverserTrackIndexedValue(k));
787            }
788        }
789        return result;
790    }
791
792    @Override
793    public boolean checkForUnAssignedBlocks() {
794        return true;
795    }
796
797    @Override
798    public void checkForNonContiguousBlocks(
799            @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) {
800    }
801
802    @Override
803    public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName,
804            @Nonnull Set<String> TrackNameSet) {
805    }
806
807    @Override
808    public void setAllLayoutBlocks(LayoutBlock layoutBlock) {
809    }
810
811    @Override
812    public boolean canRemove() {
813        return true;
814    }
815
816    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTraverserView.class);
817}