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