001package jmri.jmrit.display.controlPanelEditor;
002
003import java.awt.BorderLayout;
004import java.awt.Color;
005import java.awt.Dimension;
006import java.awt.FlowLayout;
007import java.awt.Rectangle;
008import java.awt.event.ActionEvent;
009import java.awt.event.ActionListener;
010import java.util.ArrayList;
011import java.util.HashMap;
012import java.util.Iterator;
013import java.util.List;
014import java.util.Map;
015import java.util.SortedSet;
016
017import javax.annotation.Nonnull;
018import javax.swing.*;
019import javax.swing.event.ListSelectionListener;
020import javax.swing.event.ListSelectionEvent;
021
022import jmri.InstanceManager;
023import jmri.NamedBean;
024import jmri.NamedBeanHandle;
025import jmri.NamedBeanHandleManager;
026import jmri.SignalHeadManager;
027import jmri.SignalMastManager;
028import jmri.jmrit.catalog.NamedIcon;
029import jmri.jmrit.display.Editor.TargetPane;
030import jmri.jmrit.display.palette.ItemPalette;
031import jmri.jmrit.display.IndicatorTrack;
032import jmri.jmrit.display.Positionable;
033import jmri.jmrit.display.PositionableLabel;
034import jmri.jmrit.display.PositionableIcon;
035import jmri.jmrit.display.SignalHeadIcon;
036import jmri.jmrit.display.SignalMastIcon;
037import jmri.jmrit.display.TurnoutIcon;
038import jmri.jmrit.logix.OBlock;
039import jmri.jmrit.logix.OBlockManager;
040import jmri.jmrit.logix.Portal;
041import jmri.jmrit.logix.PortalManager;
042import jmri.jmrit.logix.WarrantTableAction;
043import jmri.jmrit.picker.PickListModel;
044import jmri.util.HelpUtil;
045import jmri.util.swing.JmriJOptionPane;
046import jmri.util.swing.JmriMouseEvent;
047
048/**
049 * ControlPanelEditor CircuitBuilder tools.
050 *
051 * @author Pete Cressman Copyright: Copyright (c) 2011
052 */
053public class CircuitBuilder {
054
055    static int STRUT_SIZE = 10;
056
057    private JMenu _circuitMenu;
058    private JMenu _todoMenu;   // error checking items
059
060    // map OBlock to List of icons (track, portal, signal that represent it
061    private final HashMap<OBlock, ArrayList<Positionable>> _circuitMap = new HashMap<>();
062
063    // list of track icons not belonging to an OBlock
064    private final ArrayList<IndicatorTrack> _darkTrack = new ArrayList<>();
065
066    // list of OBlocks with no track icons
067    private final ArrayList<OBlock> _bareBlock = new ArrayList<>();
068
069    // list of OBlocks with 0 length
070    private final ArrayList<OBlock> _zeroBlock = new ArrayList<>();
071
072    // list of circuit icons needing converting
073    private final ArrayList<Positionable> _unconvertedTrack = new ArrayList<>();
074
075    // list of OBlocks whose track icons need converting
076    private final ArrayList<OBlock> _convertBlock = new ArrayList<>();
077
078    // list of Portals with no PortalIcon
079    private final ArrayList<Portal> _noPortalIcon = new ArrayList<>();
080
081    // list of OBlocks with no Portal
082    private final ArrayList<OBlock> _noPortals = new ArrayList<>();
083
084    // list of OBlocks with no Path
085    private final ArrayList<OBlock> _noPaths = new ArrayList<>();
086
087    // list of misplaced PortalIcons
088    private final ArrayList<PortalIcon> _misplacedPortalIcon = new ArrayList<>();
089
090    // map of PortalIcons by portal. A Portal may have 2 icons to connect non-adjacent blocks
091    private final HashMap<Portal, ArrayList<PortalIcon>> _portalIconMap = new HashMap<>();
092
093    // map of SignalMastIcons or SignalHeadicons by Signal. A Signal may have several icons
094    private final HashMap<NamedBean, ArrayList<PositionableIcon>> _signalIconMap = new HashMap<>();
095
096    // list of SignalMastIcon and SignalHeadicon not protecting a block
097    private final ArrayList<PositionableIcon> _unattachedMastIcon = new ArrayList<>();
098
099    // list of SignalMast and SignalHead not protecting a block
100    private final ArrayList<NamedBean> _unprotectingMast = new ArrayList<>();
101
102    // map SignalMasts and SignalHeads to the Portal where it is configured
103    private final HashMap<NamedBean, Portal> _signalMap = new HashMap<>();
104
105    private boolean _hasIndicatorTrackIcons;
106    private boolean _hasPortalIcons;
107    private boolean _hasMastIcons;
108
109    // OBlock list to open edit frames
110    private PickListModel<OBlock> _oblockModel;
111    private JTable _blockTable;
112    jmri.util.JmriJFrame _cbFrame;
113
114    // "Editing Frames" - Called from menu in Main Frame
115    private EditFrame _editFrame;
116
117    private OBlock _currentBlock;
118    private JDialog _dialog;
119    protected ControlPanelEditor _editor;
120    private Positionable _selection;
121
122    public final static Color _editGroupColor = new Color(100, 200, 255);
123    public final static Color _pathColor = Color.green;
124    public final static Color _highlightColor = new Color(255, 150, 220);
125
126    /**
127     * ***************************************************************
128     */
129    public CircuitBuilder() {
130        log.error("CircuitBuilder ctor requires an Editor class");
131    }
132
133    public CircuitBuilder(ControlPanelEditor ed) {
134        _editor = ed;
135        _oblockModel = PickListModel.oBlockPickModelInstance();
136        _blockTable = _oblockModel.makePickTable();
137
138    }
139
140    /**
141     * Makes menu for ControlPanelEditor. Called by ControlPanelEditor at init
142     * before contents have been loaded.
143     *
144     * @return the menu, created if needed
145     */
146    protected JMenu makeMenu() {
147        _circuitMenu = new JMenu(Bundle.getMessage("CircuitBuilder"));
148        OBlockManager manager = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class);
149        SortedSet<OBlock> oblocks = manager.getNamedBeanSet();
150        for (OBlock block : oblocks) {
151            _circuitMap.put(block, new ArrayList<>());
152        }
153        checkCircuits();  // need content for this
154        int num = Math.min(manager.getObjectCount(), 20) + 5;
155        _blockTable.setPreferredScrollableViewportSize(new java.awt.Dimension(300, _blockTable.getRowHeight() * num));
156        return _circuitMenu;
157    }
158
159    protected void openCBWindow() {
160        if (_cbFrame != null) {
161            _cbFrame.toFront();
162        } else {
163            _cbFrame = new CBFrame(Bundle.getMessage("CircuitBuilder"));
164        }
165    }
166
167    private void makeNoOBlockMenu() {
168        JMenuItem circuitItem = new JMenuItem(Bundle.getMessage("newCircuitItem"));
169        _circuitMenu.add(circuitItem);
170        circuitItem.addActionListener((ActionEvent event) -> newCircuit());
171        _circuitMenu.add(new JMenuItem(Bundle.getMessage("noCircuitsItem")));
172        JMenuItem helpItem = new JMenuItem(Bundle.getMessage("AboutCircuitBuilder"));
173        HelpUtil.enableHelpOnButton(helpItem, "package.jmri.jmrit.display.CircuitBuilder");
174        _circuitMenu.add(helpItem);
175
176    }
177    private void makeCircuitMenu() {
178        JMenuItem editItem = new JMenuItem(Bundle.getMessage("newCircuitItem"));
179        _circuitMenu.add(editItem);
180        editItem.addActionListener((ActionEvent event) -> {
181            closeCBWindow();
182            newCircuit();
183        });
184        editItem = new JMenuItem(Bundle.getMessage("editCircuitItem"));
185        _circuitMenu.add(editItem);
186        editItem.addActionListener((ActionEvent event) -> {
187            closeCBWindow();
188            editCircuit("editCircuitItem", true);
189        });
190        editItem = new JMenuItem(Bundle.getMessage("editPortalsItem"));
191        _circuitMenu.add(editItem);
192        editItem.addActionListener((ActionEvent event) -> {
193            closeCBWindow();
194            editPortals("editPortalsItem", true);
195        });
196        editItem = new JMenuItem(Bundle.getMessage("editCircuitPathsItem"));
197        _circuitMenu.add(editItem);
198        editItem.addActionListener((ActionEvent event) -> {
199            closeCBWindow();
200            editCircuitPaths("editCircuitPathsItem", true);
201        });
202        editItem = new JMenuItem(Bundle.getMessage("editDirectionItem"));
203        _circuitMenu.add(editItem);
204        editItem.addActionListener((ActionEvent event) -> {
205            closeCBWindow();
206            editPortalDirection("editDirectionItem", true);
207        });
208        editItem = new JMenuItem(Bundle.getMessage("editSignalItem"));
209        _circuitMenu.add(editItem);
210        editItem.addActionListener((ActionEvent event) -> {
211            closeCBWindow();
212            editSignalFrame("editSignalItem", true);
213        });
214        _todoMenu = new JMenu(Bundle.getMessage("circuitErrorsItem"));
215        _circuitMenu.add(_todoMenu);
216
217        editItem = makePortalIconMenu();
218        _circuitMenu.add(editItem);
219
220        JMenuItem helpItem = new JMenuItem(Bundle.getMessage("AboutCircuitBuilder"));
221        HelpUtil.enableHelpOnButton(helpItem, "package.jmri.jmrit.display.CircuitBuilder");
222        _circuitMenu.add(helpItem);
223        makeToDoMenu();
224    }
225
226    /**
227     * Add icon 'pos' to circuit 'block'
228     */
229    private void addIcon(OBlock block, Positionable pos) {
230        List<Positionable> icons = getCircuitIcons(block);
231        if (pos != null) {
232            if (!icons.contains(pos)) {
233                icons.add(pos);
234            }
235        }
236        _darkTrack.remove(pos);
237        // if (log.isDebugEnabled()) log.debug("addIcon: block "+block.getDisplayName()+" has "+icons.size()+" icons.");
238    }
239
240    private JMenu makePortalIconMenu() {
241        JMenu familyMenu = new JMenu(Bundle.getMessage("portalIconSet"));
242        ButtonGroup familyGroup = new ButtonGroup();
243        ActionListener portalIconAction = (ActionEvent event) -> {
244            String family = event.getActionCommand();
245            if (!family.equals(_editor.getPortalIconFamily())) {
246                closeCBWindow();
247                _editor.setPortalIconFamily(family);
248                for (Positionable pos : _editor.getContents()) {
249                    if (pos instanceof PortalIcon) {
250                        PortalIcon pIcon = (PortalIcon) pos;
251                        pIcon.setMap(_editor.getPortalIconMap());
252                    }
253                }
254            }
255        };
256        HashMap<String, HashMap<String, NamedIcon>> familyMap = ItemPalette.getFamilyMaps("Portal");
257        for (String family : familyMap.keySet()) {
258            JCheckBoxMenuItem mi = new JCheckBoxMenuItem(family);
259            familyGroup.add(mi);
260            if (_editor.getPortalIconFamily().equals(family)) {
261                mi.setSelected(true);
262            }
263            mi.setActionCommand(family);
264            mi.addActionListener(portalIconAction);
265            familyMenu.add(mi);
266        }
267        return familyMenu;
268    }
269
270
271    // Rebuild after any edit change
272    private void makeToDoMenu() {
273        if (_todoMenu == null) {
274            _todoMenu = new JMenu(Bundle.getMessage("circuitErrorsItem"));
275            _circuitMenu.add(_todoMenu);
276        } else {
277            _todoMenu.removeAll();
278        }
279
280        JMenu blockNeeds = new JMenu(Bundle.getMessage("blockNeedsIconsItem"));
281        ActionListener editCircuitAction = (ActionEvent event) -> {
282            String sysName = event.getActionCommand();
283            editCircuitError(sysName);
284        };
285        if (_bareBlock.size() > 0) {
286            for (OBlock block : _bareBlock) {
287                JMenuItem mi = new JMenuItem(java.text.MessageFormat.format(Bundle.getMessage("OpenCircuitItem"), block.getDisplayName()));
288                mi.setActionCommand(block.getSystemName());
289                mi.addActionListener(editCircuitAction);
290                blockNeeds.add(mi);
291            }
292        } else {
293            blockNeeds.add(new JMenuItem(Bundle.getMessage("circuitsHaveIcons")));
294        }
295        _todoMenu.add(blockNeeds);  // #1
296
297        blockNeeds = new JMenu(Bundle.getMessage("blocksNeedConversionItem"));
298        if (_convertBlock.size() > 0) {
299            for (OBlock block : _convertBlock) {
300                JMenuItem mi = new JMenuItem(java.text.MessageFormat.format(Bundle.getMessage("OpenCircuitItem"), block.getDisplayName()));
301                mi.setActionCommand(block.getSystemName());
302                mi.addActionListener(editCircuitAction);
303                blockNeeds.add(mi);
304            }
305        } else {
306            blockNeeds.add(new JMenuItem(Bundle.getMessage("circuitIconsConverted")));
307        }
308        _todoMenu.add(blockNeeds);  // #2
309
310        JMenuItem iconNeeds = new JMenuItem(Bundle.getMessage("iconsNeedConversionItem"));
311        if (_unconvertedTrack.size() > 0) {
312            iconNeeds.addActionListener((ActionEvent event) -> {
313                if (editingOK()) {
314                    hidePortalIcons(true);
315                    ArrayList<Positionable> group = new ArrayList<>();
316                    for (Positionable positionable : _unconvertedTrack) {
317                        group.add(positionable);
318                    }
319                    _editor.setSelectionGroup(group);
320                }
321            });
322        } else {
323            iconNeeds = new JMenuItem(Bundle.getMessage("noneNeedConversion"));
324        }
325        _todoMenu.add(iconNeeds);   // #3
326
327        if (_darkTrack.size() > 0) {
328            iconNeeds = new JMenuItem(Bundle.getMessage("iconsNeedsBlocksItem"));
329            iconNeeds.addActionListener((ActionEvent event) -> {
330                if (editingOK()) {
331                    hidePortalIcons(true);
332                    ArrayList<Positionable> group = new ArrayList<>();
333                    for (Positionable positionable : _darkTrack) {
334                        group.add(positionable);
335                    }
336                    _editor.setSelectionGroup(group);
337                }
338            });
339        } else {
340            if (_hasIndicatorTrackIcons) {
341                iconNeeds = new JMenuItem(Bundle.getMessage("IconsHaveCircuits"));
342            } else {
343                iconNeeds = new JMenuItem(Bundle.getMessage("noIndicatorTrackIcon"));
344            }
345        }
346        _todoMenu.add(iconNeeds);   // #4
347
348        if (!_noPortals.isEmpty()) {
349            blockNeeds = new JMenu(Bundle.getMessage("blockNeedsPortals"));
350            ActionListener editPortalAction = (ActionEvent event) -> {
351                String sysName = event.getActionCommand();
352                _currentBlock = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getOBlock(sysName);
353                editPortals(null, false);
354            };
355            for (OBlock block : _noPortals) {
356                JMenuItem mi = new JMenuItem(java.text.MessageFormat.format(
357                        Bundle.getMessage("OpenPortalTitle"), block.getDisplayName()));
358                mi.setActionCommand(block.getSystemName());
359                mi.addActionListener(editPortalAction);
360                blockNeeds.add(mi);
361            }
362        } else if (_noPaths.size() > 0) {
363            blockNeeds = new JMenu(Bundle.getMessage("blockNeedsPaths"));
364            ActionListener editPortalAction = (ActionEvent event) -> {
365                String sysName = event.getActionCommand();
366                _currentBlock = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getOBlock(sysName);
367                editCircuitPaths(null, false);
368            };
369            for (OBlock block : _noPaths) {
370                JMenuItem mi = new JMenuItem(java.text.MessageFormat.format(
371                        Bundle.getMessage("OpenPathTitle"), block.getDisplayName()));
372                mi.setActionCommand(block.getSystemName());
373                mi.addActionListener(editPortalAction);
374                blockNeeds.add(mi);
375            }
376        } else {
377            blockNeeds = new JMenu(Bundle.getMessage("circuitsHavePortalsPaths"));
378        }
379        _todoMenu.add(blockNeeds);  // #5
380
381        if (_zeroBlock.isEmpty()) {
382            blockNeeds = new JMenu(Bundle.getMessage("blocksHaveLength"));
383        } else {
384            blockNeeds = new JMenu(Bundle.getMessage("blockNeedLength"));
385            for (OBlock block : _zeroBlock) {
386                JMenuItem mi = new JMenuItem(java.text.MessageFormat.format(Bundle.getMessage("OpenCircuitItem"), block.getDisplayName()));
387                mi.setActionCommand(block.getSystemName());
388                mi.addActionListener(editCircuitAction);
389                blockNeeds.add(mi);
390            }
391        }
392        _todoMenu.add(blockNeeds);  // #6
393
394        blockNeeds = new JMenu(Bundle.getMessage("portalsMisplaced"));
395        if (_misplacedPortalIcon.size() > 0) {
396            for (PortalIcon icon : _misplacedPortalIcon) {
397                Portal portal = icon.getPortal();
398                OBlock fromBlock = portal.getFromBlock();
399                OBlock toBlock = portal.getToBlock();
400                if (fromBlock != null) {
401                    JMenuItem mi = new JMenuItem(Bundle.getMessage("OpenPortalTitle", fromBlock.getDisplayName()));
402                    mi.addActionListener((ActionEvent event) -> editPortalError(fromBlock, portal, icon));
403                    blockNeeds.add(mi);
404                } else if (toBlock != null) {
405                    JMenuItem mi = new JMenuItem(Bundle.getMessage("OpenPortalTitle", toBlock.getDisplayName()));
406                    mi.addActionListener((ActionEvent event) -> editPortalError(toBlock, portal, icon));
407                    blockNeeds.add(mi);
408                }
409            }
410        } else {
411            if (_hasPortalIcons) {
412                blockNeeds.add(new JMenuItem(Bundle.getMessage("portalsInPlace")));
413            } else {
414                blockNeeds.add(new JMenuItem(Bundle.getMessage("NoPortalIcons")));
415            }
416        }
417        _todoMenu.add(blockNeeds);  //#7
418
419        if (_misplacedPortalIcon.size() > 0) {
420            iconNeeds = new JMenuItem(Bundle.getMessage("iconsNeedPositioning"));
421            iconNeeds.addActionListener((ActionEvent event) -> {
422                if (editingOK()) {
423                    ArrayList<Positionable> group = new ArrayList<>();
424                    for (PortalIcon pi : _misplacedPortalIcon) {
425                        group.add(pi);
426                        pi.setStatus(PortalIcon.VISIBLE);
427                    }
428                    _editor.setSelectionGroup(group);
429                }
430            });
431        } else {
432            if (_hasPortalIcons) {
433                iconNeeds = new JMenuItem(Bundle.getMessage("portalsInPlace"));
434            } else {
435                iconNeeds = new JMenuItem(Bundle.getMessage("NoPortalIcons"));
436            }
437        }
438        _todoMenu.add(iconNeeds);   // #8
439
440        JMenu mastNeeds = new JMenu(Bundle.getMessage("UnprotectingMasts"));
441        if (!_unprotectingMast.isEmpty()) {
442//            mastNeeds.addActionListener((ActionEvent event) -> {
443                for (NamedBean sig : _unprotectingMast) {
444                    JMenuItem mi = new JMenuItem(sig.getDisplayName());
445                    mastNeeds.add(mi);
446                }
447//            });
448        } else {
449            mastNeeds.add(new JMenuItem(Bundle.getMessage("mastsInPlace")));
450        }
451        _todoMenu.add(mastNeeds);   // #9
452
453        if (_unattachedMastIcon.size() > 0) {
454            iconNeeds = new JMenuItem(Bundle.getMessage("UnattachedMasts"));
455            iconNeeds.addActionListener((ActionEvent event) -> {
456//                if (editingOK()) {
457                    ArrayList<Positionable> group = new ArrayList<>();
458                    for (PositionableIcon pi : _unattachedMastIcon) {
459                        group.add(pi);
460                    }
461                    _editor.setSelectionGroup(group);
462//                }
463            });
464        } else {
465            if (_hasMastIcons) {
466                iconNeeds = new JMenuItem(Bundle.getMessage("mastsInPlace"));
467            } else {
468                iconNeeds = new JMenuItem(Bundle.getMessage("NoMastIcons"));
469            }
470        }
471        _todoMenu.add(iconNeeds);   // #10
472
473        blockNeeds = new JMenu(Bundle.getMessage("portalNeedsIcon"));
474        ActionListener editPortalAction = (ActionEvent event) -> {
475            String portalName = event.getActionCommand();
476            editPortalError(portalName);
477            };
478        if (_noPortalIcon.size() > 0) {
479            for (Portal portal : _noPortalIcon) {
480                JMenuItem mi = new JMenuItem(portal.toString());
481                mi.setActionCommand(portal.getName());
482                mi.addActionListener(editPortalAction);
483                blockNeeds.add(mi);
484            }
485        } else {
486            blockNeeds.add(new JMenuItem(Bundle.getMessage("portalsHaveIcons")));
487        }
488        _todoMenu.add(blockNeeds);  // #11
489
490        JMenuItem pError = new JMenuItem(Bundle.getMessage("CheckPortalPaths"));
491        pError.addActionListener((ActionEvent event) -> {
492            if (!WarrantTableAction.getDefault().errorCheck()) {
493                javax.swing.JFrame frame;
494                if (_editFrame != null) {
495                    frame = _editFrame;
496                } else if (_cbFrame != null){
497                    frame = _cbFrame;
498                } else {
499                    frame = _editor;
500                }
501                JmriJOptionPane.showMessageDialog(frame,
502                        Bundle.getMessage("blocksEtcOK"), Bundle.getMessage("ButtonOK"),
503                        JmriJOptionPane.INFORMATION_MESSAGE);
504            }
505        });
506        _todoMenu.add(pError);      // #12
507
508    }
509
510    // used for testing only
511    protected EditFrame getEditFrame() {
512        return _editFrame;
513    }
514
515    /**
516     * ************** Set up editing Frames ****************
517     */
518    protected void newCircuit() {
519        if (editingOK()) {
520            _blockTable.clearSelection();
521            setUpEditCircuit();
522            _editFrame = new EditCircuitFrame(Bundle.getMessage("newCircuitItem"), this, null);
523        }
524    }
525
526    protected void editCircuit(String title, boolean fromMenu) {
527        if (editingOK()) {
528            if (fromMenu) {
529                editCircuitDialog(title);
530            }
531            if (_currentBlock != null) {
532                setUpEditCircuit();
533                _editFrame = new EditCircuitFrame(Bundle.getMessage("OpenCircuitItem"), this, _currentBlock);
534            } else if (!fromMenu) {
535                selectPrompt();
536            }
537        }
538    }
539
540    private void setUpEditCircuit() {
541        _editor.setSelectionGroup(makeSelectionGroup(_currentBlock, false));
542        _editor.disableMenus();
543        TargetPane targetPane = (TargetPane) _editor.getTargetPanel();
544        targetPane.setSelectGroupColor(_editGroupColor);
545        targetPane.setHighlightColor(_highlightColor);
546    }
547
548    protected void editCircuitError(String sysName) {
549        hidePortalIcons(true);
550        if (editingOK()) {
551            _currentBlock = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getBySystemName(sysName);
552            if (_currentBlock != null) {
553                _editor.setSelectionGroup(makeSelectionGroup(_currentBlock, false));
554                _editor.disableMenus();
555                _editFrame = new EditCircuitFrame(Bundle.getMessage("OpenCircuitItem"), this, _currentBlock);
556            }
557        }
558    }
559
560    protected void editPortals(String title, boolean fromMenu) {
561        if (editingOK()) {
562            if (fromMenu) {
563                editCircuitDialog(title);
564            }
565            if (_currentBlock != null) {
566                // check icons to be indicator type
567                _editor.setSelectionGroup(makeSelectionGroup(_currentBlock, true));
568                _editor.disableMenus();
569                TargetPane targetPane = (TargetPane) _editor.getTargetPanel();
570                targetPane.setSelectGroupColor(_editGroupColor);
571                targetPane.setHighlightColor(_highlightColor);
572                setPortalsPositionable(_currentBlock, true);
573                _editFrame = new EditPortalFrame(Bundle.getMessage("OpenPortalTitle"), this, _currentBlock);
574                _editFrame.canEdit();   // will close _editFrame if editing cannot be done
575            } else if (!fromMenu) {
576                selectPrompt();
577            }
578        }
579    }
580
581    protected void editPortalError(String name) {
582        if (editingOK()) {
583            Portal portal = InstanceManager.getDefault(jmri.jmrit.logix.PortalManager.class).getPortal(name);
584            _currentBlock = portal.getFromBlock();
585            if (_currentBlock == null) {
586                _currentBlock = portal.getToBlock();
587            }
588            editPortals(null, false);
589        }
590    }
591
592    protected void editPortalError(OBlock block, Portal portal, PortalIcon icon) {
593        if (editingOK()) {
594            _currentBlock = block;
595            if (_currentBlock != null) {
596                _editor.setSelectionGroup(makeSelectionGroup(_currentBlock, true));
597                _editor.disableMenus();
598                TargetPane targetPane = (TargetPane) _editor.getTargetPanel();
599                targetPane.setSelectGroupColor(_editGroupColor);
600                targetPane.setHighlightColor(_highlightColor);
601                setPortalsPositionable(_currentBlock, true);
602                _editFrame = new EditPortalFrame(Bundle.getMessage("OpenPortalTitle"), this,
603                        _currentBlock, portal, icon);
604            }
605        }
606    }
607
608    protected void editPortalDirection(String title, boolean fromMenu) {
609        if (editingOK()) {
610            if (fromMenu) {
611                editCircuitDialog(title);
612            }
613            if (_currentBlock != null) {
614                _editor.setSelectionGroup(makeSelectionGroup(_currentBlock, true));
615                _editor.disableMenus();
616                TargetPane targetPane = (TargetPane) _editor.getTargetPanel();
617                targetPane.setSelectGroupColor(_editGroupColor);
618                targetPane.setHighlightColor(_highlightColor);
619                setPortalsPositionable(_currentBlock, true);
620                _editFrame = new EditPortalDirection(Bundle.getMessage("OpenDirectionTitle"), this, _currentBlock);
621                _editFrame.canEdit();   // will close _editFrame if editing cannot be done
622            } else if (!fromMenu) {
623                selectPrompt();
624            }
625        }
626    }
627
628    protected void editSignalFrame(String title, boolean fromMenu) {
629        if (editingOK()) {
630            if (fromMenu) {
631                editCircuitDialog(title);
632            }
633            if (_currentBlock != null) {
634                // check icons to be indicator type
635                _editor.setSelectionGroup(makeSelectionGroup(_currentBlock, true));
636                _editor.disableMenus();
637                TargetPane targetPane = (TargetPane) _editor.getTargetPanel();
638                targetPane.setSelectGroupColor(_editGroupColor);
639                targetPane.setHighlightColor(_highlightColor);
640                _editFrame = new EditSignalFrame(Bundle.getMessage("OpenSignalsTitle"), this, _currentBlock);
641                _editFrame.canEdit();   // will close _editFrame if editing cannot be done
642            } else if (!fromMenu) {
643                selectPrompt();
644            }
645        }
646    }
647
648    protected void editCircuitPaths(String title, boolean fromMenu) {
649        if (editingOK()) {
650            if (fromMenu) {
651                editCircuitDialog(title);
652            }
653            if (_currentBlock != null) {
654                // check icons to be indicator type
655                // must have converted icons for paths
656                _editor.setSelectionGroup(makeSelectionGroup(_currentBlock, true));
657                // A temporary path "TEST_PATH" is used to display the icons representing a path
658                _currentBlock.allocatePath(EditCircuitPaths.TEST_PATH);
659                _editor.disableMenus();
660                TargetPane targetPane = (TargetPane) _editor.getTargetPanel();
661                targetPane.setSelectGroupColor(_editGroupColor);
662                targetPane.setHighlightColor(_editGroupColor);
663                _currentBlock.setState(OBlock.UNOCCUPIED);
664                _editFrame = new EditCircuitPaths(Bundle.getMessage("OpenPathTitle"), this, _currentBlock);
665                _editFrame.canEdit();   // will close _editFrame if editing cannot be done
666            } else if (!fromMenu) {
667                selectPrompt();
668            }
669        }
670    }
671
672    protected void setCurrentBlock(OBlock b) {
673        _currentBlock = b;
674    }
675
676    protected void hidePortalIcons(boolean hideAll) {
677        if (_editFrame != null) {
678            _editFrame.clearListSelection();
679        } else {
680            for (ArrayList<PortalIcon> array : _portalIconMap.values()) {
681                for (PortalIcon pi : array) {
682                    if (hideAll || pi.getStatus().equals(PortalIcon.VISIBLE)) {
683                        // don't hide warrant arrows
684                        pi.setStatus(PortalIcon.HIDDEN);
685                    }
686                }
687            }
688        }
689    }
690
691    private boolean editingOK() {
692        if (_editFrame != null) {
693            // Already editing a circuit, ask for completion of that edit
694            JmriJOptionPane.showMessageDialog(_editFrame,
695                    Bundle.getMessage("AlreadyEditing"), Bundle.getMessage("ErrorTitle"),
696                    JmriJOptionPane.ERROR_MESSAGE);
697            _editFrame.toFront();
698            _editFrame.setVisible(true);
699            return false;
700        }
701        OBlockManager manager = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class);
702        if (manager.getObjectCount() == 0) {
703            return true;
704        } else {
705            for (OBlock block : manager.getNamedBeanSet()) {
706                if ((block.getState() & OBlock.ALLOCATED) != 0) {
707                    JmriJOptionPane.showMessageDialog(_editor, Bundle.getMessage("cannotEditCB", block.getWarrant().getDisplayName()),
708                            Bundle.getMessage("editCiruit"), JmriJOptionPane.INFORMATION_MESSAGE);
709                    return false;
710                }
711            }
712        }
713        checkCircuits();
714        return true;
715    }
716
717    /**
718     * Edit existing OBlock. Used by edit to set up _editCircuitFrame.
719     * Sets _currentBlock to chosen OBlock or null if none selected.
720     */
721    private void editCircuitDialog(String title) {
722        _dialog = new JDialog(_editor, Bundle.getMessage(title), true);
723        JPanel panel = new JPanel();
724        panel.setLayout(new BorderLayout(10, 10));
725        JPanel mainPanel = new JPanel();
726        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
727
728        mainPanel.add(Box.createVerticalStrut(STRUT_SIZE));
729        JPanel p = new JPanel();
730        p.add(new JLabel(Bundle.getMessage("selectOBlock")));
731        p.setMaximumSize(new Dimension(300, p.getPreferredSize().height));
732        mainPanel.add(p);
733
734        mainPanel.add(new JScrollPane(_blockTable));
735        mainPanel.add(Box.createVerticalStrut(STRUT_SIZE));
736        mainPanel.add(makeDoneButtonPanel());
737        panel.add(mainPanel);
738        _dialog.getContentPane().add(panel);
739        _dialog.setLocation(_editor.getLocation().x + 100, _editor.getLocation().y + 100);
740        _dialog.pack();
741        _dialog.setVisible(true);
742    }
743
744    private JPanel makeDoneButtonPanel() {
745        JPanel buttonPanel = new JPanel();
746        JPanel panel0 = new JPanel();
747        panel0.setLayout(new FlowLayout());
748        JButton doneButton;
749        doneButton = new JButton(Bundle.getMessage("ButtonOpenCircuit"));
750        doneButton.addActionListener((ActionEvent a) -> {
751            if (doOpenAction()) {
752                _dialog.dispose();
753            }
754        });
755        panel0.add(doneButton);
756
757        JButton cancelButton = new JButton(Bundle.getMessage("ButtonCancel"));
758        cancelButton.addActionListener((ActionEvent a) -> _dialog.dispose());
759        panel0.add(cancelButton);
760        buttonPanel.add(panel0);
761        buttonPanel.setMaximumSize(new Dimension(300, buttonPanel.getPreferredSize().height));
762        return buttonPanel;
763    }
764
765    private boolean doOpenAction() {
766        int row = _blockTable.getSelectedRow();
767        if (row >= 0) {
768            row = _blockTable.convertRowIndexToModel(row);
769            _currentBlock = _oblockModel.getBeanAt(row);
770            return true;
771        }
772        _currentBlock = null;
773        selectPrompt();
774        return false;
775    }
776    private void selectPrompt() {
777        JmriJOptionPane.showMessageDialog(_editor, Bundle.getMessage("selectOBlock"),
778                Bundle.getMessage("NeedDataTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
779    }
780
781    /*
782     * ************************ end setup frames *****************************
783     */
784    private void setPortalsPositionable(OBlock block, boolean set) {
785        for (Positionable p : getCircuitIcons(block)) {
786            if (p instanceof PortalIcon) {
787                p.setPositionable(set);
788            }
789        }
790    }
791
792    ////////////////////////// Closing Editing Frames //////////////////////////
793    /**
794     * Edit frame closing, set block's icons to support OBlock's state changes
795     * @param block OBlock to set icon selections into data maps
796     */
797    protected void setIconGroup(OBlock block) {
798        for (Positionable pos : getCircuitIcons(block)) {
799            if (pos instanceof IndicatorTrack) {
800                ((IndicatorTrack) pos).setOccBlockHandle(null);
801            }
802        }
803        // the selectionGroup for all edit frames is full collection of icons
804        // comprising the block.  Gather them and store in the block's hashMap
805        List<Positionable> selections = _editor.getSelectionGroup();
806        List<Positionable> icons = getCircuitIcons(block);
807        icons.clear();
808        if (selections != null && !selections.isEmpty()) {
809            NamedBeanHandle<OBlock> handle =
810                    InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(block.getSystemName(), block);
811             for (Positionable pos : selections) {
812                 if (pos instanceof IndicatorTrack) {
813                     ((IndicatorTrack) pos).setOccBlockHandle(handle);
814                 }
815                 icons.add(pos);
816             }
817        }
818        if (log.isDebugEnabled()) {
819            log.debug("setIconGroup: block \"{}\" has {} icons.", block.getDisplayName(), icons.size());
820        }
821    }
822
823    protected void closeCircuitBuilder(OBlock block) {
824        _currentBlock = null;
825        _editFrame = null;
826        checkCircuits();
827        setPortalsPositionable(block, false);
828        hidePortalIcons(true);
829        _editor.resetEditor();
830
831    }
832
833    /*
834     * ************** end closing frames *******************
835     */
836
837    /**
838     * Find the blocks with no icons and the blocks with icons that need
839     * conversion Setup for main Frame - used in both initialization and close
840     * of an editing frame Build Lists that are used to create menu items
841     */
842    private void checkCircuits() {
843
844        _portalIconMap.clear();
845        _signalIconMap.clear();
846        _signalMap.clear();
847        _unattachedMastIcon.clear();
848        _darkTrack.clear();
849        _unconvertedTrack.clear();
850        _hasIndicatorTrackIcons = false;
851        _hasPortalIcons = false;
852        _hasMastIcons = false;
853        ArrayList<Positionable> removeList = new ArrayList<>(); // avoid comodification
854        PortalManager portalMgr = InstanceManager.getDefault(jmri.jmrit.logix.PortalManager.class);
855
856        for (Positionable pos : _editor.getContents()) {
857            if (pos instanceof IndicatorTrack) {
858                IndicatorTrack iPos = (IndicatorTrack) pos;
859                _hasIndicatorTrackIcons = true;
860                OBlock block = iPos.getOccBlock();
861                iPos.removePath(EditCircuitPaths.TEST_PATH);
862                if (block != null) {
863                    addIcon(block, iPos);
864                } else {
865                    _darkTrack.add(iPos);
866                }
867            } else if (pos instanceof PortalIcon) {
868                _hasPortalIcons = true;
869                PortalIcon pIcon = (PortalIcon) pos;
870                Portal portal = pIcon.getPortal();
871                if (portal == null) {
872                    log.error("No Portal for PortalIcon called \"{}\". Discarding icon.", pIcon.getName());
873                    removeList.add(pIcon);
874                } else {
875                    List<PortalIcon> piArray = getPortalIcons(portal);
876                    piArray.add(pIcon);
877                }
878            } else if (pos instanceof SignalMastIcon) {
879                _hasMastIcons = true;
880                SignalMastIcon sIcon = (SignalMastIcon) pos;
881                NamedBean mast = sIcon.getSignalMast();
882                if (mast == null) {
883                    log.error("No SignalMast for SignalMastIcon called \"{}\".", sIcon.getNameString());
884                    removeList.add(sIcon);
885                } else {
886                    List<PositionableIcon> siArray = getSignalIconMap(mast);
887                    siArray.add(sIcon);
888                    _unattachedMastIcon.add(sIcon);
889                }
890            } else if (pos instanceof SignalHeadIcon) {
891                _hasMastIcons = true;
892                SignalHeadIcon sIcon = (SignalHeadIcon) pos;
893                NamedBean mast = sIcon.getSignalHead();
894                if (mast == null) {
895                    log.error("No SignalHead for SignalHeadIcon called \"{}\".", sIcon.getNameString());
896                    removeList.add(sIcon);
897                } else {
898                    List<PositionableIcon> siArray = getSignalIconMap(mast);
899                    siArray.add(sIcon);
900                    _unattachedMastIcon.add(sIcon);
901                }
902            } else if (isUnconvertedTrack(pos)) {
903                if (!_unconvertedTrack.contains(pos)) {
904                    _unconvertedTrack.add(pos);
905                }
906            }
907        }
908        for (Positionable positionable : removeList) {
909            positionable.remove();
910        }
911
912        _bareBlock.clear();         // blocks with no track icons
913        _zeroBlock.clear();         // blocks with 0 length
914        _convertBlock.clear();      // blocks with at least one unconverted track icon
915        _misplacedPortalIcon.clear();
916        _noPortalIcon.clear();
917        _noPortals.clear();
918        _noPaths.clear();
919        _unprotectingMast.clear();
920        // initialize _signalMap
921        Iterator<jmri.SignalMast> iter1 =
922                InstanceManager.getDefault(SignalMastManager.class).getNamedBeanSet().iterator();
923        while (iter1.hasNext()) {
924            _signalMap.put(iter1.next(), null);
925        }
926        Iterator<jmri.SignalHead> iter2 =
927                InstanceManager.getDefault(SignalHeadManager.class).getNamedBeanSet().iterator();
928        while (iter2.hasNext()) {
929            _signalMap.put(iter2.next(), null);
930        }
931
932        OBlockManager manager = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class);
933        SortedSet<OBlock> oblocks = manager.getNamedBeanSet();
934        for (OBlock block : oblocks) {
935            List<Portal> portals = block.getPortals();
936            if (portals.isEmpty()) {
937                _noPortals.add(block);
938            } else {
939                // first add PortalIcons and SignalIcons to circuitMap
940                for (Portal portal : portals) {
941                    List<PortalIcon> piArray = getPortalIcons(portal);
942                    for (PortalIcon pi : piArray) {
943                        addIcon(block, pi);
944                    }
945                    NamedBean mast = portal.getSignalProtectingBlock(block);
946                    if (mast != null) {
947                        List<PositionableIcon> siArray = getSignalIconMap(mast);
948                        for (PositionableIcon si : siArray) {
949                            addIcon(block, si);
950                            _unattachedMastIcon.remove(si);
951                        }
952                        _signalMap.put(mast, portal);
953                    }
954                    if (log.isDebugEnabled()) {
955                        log.debug("Portal {} in block {} has {} icons", portal.getName(), block.getDisplayName(), piArray.size());
956                    }
957                }
958            }
959
960            List<jmri.Path> paths = block.getPaths();
961            float blkLen = block.getLengthMm();
962            if (paths.isEmpty()) {
963                _noPaths.add(block);
964                if (blkLen < .001f) {
965                    _zeroBlock.add(block);
966               }
967            } else if (blkLen < .001f) {
968                for (jmri.Path path : paths) {
969                    if (path.getLengthMm() < .001f) {
970                        _zeroBlock.add(block);
971                        break;
972                    }   // blkLen == 0 OK, if all paths have length
973                }
974            }
975
976            List<Positionable> icons = getCircuitIcons(block);
977            if (log.isDebugEnabled()) {
978                log.debug("checkCircuits: block {} has {} icons.", block.getDisplayName(), icons.size());
979            }
980            if (icons.isEmpty()) {
981                _bareBlock.add(block);
982            } else {
983                boolean hasTrackIcon = false;
984                boolean iconNeedsConversion = false;
985                for (Positionable pos : icons) {
986                    if (!(pos instanceof PortalIcon) && !(pos instanceof SignalMastIcon) && !(pos instanceof SignalHeadIcon)) {
987                        hasTrackIcon = true;
988                        if (!(pos instanceof IndicatorTrack)) {
989                            iconNeedsConversion = true;
990                        }
991                    }
992                }
993                if (hasTrackIcon) {
994                    _bareBlock.remove(block);
995                } else if (!_bareBlock.contains(block)) {
996                    _bareBlock.add(block);
997                }
998                if (iconNeedsConversion && !_convertBlock.contains(block)) {
999                    _convertBlock.add(block);
1000                }
1001            }
1002        }
1003
1004        // check positioning of portal icons for 'direction arrow' state.
1005        for (Portal portal : portalMgr.getPortalSet()) {
1006            List<PortalIcon> piArray = getPortalIcons(portal);
1007            if (piArray.isEmpty()) {
1008                _noPortalIcon.add(portal);
1009            } else {
1010                PortalIcon icon1 = piArray.get(0);
1011                if (piArray.size() == 1) {
1012                    if (!iconIntersectsBlock(icon1, portal.getToBlock()) ||
1013                            !iconIntersectsBlock(icon1, portal.getFromBlock())) {
1014                        _misplacedPortalIcon.add(icon1);
1015                    }
1016                } else {
1017                    boolean fromOK = false;
1018                    boolean toOK = false;
1019                    PortalIcon icon = null;
1020                    for (PortalIcon ic : piArray) {
1021                        if (!toOK && iconIntersectsBlock(ic, portal.getToBlock()) &&
1022                                !iconIntersectsBlock(ic, portal.getFromBlock())) {
1023                            toOK = true;
1024                        } else if (!fromOK && !iconIntersectsBlock(ic, portal.getToBlock()) &&
1025                                iconIntersectsBlock(ic, portal.getFromBlock())) {
1026                            fromOK = true;
1027                        } else {
1028                            icon = ic;
1029                        }
1030                    }
1031                    if (!toOK || !fromOK) {
1032                        _misplacedPortalIcon.add(icon);
1033                    }
1034                }
1035            }
1036        }
1037
1038        for (Map.Entry<NamedBean, Portal> entry : _signalMap.entrySet()) {
1039            if (entry.getValue() == null) {
1040                _unprotectingMast.add(entry.getKey());
1041            }
1042        }
1043
1044        if (oblocks.size() > 1) {
1045            if (_circuitMenu.getItemCount() <= 3) {
1046                _circuitMenu.removeAll();
1047                makeCircuitMenu();
1048            } else {
1049                makeToDoMenu();
1050            }
1051        } else {
1052            _circuitMenu.removeAll();
1053            makeNoOBlockMenu();
1054        }
1055    }   // end checkCircuits
1056
1057    protected boolean iconIntersectsBlock(Positionable icon, OBlock block) {
1058        Rectangle iconRect = icon.getBounds(new Rectangle());
1059        return rectIntersectsBlock(iconRect, block);
1060    }
1061
1062    protected boolean rectIntersectsBlock(Rectangle iconRect, OBlock block) {
1063        java.util.List<Positionable> list = getCircuitIcons(block);
1064        if (list.isEmpty()) {
1065            return false;
1066        }
1067        Rectangle rect = new Rectangle();
1068        for (Positionable comp : list) {
1069            if (CircuitBuilder.isTrack(comp)) {
1070                rect = comp.getBounds(rect);
1071                if (iconRect.intersects(rect)) {
1072                    return true;
1073                }
1074            }
1075        }
1076        return false;
1077    }
1078
1079    ////////////////////////// Frame Utilities //////////////////////////
1080    @Nonnull
1081    protected ArrayList<Positionable> getCircuitIcons(OBlock block) {
1082        // return empty array when block == null
1083        return _circuitMap.computeIfAbsent(block, k -> new ArrayList<>());
1084    }
1085
1086    @Nonnull
1087    protected List<PortalIcon> getPortalIcons(@Nonnull Portal portal) {
1088        return _portalIconMap.computeIfAbsent(portal, k -> new ArrayList<>());
1089    }
1090
1091    @Nonnull
1092    protected List<PositionableIcon> getSignalIconMap(@Nonnull NamedBean mast) {
1093        return _signalIconMap.computeIfAbsent(mast, k -> new ArrayList<>());
1094    }
1095
1096    protected HashMap<NamedBean, ArrayList<PositionableIcon>> getSignalIconMap() {
1097        return _signalIconMap;
1098    }
1099
1100    protected Portal getSignalPortal(@Nonnull NamedBean mast) {
1101        return _signalMap.get(mast);
1102    }
1103
1104    protected void putSignalPortal(@Nonnull NamedBean mast, Portal portal) {
1105        if (portal == null) {
1106            _signalMap.remove(mast);
1107        }
1108        _signalMap.put(mast, portal);
1109    }
1110
1111    /**
1112     * Remove block, but keep the track icons. Sets block reference in icon to
1113     * null.
1114     *
1115     * @param block the block to remove
1116     */
1117    protected void removeBlock(OBlock block) {
1118        java.util.List<Positionable> list = getCircuitIcons(block);
1119        for (Positionable pos : list) {
1120            if (pos instanceof IndicatorTrack) {
1121                ((IndicatorTrack) pos).setOccBlockHandle(null);
1122                _darkTrack.add((IndicatorTrack) pos);
1123            }
1124        }
1125        block.dispose();
1126        if (jmri.InstanceManager.getDefault(OBlockManager.class).getNamedBeanSet().size() < 2) {
1127            _editor.makeWarrantMenu(true, false);
1128        }
1129    }
1130
1131    protected String checkForPortals(@Nonnull OBlock block, String key) {
1132        StringBuffer sb = new StringBuffer();
1133        List<Portal> portals = block.getPortals();
1134        if (portals.isEmpty()) {
1135            sb.append(Bundle.getMessage("needPortal", block.getDisplayName(), Bundle.getMessage(key)));
1136        } else {
1137            for (Portal portal : portals) {
1138                if (portal.getToBlock() == null || portal.getFromBlock() == null) {
1139                    if (sb.length() > 0) {
1140                        sb.append("\n");
1141                    }
1142                    sb.append(Bundle.getMessage("portalNeedsBlock", portal.getName()));
1143                }
1144            }
1145            for (Portal portal : portals) {
1146                if (sb.length() > 0) {
1147                    sb.append("\n");
1148                }
1149                if (!block.equals(portal.getToBlock()) && !block.equals(portal.getFromBlock())) {
1150                    sb.append(Bundle.getMessage("portalNotInCircuit", portal.getName(), block.getDisplayName()));
1151                }
1152            }
1153        }
1154        return sb.toString();
1155    }
1156
1157    /**
1158     * Check that there is at least one PortalIcon. called from various _editFrame's
1159     * @param block check icons of this block
1160     * @param key properties key
1161     * @return true if at least one PortalIcon found
1162     */
1163    protected String checkForPortalIcons(@Nonnull OBlock block, String key) {
1164        StringBuffer sb = new StringBuffer();
1165        List<Portal> portals = block.getPortals();
1166        if (portals.isEmpty()) {
1167            sb.append(Bundle.getMessage("needPortal", block.getDisplayName(), Bundle.getMessage(key)));
1168        } else if (_editFrame instanceof EditPortalFrame) {
1169            for (Portal portal : portals) {
1170                String msg = ((EditPortalFrame)_editFrame).checkPortalIcons(portal, false, key);
1171                if (msg != null ) {
1172                    if (sb.length() > 0) {
1173                        sb.append("\n");
1174                    }
1175                    sb.append(msg);
1176                }
1177            }
1178        }
1179        // block has pPortals
1180        boolean ok = false;
1181        List<Positionable> list = getCircuitIcons(block);
1182        if (!list.isEmpty()) {
1183            for (Positionable pos : list) {
1184                if ((pos instanceof PortalIcon)) {
1185                    ok = true;
1186                }
1187            }
1188        }
1189        if (!ok) {
1190            if (sb.length() > 0) {
1191                sb.append("\n");
1192            }
1193            sb.append(Bundle.getMessage("needPortalIcons", block.getDisplayName(), Bundle.getMessage(key)));
1194        }
1195        return sb.toString();
1196    }
1197
1198    protected String checkForTrackIcons(@Nonnull OBlock block, String key) {
1199        StringBuilder sb = new StringBuilder();
1200        List<Positionable> list = getCircuitIcons(block);
1201        if (list.isEmpty()) {
1202            sb.append(Bundle.getMessage("needIcons", block.getDisplayName(), Bundle.getMessage(key)));
1203        } else {
1204            boolean ok = true;
1205            for (Positionable p : list) {
1206                PositionableLabel pos = (PositionableLabel) p;
1207                if (CircuitBuilder.isUnconvertedTrack(pos)) {
1208                    ok = false;
1209                    break;
1210                }
1211            }
1212            if (!ok) {
1213                sb.append(Bundle.getMessage("cantSaveIcon", block.getDisplayName()));
1214                sb.append("\n");
1215                sb.append(Bundle.getMessage("needIcons", block.getDisplayName(), Bundle.getMessage(key)));
1216            }
1217        }
1218        return  sb.toString();
1219    }
1220
1221    protected void deletePortalIcon(PortalIcon icon) {
1222        if (log.isDebugEnabled()) {
1223            log.debug("deletePortalIcon: {}", icon.getName());
1224        }
1225        Portal portal = icon.getPortal();
1226        if (portal != null) {
1227            getCircuitIcons(portal.getToBlock()).remove(icon);
1228            getCircuitIcons(portal.getFromBlock()).remove(icon);
1229            getPortalIcons(portal).remove(icon);
1230        }
1231        List<Positionable> selections = _editor.getSelectionGroup();
1232        if (selections != null) {
1233            _editor.getSelectionGroup().remove(icon);
1234        }
1235        _editor.repaint();
1236    }
1237
1238    /**
1239      * Check if the block being edited has all its track icons converted to indicator icons
1240     * If icons need conversion. ask if user wants to convert them
1241     * @param block OBlock to check
1242     * @param key properties key
1243     * @return true if all track icons are IndicatorTrack icons
1244     */
1245    protected boolean queryConvertTrackIcons(@Nonnull OBlock block, String key) {
1246        // since iconList will be modified, use a copy to find unconverted icons
1247        ArrayList<Positionable> list = new ArrayList<>(getCircuitIcons(block));
1248        String msg = null;
1249        if (list.isEmpty()) {
1250            msg = Bundle.getMessage("needIcons", block.getDisplayName(), Bundle.getMessage(key));
1251        } else {
1252            boolean needConversion = false;
1253            for (Positionable p : list) {
1254                PositionableLabel pos = (PositionableLabel) p;
1255                if (CircuitBuilder.isUnconvertedTrack(pos)) {
1256                    _editor.highlight(pos);
1257                    needConversion = true;
1258                    new ConvertDialog(this, pos, block);
1259                    _editor.highlight(null);
1260                }
1261            }
1262            if (!needConversion) {
1263                msg = Bundle.getMessage("noneNeedConversion");
1264            }
1265        }
1266        if (msg != null) {
1267            JmriJOptionPane.showMessageDialog(_editFrame, msg,
1268                    Bundle.getMessage("noIcons"), JmriJOptionPane.INFORMATION_MESSAGE);
1269            return false;
1270        } else {
1271            return true;
1272        }
1273    }
1274
1275
1276    //////////////// select - deselect track icons //////////
1277    /**
1278     * Select block's track icons for editing. filter for what icon types to show and highlight
1279     */
1280    private ArrayList<Positionable> makeSelectionGroup(OBlock block, boolean showPortal) {
1281        ArrayList<Positionable> group = new ArrayList<>();
1282        for (Positionable p : getCircuitIcons(block)) {
1283            if (p instanceof PortalIcon) {
1284                if (showPortal) {
1285                    ((PortalIcon) p).setStatus(PortalIcon.VISIBLE);
1286                    group.add(p);
1287                }
1288            } else if (!(p instanceof SignalMastIcon) && !(p instanceof SignalHeadIcon)) {
1289                group.add(p);
1290            }
1291        }
1292        return group;
1293    }
1294
1295    protected static boolean isTrack(Positionable pos) {
1296        if (pos instanceof IndicatorTrack) {
1297            return true;
1298        } else if (pos instanceof TurnoutIcon) {
1299            return true;
1300        } else if ((pos instanceof PortalIcon) ||
1301                (pos instanceof SignalMastIcon) || (pos instanceof SignalHeadIcon)) {
1302            return false;
1303        } else if (pos instanceof PositionableLabel) {
1304            PositionableLabel pl = (PositionableLabel) pos;
1305            if (pl.isIcon()) {
1306                NamedIcon icon = (NamedIcon) pl.getIcon();
1307                if (icon != null) {
1308                    String fileName = icon.getURL();
1309                    // getURL() returns Unix separatorChar= "/" even on windows
1310                    // so don't use java.io.File.separatorChar
1311                    if (fileName != null && (fileName.contains("/track/")
1312                            || (fileName.contains("/tracksegments/") && !fileName.contains("circuit")))) {
1313                        return true;
1314                    }
1315                }
1316            }
1317        }
1318        return false;
1319    }
1320
1321    private static boolean isUnconvertedTrack(Positionable pos) {
1322        if (pos instanceof IndicatorTrack || (pos instanceof PortalIcon) ||
1323                (pos instanceof SignalMastIcon) || (pos instanceof SignalHeadIcon)) {
1324            return false;
1325        } else if (pos instanceof TurnoutIcon) {
1326            return true;
1327        } else if (pos instanceof PositionableLabel) {
1328            PositionableLabel pl = (PositionableLabel) pos;
1329            if (pl.isIcon()) {
1330                NamedIcon icon = (NamedIcon) pl.getIcon();
1331                if (icon != null) {
1332                    String fileName = icon.getURL();
1333                    if (log.isDebugEnabled()) {
1334                        log.debug("isUnconvertedTrack Test: url= {}", fileName);
1335                    }
1336                    // getURL() returns Unix separatorChar= "/" even on windows
1337                    // so don't use java.io.File.separatorChar
1338                    if (fileName != null
1339                            && (fileName.contains("/track/") || fileName.contains("/tracksegments/"))
1340                            && !fileName.contains("circuit")) {
1341                        return true;
1342                    }
1343                }
1344            }
1345        }
1346        return false;
1347    }
1348
1349    /**
1350     * Can this track icon be added to the circuit? N.B. Be sure Positionable
1351     * pos passes isTrack() call
1352     */
1353    private boolean okToAdd(Positionable pos, OBlock editBlock) {
1354        if (pos instanceof IndicatorTrack) {
1355            OBlock block = ((IndicatorTrack) pos).getOccBlock();
1356            if (block != null) {
1357                if (!block.equals(editBlock)) {
1358                    int result = JmriJOptionPane.showConfirmDialog(_editor, java.text.MessageFormat.format(
1359                            Bundle.getMessage("iconBlockConflict"),
1360                            block.getDisplayName(), editBlock.getDisplayName()),
1361                            Bundle.getMessage("whichCircuit"), JmriJOptionPane.YES_NO_OPTION,
1362                            JmriJOptionPane.QUESTION_MESSAGE);
1363                    if (result == JmriJOptionPane.YES_OPTION) {
1364                        // move icon from block to editBlock
1365                        getCircuitIcons(block).remove(pos);
1366                        ((IndicatorTrack) pos).setOccBlockHandle(
1367                                InstanceManager.getDefault(NamedBeanHandleManager.class)
1368                                        .getNamedBeanHandle(editBlock.getSystemName(), editBlock));
1369                        return true;
1370                    }
1371                    return false;
1372                }
1373            }
1374        }
1375        return true;
1376    }
1377
1378    /**
1379     *
1380     * @param pos PortalIcon attempting a move. allow or disallow
1381     * @param x new x position
1382     * @param y new y position
1383     * @return allow, or not
1384     */
1385    protected boolean portalIconMove(PortalIcon pos, int x, int y) {
1386        if (_editFrame == null || (_editFrame instanceof EditPortalFrame)) {
1387            return true;
1388        }
1389        Rectangle iconRect = pos.getBounds(new Rectangle());
1390        iconRect.x = x;
1391        iconRect.y = y;
1392        OBlock block = _editFrame._homeBlock;
1393        if (rectIntersectsBlock(iconRect, block)) {
1394            Portal port = pos.getPortal();
1395            if (block.equals(port.getToBlock())) {
1396                block = port.getFromBlock();
1397            } else {
1398                block = port.getToBlock();
1399            }
1400            if (block == null || rectIntersectsBlock(iconRect, block)) {
1401                return true;
1402            }
1403        }
1404        JmriJOptionPane.showMessageDialog(_editFrame,
1405                Bundle.getMessage("moveOffBlock", block.getDisplayName(), pos.getNameString()),
1406                Bundle.getMessage("editCiruit"), JmriJOptionPane.INFORMATION_MESSAGE);
1407        return false;
1408    }
1409
1410    /**
1411     * ************************** Mouse ************************
1412     */
1413    ArrayList<Positionable> _saveSelectionGroup;
1414
1415    /**
1416     * Keep selections when editing. Editor calls at entry to mousePressed().
1417     * CircuitBuilder keeps is own concept of what is "selected".
1418     *
1419     * @param selectionGroup the selection group to save
1420     * @return true if retaining a reference to a frame
1421     */
1422    protected boolean saveSelectionGroup(ArrayList<Positionable> selectionGroup) {
1423        _saveSelectionGroup = selectionGroup;
1424        return _editFrame != null;
1425    }
1426
1427    /**
1428     * Make note of selection.
1429     *
1430     * @param event     the triggering event
1431     * @param selection the selection
1432     * @return true
1433     */
1434    protected boolean doMousePressed(JmriMouseEvent event, Positionable selection) {
1435        _selection = selection;
1436        return true;
1437    }
1438
1439    /**
1440     * If CircuitBuilder is in progress, restore what editor nulls.
1441     *
1442     * @param event     the triggering event
1443     * @return true if the selection group is restored; false otherwise
1444     */
1445    protected boolean doMousePressed(JmriMouseEvent event) {
1446        if (_editFrame != null) {
1447            _editFrame.toFront();
1448            _editor.setSelectionGroup(_saveSelectionGroup);
1449        } else {
1450            return false;
1451        }
1452        return true;
1453    }
1454
1455    public boolean doMouseReleased(Positionable selection, boolean dragging) {
1456        if (_editFrame != null) {
1457            if (_editFrame instanceof EditPortalFrame) {
1458                if (selection instanceof PortalIcon
1459                        && getCircuitIcons(_editFrame._homeBlock).contains(selection)) {
1460                    PortalIcon icon = (PortalIcon)selection;
1461                    if (dragging) {
1462                        Portal portal = icon.getPortal();
1463                        ((EditPortalFrame)_editFrame).checkPortalIcons(portal, true, null);
1464                    } else {
1465                        ((EditPortalFrame)_editFrame).setSelected(icon);
1466                    }
1467                }
1468            }
1469            return true;
1470        } else {
1471            if (_selection != null) {
1472                if (_selection instanceof PortalIcon) {
1473                    PortalIcon pos = (PortalIcon)_selection;
1474                    Portal portal = pos.getPortal();
1475                    if (portal != null) {
1476                        OBlock block = portal.getToBlock();
1477                        if (block == null) {
1478                            block = portal.getFromBlock();
1479                        }
1480                        editPortalError(block, portal, pos);
1481                    }
1482                }
1483            }
1484        }
1485        return false;
1486    }
1487
1488    // Return true if CircuitBuilder is editing
1489    protected boolean doMouseClicked(List<Positionable> selections, JmriMouseEvent event) {
1490        if (_editFrame != null) {
1491            if (selections != null && selections.size() > 0) {
1492                ArrayList<Positionable> tracks = new ArrayList<>();
1493                Iterator<Positionable> iter = selections.iterator();
1494                if (_editFrame instanceof EditCircuitFrame) {
1495                    while (iter.hasNext()) {
1496                        Positionable pos = iter.next();
1497                        if (isTrack(pos)) {
1498                            tracks.add(pos);
1499                        }
1500                    }
1501                } else if (_editFrame instanceof EditCircuitPaths) {
1502                    while (iter.hasNext()) {
1503                        Positionable pos = iter.next();
1504                        if (isTrack(pos) || pos instanceof PortalIcon) {
1505                            tracks.add(pos);
1506                        }
1507                    }
1508                } else if (_editFrame instanceof EditSignalFrame) {
1509                    while (iter.hasNext()) {
1510                        Positionable pos = iter.next();
1511                        if (pos instanceof PortalIcon || pos instanceof SignalMastIcon || pos instanceof SignalHeadIcon) {
1512                            tracks.add(pos);
1513                        }
1514                    }
1515                } else {
1516                    while (iter.hasNext()) {
1517                        Positionable pos = iter.next();
1518                        if (pos instanceof PortalIcon) {
1519                            tracks.add(pos);
1520                        }
1521                    }
1522                }
1523                if (tracks.size() > 0) {
1524                    Positionable selection;
1525                    if (tracks.size() == 1) {
1526                        selection = tracks.get(0);
1527                    } else {
1528                        selection = getSelection(tracks);
1529                    }
1530                    if (_editFrame instanceof EditCircuitPaths && event.isShiftDown() && !event.isControlDown()) {
1531                        selection.doMouseClicked(event);
1532                    }
1533                    handleSelection(selection, event);
1534                }
1535                _editFrame.toFront();
1536            }
1537            return true;
1538        }
1539        return false;
1540    }
1541
1542    private Positionable getSelection(List<Positionable> tracks) {
1543        if (tracks.size() > 0) {
1544            if (tracks.size() == 1) {
1545                return tracks.get(0);
1546            }
1547            if (tracks.size() > 1) {
1548                String[] selects = new String[tracks.size()];
1549                Iterator<Positionable> iter = tracks.iterator();
1550                int i = 0;
1551                while (iter.hasNext()) {
1552                    selects[i++] = iter.next().getNameString();
1553                }
1554                Object select = JmriJOptionPane.showInputDialog(_editor, Bundle.getMessage("multipleSelections"),
1555                        Bundle.getMessage("QuestionTitle"), JmriJOptionPane.QUESTION_MESSAGE,
1556                        null, selects, null);
1557                if (select != null) {
1558                    iter = tracks.iterator();
1559                    while (iter.hasNext()) {
1560                        Positionable pos = iter.next();
1561                        if (((String) select).equals(pos.getNameString())) {
1562                            return pos;
1563                        }
1564                    }
1565                }
1566            }
1567        }
1568        return null;
1569    }
1570
1571    /**
1572     * Prevent dragging when CircuitBuilder is in progress, except for
1573     * PortalIcon.
1574     *
1575     * @param selection the item(s) being dragged
1576     * @param event     the triggering event
1577     * @return true to prevent dragging; false otherwise
1578     */
1579    public boolean doMouseDragged(Positionable selection, JmriMouseEvent event) {
1580        if (_editFrame != null) {
1581            if (selection instanceof PortalIcon) {
1582                if (_editFrame instanceof EditPortalFrame) {
1583                    ((EditPortalFrame)_editFrame).setSelected((PortalIcon)selection);
1584                    return false;  // OK to drag portal icon
1585                } else if (_editFrame instanceof EditPortalDirection) {
1586                    return false;  // OK to drag portal arrow
1587                }
1588            } if (selection instanceof SignalMastIcon || selection instanceof SignalHeadIcon) {
1589                if (_editFrame instanceof EditSignalFrame) {
1590                    return false;  // OK to drag signal
1591                }
1592            }
1593            return true;     // no dragging when editing
1594        }
1595        return false;
1596    }
1597
1598    /**
1599     * Second call needed to only drag the portal icon and not entire selection
1600     *
1601     * @return true if portal frame is open
1602     */
1603    public boolean dragPortal() {
1604        return (_editFrame instanceof EditPortalFrame || _editFrame instanceof EditPortalDirection);
1605    }
1606
1607    /*
1608     * For the param, selection, Add to or delete from selectionGroup.
1609     * If not there, add.
1610     * If there, delete.
1611     */
1612    private void handleSelection(Positionable selection, JmriMouseEvent event) {
1613        if (_editFrame == null) {
1614            return;
1615        }
1616        if (_editFrame instanceof EditCircuitFrame) {
1617            EditCircuitFrame editCircuitFrame = (EditCircuitFrame)_editFrame;
1618            ArrayList<Positionable> selectionGroup = _editor.getSelectionGroup();
1619            if (selectionGroup == null) {
1620                selectionGroup = new ArrayList<>();
1621            }
1622            if (selectionGroup.contains(selection)) {
1623                selectionGroup.remove(selection);
1624            } else if (okToAdd(selection, editCircuitFrame._homeBlock)) {
1625                selectionGroup.add(selection);
1626            }
1627            editCircuitFrame.updateIconList(selectionGroup);
1628            _editor.setSelectionGroup(selectionGroup);
1629        } else if (_editFrame instanceof EditCircuitPaths) {
1630            EditCircuitPaths editPathsFrame = (EditCircuitPaths)_editFrame;
1631            editPathsFrame.updateSelections(!event.isShiftDown(), selection);
1632        } else if (_editFrame instanceof EditPortalFrame) {
1633            EditPortalFrame editPortalFrame = (EditPortalFrame)_editFrame;
1634            if (selection instanceof PortalIcon && getCircuitIcons(_editFrame._homeBlock).contains(selection)) {
1635                editPortalFrame.setSelected((PortalIcon)selection);
1636            }
1637        } else if (_editFrame instanceof EditPortalDirection) {
1638            EditPortalDirection editDirectionFrame = (EditPortalDirection)_editFrame;
1639            if (selection instanceof PortalIcon) {
1640                editDirectionFrame.setPortalIcon((PortalIcon)selection, true);
1641            }
1642        } else if (_editFrame instanceof EditSignalFrame) {
1643            EditSignalFrame editSignalFrame = (EditSignalFrame)_editFrame;
1644            editSignalFrame.setSelected((PositionableIcon)selection);
1645        }
1646    }
1647
1648    protected void closeCBWindow() {
1649        if (_cbFrame !=null) {
1650            _cbFrame.dispose();
1651        }
1652        if (_editFrame != null) {
1653            _editFrame.closingEvent(true, null);
1654        }
1655    }
1656
1657    static int NONE = 0;
1658    static int OBLOCK = 1;
1659    static int PORTAL = 2;
1660    static int OPATH = 3;
1661    static int ARROW = 4;
1662    static int SIGNAL = 5;
1663    class CBFrame extends jmri.util.JmriJFrame implements ListSelectionListener  {
1664
1665        ButtonGroup _buttonGroup = new ButtonGroup();
1666        int _which = 0;
1667        JRadioButton _newCircuitButton = makeButton("newCircuitItem", NONE);
1668
1669        CBFrame(String title) {
1670            super(false, false);
1671            setTitle(title);
1672            addHelpMenu("package.jmri.jmrit.display.CircuitBuilder", true);
1673
1674            _blockTable.getSelectionModel().addListSelectionListener(this);
1675
1676            JPanel contentPane = new JPanel();
1677            contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
1678            javax.swing.border.Border padding = BorderFactory.createEmptyBorder(10, 5, 4, 5);
1679            contentPane.setBorder(padding);
1680
1681            JPanel panel0 = new JPanel();
1682            panel0.setLayout(new BoxLayout(panel0, BoxLayout.X_AXIS));
1683            JPanel panel = new JPanel();
1684            panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
1685            panel.add(_newCircuitButton);
1686            panel.add(makeButton("editCircuitItem", OBLOCK));
1687            panel.add(makeButton("editPortalsItem", PORTAL));
1688            panel.add(makeButton("editCircuitPathsItem", OPATH));
1689            panel.add(makeButton("editDirectionItem", ARROW));
1690            panel.add(makeButton("editSignalItem", SIGNAL));
1691            _newCircuitButton.setSelected(true);
1692            panel0.add(panel);
1693
1694            panel = new JPanel();
1695            panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
1696            panel.add(new JScrollPane(_blockTable));
1697            panel0.add(panel);
1698            contentPane.add(panel0);
1699
1700            panel0 = new JPanel();
1701            panel0.setLayout(new BoxLayout(panel0, BoxLayout.X_AXIS));
1702            panel = new JPanel();
1703            JButton button = new JButton(Bundle.getMessage("ButtonOpen"));
1704            button.addActionListener((ActionEvent event) -> {
1705                if (editingOK()) {
1706                    setCurrentBlock();
1707                    if (_which == NONE) {
1708                        newCircuit();
1709                    } else if (_which == OBLOCK) {
1710                        editCircuit("editCircuitItem", false);
1711                    } else if (_which == PORTAL) {
1712                        editPortals("editPortalsItem", false);
1713                    } else if (_which == OPATH) {
1714                        editCircuitPaths("editCircuitPathsItem", false);
1715                    } else if (_which == ARROW) {
1716                        editPortalDirection("editDirectionItem", false);
1717                    } else if (_which == SIGNAL) {
1718                        editSignalFrame("editSignalItem", false);
1719                    }
1720                }
1721            });
1722            panel.add(button);
1723
1724            button = new JButton(Bundle.getMessage("ButtonDone"));
1725            button.addActionListener((ActionEvent a) -> {
1726                _currentBlock = null;
1727                this.dispose();
1728            });
1729            panel.add(button);
1730            panel.setMaximumSize(new Dimension(300, panel.getPreferredSize().height));
1731            panel0.add(panel);
1732            contentPane.add(panel0);
1733
1734            setContentPane(contentPane);
1735            _blockTable.clearSelection();
1736            pack();
1737            InstanceManager.getDefault(jmri.util.PlaceWindow.class).nextTo(_editor, null, this);
1738            setVisible(true);
1739        }
1740
1741        @Override
1742        public void valueChanged(ListSelectionEvent e) {
1743            setCurrentBlock();
1744        }
1745        private void setCurrentBlock() {
1746            int row = _blockTable.getSelectedRow();
1747            if (row >= 0) {
1748                row = _blockTable.convertRowIndexToModel(row);
1749                _currentBlock = _oblockModel.getBeanAt(row);
1750            } else {
1751                _currentBlock = null;
1752            }
1753        }
1754
1755        JRadioButton makeButton(String title, int which) {
1756            JRadioButton button = new JRadioButton(Bundle.getMessage(title));
1757            button.addActionListener((ActionEvent event) -> _which = which);
1758            _buttonGroup.add(button);
1759            return button;
1760        }
1761
1762        @Override
1763        public void dispose() {
1764            _cbFrame = null;
1765            super.dispose();
1766        }
1767    }
1768
1769    ////////////////////////// static methods //////////////////////////
1770
1771    protected static void doSize(JComponent comp, int max, int min) {
1772        Dimension dim = comp.getPreferredSize();
1773        dim.width = max;
1774        comp.setMaximumSize(dim);
1775        dim.width = min;
1776        comp.setMinimumSize(dim);
1777    }
1778
1779    protected static JPanel makeTextBoxPanel(boolean vertical, JTextField textField, String label,
1780            boolean editable, String tooltip) {
1781        JPanel panel = makeBoxPanel(vertical, textField, label, tooltip);
1782        textField.setEditable(editable);
1783        textField.setBackground(Color.white);
1784        return panel;
1785    }
1786
1787    protected static JPanel makeBoxPanel(boolean vertical, JComponent textField, String label,
1788            String tooltip) {
1789        JPanel panel = new JPanel();
1790        panel.setLayout(new java.awt.GridBagLayout());
1791        java.awt.GridBagConstraints c = new java.awt.GridBagConstraints();
1792        c.gridwidth = 1;
1793        c.gridheight = 1;
1794        c.gridx = 0;
1795        c.gridy = 0;
1796        c.weightx = 1.0;
1797        JLabel l = new JLabel(Bundle.getMessage(label));
1798        if (vertical) {
1799            c.anchor = java.awt.GridBagConstraints.SOUTH;
1800            l.setAlignmentX(JComponent.CENTER_ALIGNMENT);
1801            textField.setAlignmentX(JComponent.CENTER_ALIGNMENT);
1802        } else {
1803            c.anchor = java.awt.GridBagConstraints.EAST;
1804            l.setAlignmentX(JComponent.LEFT_ALIGNMENT);
1805            textField.setAlignmentX(JComponent.RIGHT_ALIGNMENT);
1806        }
1807        panel.add(l, c);
1808        if (vertical) {
1809            c.anchor = java.awt.GridBagConstraints.NORTH;
1810            c.gridy = 1;
1811        } else {
1812            c.anchor = java.awt.GridBagConstraints.WEST;
1813            c.gridx = 1;
1814        }
1815        c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
1816        doSize(textField, 9000, 200);    // default
1817        panel.add(textField, c);
1818        if (tooltip != null) {
1819            textField.setToolTipText(Bundle.getMessage(tooltip));
1820            l.setToolTipText(Bundle.getMessage(tooltip));
1821            panel.setToolTipText(Bundle.getMessage(tooltip));
1822        }
1823        return panel;
1824    }
1825
1826    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CircuitBuilder.class);
1827
1828}