001package jmri.jmrit.display.controlPanelEditor;
002
003import java.awt.Color;
004import java.awt.Dimension;
005import java.awt.datatransfer.DataFlavor;
006import java.awt.datatransfer.UnsupportedFlavorException;
007import java.io.IOException;
008import java.util.ArrayList;
009import java.util.Iterator;
010import java.util.List;
011import java.util.SortedSet;
012
013import javax.swing.BorderFactory;
014import javax.swing.Box;
015import javax.swing.BoxLayout;
016import javax.swing.JButton;
017import javax.swing.JComponent;
018import javax.swing.JLabel;
019import javax.swing.JPanel;
020import javax.swing.JScrollPane;
021import javax.swing.JTextField;
022import javax.swing.event.ListSelectionEvent;
023import javax.swing.event.ListSelectionListener;
024
025import jmri.InstanceManager;
026import jmri.jmrit.catalog.DragJLabel;
027import jmri.jmrit.catalog.NamedIcon;
028import jmri.jmrit.display.Editor;
029import jmri.jmrit.display.Positionable;
030import jmri.jmrit.logix.OBlock;
031import jmri.jmrit.logix.OBlockManager;
032import jmri.jmrit.logix.Portal;
033import jmri.jmrit.logix.PortalManager;
034import jmri.util.swing.JmriJOptionPane;
035
036/**
037 *
038 * @author Pete Cressman Copyright: Copyright (c) 2011
039 */
040public class EditPortalFrame extends EditFrame implements ListSelectionListener {
041
042    private PortalList _portalList;
043    private JTextField _portalName;
044    private Portal _currentPortal;
045
046    /* Ctor for fix a portal error  */
047    public EditPortalFrame(String title, CircuitBuilder parent, OBlock block, Portal portal, PortalIcon icon) {
048        this(title, parent, block);
049        String name = portal.getName();
050        _portalName.setText(name);
051
052        StringBuilder sb = new StringBuilder();
053        if (icon != null) {
054            setSelected(icon);
055        } else {
056            sb.append(Bundle.getMessage("portalHasNoIcon", name)); 
057            sb.append("\n");
058        }
059        if (_canEdit) {
060            String msg = _parent.checkForPortals(block, "BlockPaths");
061            if (msg.length() > 0) {
062                sb.append(msg);
063                sb.append("\n");
064                sb.append(Bundle.getMessage("portIconPosition1"));
065                sb.append("\n");
066                sb.append(Bundle.getMessage("portIconPosition2"));
067                sb.append("\n");
068            } else {
069                msg = _parent.checkForPortalIcons(block, "DirectionArrow");
070                if (msg.length() > 0) {
071                    sb.append(msg);
072                    sb.append("\n");
073                }
074            }
075        }
076        if (sb.toString().length() > 0) {
077            JmriJOptionPane.showMessageDialog(EditPortalFrame.this, sb.toString(), 
078                    Bundle.getMessage("incompleteCircuit"), JmriJOptionPane.INFORMATION_MESSAGE);
079        }
080    }
081
082    public EditPortalFrame(String title, CircuitBuilder parent, OBlock block) {
083        super(title, parent, block);
084        pack();
085        String msg = _parent.checkForTrackIcons(block, "BlockPortals");
086        if (msg.length() > 0) {
087            _canEdit = false;
088            JmriJOptionPane.showMessageDialog(EditPortalFrame.this, msg,
089                    Bundle.getMessage("incompleteCircuit"), JmriJOptionPane.INFORMATION_MESSAGE);
090        }
091    }
092
093    @Override
094    protected JPanel makeContentPanel() {
095        JPanel portalPanel = new JPanel();
096        portalPanel.setLayout(new BoxLayout(portalPanel, BoxLayout.Y_AXIS));
097
098        JPanel panel = new JPanel();
099        panel.add(new JLabel(Bundle.getMessage("PortalTitle", _homeBlock.getDisplayName())));
100        portalPanel.add(panel);
101        _portalName = new JTextField();
102        _portalList = new PortalList(_homeBlock, this);
103        _portalList.addListSelectionListener(this);
104        portalPanel.add(new JScrollPane(_portalList));
105
106        JButton clearButton = new JButton(Bundle.getMessage("buttonClearSelection"));
107        clearButton.addActionListener(a -> clearListSelection());
108        panel = new JPanel();
109        panel.add(clearButton);
110        portalPanel.add(panel);
111        portalPanel.add(Box.createVerticalStrut(STRUT_SIZE));
112
113        panel = new JPanel();
114        panel.add(CircuitBuilder.makeTextBoxPanel(
115                false, _portalName, "portalName", true, null));
116        _portalName.setPreferredSize(new Dimension(300, _portalName.getPreferredSize().height));
117        _portalName.setToolTipText(Bundle.getMessage("TooltipPortalName"));
118        portalPanel.add(panel);
119
120        panel = new JPanel();
121        JButton changeButton = new JButton(Bundle.getMessage("buttonChangeName"));
122        changeButton.addActionListener(a -> changePortalName());
123        changeButton.setToolTipText(Bundle.getMessage("ToolTipChangeName"));
124        panel.add(changeButton);
125
126        JButton deleteButton = new JButton(Bundle.getMessage("buttonDeletePortal"));
127        deleteButton.addActionListener(a -> deletePortal());
128        deleteButton.setToolTipText(Bundle.getMessage("ToolTipDeletePortal"));
129        panel.add(deleteButton);
130        portalPanel.add(panel);
131
132        panel = new JPanel();
133        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
134        portalPanel.add(Box.createVerticalStrut(STRUT_SIZE));
135        JLabel l = new JLabel(Bundle.getMessage("enterNameToDrag"));
136        l.setAlignmentX(JComponent.LEFT_ALIGNMENT);
137        panel.add(l);
138        l = new JLabel(Bundle.getMessage("dragNewIcon"));
139        l.setAlignmentX(JComponent.LEFT_ALIGNMENT);
140        panel.add(l);
141        panel.add(Box.createVerticalStrut(STRUT_SIZE / 2));
142        l = new JLabel(Bundle.getMessage("selectPortal"));
143        l.setAlignmentX(JComponent.LEFT_ALIGNMENT);
144        panel.add(l);
145        panel.add(Box.createVerticalStrut(STRUT_SIZE / 2));
146        l = new JLabel(Bundle.getMessage("portIconPosition1"));
147        l.setAlignmentX(JComponent.LEFT_ALIGNMENT);
148        panel.add(l);
149        l = new JLabel(Bundle.getMessage("portIconPosition2"));
150        l.setAlignmentX(JComponent.LEFT_ALIGNMENT);
151        panel.add(l);
152        JPanel p = new JPanel();
153        p.add(panel);
154        portalPanel.add(p);
155
156        portalPanel.add(makeDndIconPanel());
157        portalPanel.add(Box.createVerticalStrut(STRUT_SIZE));
158        portalPanel.add(makeDoneButtonPanel());
159        return portalPanel;
160    }
161
162    @Override
163    protected void clearListSelection() {
164        _portalList.clearSelection();
165        _portalName.setText(null);
166        _parent._editor.highlight(null);
167    }
168
169    @Override
170    public void valueChanged(ListSelectionEvent e) {
171        if (askForNameChange()) {
172            return;
173        }
174        Portal portal = _portalList.getSelectedValue();
175        if (portal != null) {
176            _portalName.setText(portal.getName());
177            hightLightIcon(portal);
178            _currentPortal = portal;
179        } else {
180            _portalName.setText(null);
181        }
182    }
183
184    private void hightLightIcon(Portal portal) {
185        _parent._editor.highlight(null);
186        List<PortalIcon> piArray = _parent.getPortalIcons(portal);
187        for (PortalIcon pi : piArray) {
188            _parent._editor.highlight(pi);
189        }
190    }
191
192    private boolean askForNameChange() {
193        String name = _portalName.getText();
194        if (_currentPortal != null && !_currentPortal.getName().equals(name)) {
195            if (name.length() > 0) {
196                int answer = JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("changeOrCancel", 
197                        _currentPortal.getName(), name, Bundle.getMessage("BeanNamePortal")),
198                        Bundle.getMessage("makePortal"), JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.QUESTION_MESSAGE);
199                if (answer == JmriJOptionPane.YES_OPTION) {
200                    setName(_currentPortal, name);
201                    return true;
202                }
203            }
204        }
205        return false;
206    }
207
208    protected void setSelected(PortalIcon icon) {
209        if (!canEdit()) {
210            return;
211        }
212        Portal portal = icon.getPortal();
213        if (portal != null ) {
214            if (!portal.equals(_portalList.getSelectedValue())) {
215                _parent._editor.highlight(null);
216            }
217            List<PortalIcon> piArray = _parent.getPortalIcons(portal);
218            for (PortalIcon pi : piArray) {
219                _parent._editor.highlight(pi);
220            }
221        }
222        _portalList.setSelectedValue(portal, true);
223    }
224
225    /*
226     * *********************** end setup *************************
227     */
228
229    private void changePortalName() {
230        Portal portal = _portalList.getSelectedValue();
231        String name = _portalName.getText();
232        if (portal == null || name == null || name.trim().length() == 0) {
233            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("changePortalName", Bundle.getMessage("buttonChangeName")),
234                    Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE);
235            return;
236        }
237        setName(portal, name);
238    }
239
240    private void setName(Portal portal, String name) {
241        String msg = portal.setName(name);
242        if (msg == null) {
243            _portalList.dataChange();
244            hightLightIcon(portal);
245        } else {
246            JmriJOptionPane.showMessageDialog(this, msg,
247                    Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE);
248        }
249    }
250    private void deletePortal() {
251        String name = _portalName.getText();
252        if (name == null || name.length() == 0) {
253            return;
254        }
255        Portal portal = _portalList.getSelectedValue();
256        if (portal == null) {
257            PortalManager portalMgr = InstanceManager.getDefault(jmri.jmrit.logix.PortalManager.class);
258            portal = portalMgr.getPortal(name);
259        }
260        if (portal == null) {
261            return;
262        }        
263        if (!_suppressWarnings) {
264            int val = JmriJOptionPane.showOptionDialog(this, Bundle.getMessage("confirmPortalDelete", portal.getName()),
265                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.DEFAULT_OPTION,
266                    JmriJOptionPane.QUESTION_MESSAGE, null,
267                    new Object[]{Bundle.getMessage("ButtonYes"),
268                            Bundle.getMessage("ButtonYesPlus"),
269                            Bundle.getMessage("ButtonNo"),},
270                    Bundle.getMessage("ButtonNo")); // default NO
271            if ( val == 2 || val == -1 ) { // array position 2 or dialog cancelled
272                return;
273            }
274            if (val == 1) { // array position 1 suppress future warnings
275                _suppressWarnings = true;
276            }
277        }
278        if (portal.dispose()) {
279            _portalList.dataChange();
280            _portalName.setText(null);
281            OBlock oppBlock = portal.getOpposingBlock(_homeBlock);
282            ArrayList<PortalIcon> removeList = new ArrayList<>(_parent.getPortalIcons(portal));
283            for (PortalIcon icon : removeList) {
284                _parent.getCircuitIcons(oppBlock).remove(icon);
285                icon.remove();  // will call _parent.deletePortalIcon(icon)
286            }
287        }
288    }
289
290    @Override
291    protected void closingEvent(boolean close) {
292        StringBuffer sb = new StringBuffer();
293        String msg = _parent.checkForPortals(_homeBlock, "BlockPaths");
294        if(msg.length() > 0) {
295            sb.append(msg);
296            sb.append("\n");
297        }
298        if (_canEdit) {
299            msg = _parent.checkForPortalIcons(_homeBlock, "BlockPaths");
300            if(msg.length() > 0) {
301                sb.append(msg);
302                sb.append("\n");
303            }
304        }
305        closingEvent(close, sb.toString());
306    }
307
308    protected String checkPortalIcons(Portal portal, boolean moved, String key) {
309        List<PortalIcon> iconMap = _parent.getPortalIcons(portal);
310        if (iconMap.isEmpty()) {
311            return Bundle.getMessage("noPortalIcon", portal.getName(), Bundle.getMessage(key));
312        }
313
314        String name = portal.getName();
315        boolean homeBlockCovered = false;
316        boolean adjacentBlockCovered = false;
317        OBlock adjacentBlock = null;
318        for (PortalIcon icon : iconMap) {
319            Portal p = icon.getPortal();
320            if (p == null) {
321                _parent.deletePortalIcon(icon);
322                log.error("Removed PortalIcon without Portal");
323            } else {
324                OBlock fromBlock = portal.getFromBlock();
325                OBlock toBlock = portal.getToBlock();
326                if (!_homeBlock.equals(fromBlock) && !_homeBlock.equals(toBlock)) {
327                    log.error("HomeBlock \"{}\" does not know {}", _homeBlock.getDisplayName(), portal.getDescription());
328                    return showIntersectMessage(_homeBlock, portal, moved); 
329                }
330                boolean homeCovered = _parent.iconIntersectsBlock(icon, _homeBlock);
331
332                if (_homeBlock.equals(fromBlock)) {
333                    adjacentBlock = toBlock;
334                } else {
335                    adjacentBlock = fromBlock;
336                }
337                boolean adjacentCovered = adjacentBlock != null &&_parent.iconIntersectsBlock(icon, adjacentBlock);
338
339                OBlock block = findAdjacentBlock(icon);
340                if (adjacentBlock == null) { // maybe first time
341                    if (block != null) {
342                        boolean valid;
343                        if (_homeBlock.equals(fromBlock)) {
344                            valid = portal.setToBlock(block, true);
345                        } else {
346                            valid = portal.setFromBlock(block, true);
347                        }
348                        _portalList.dataChange();
349                        log.debug("Adjacent block change of null to {} is {} valid.",
350                                block.getDisplayName(), (valid?"":"NOT"));
351                        adjacentBlock = block;
352                        if (homeCovered) {
353                            return null;    // home and adjacent covered by icon
354                        }
355                        adjacentCovered = true;
356                    }
357                } else {
358                    if (block != null) {
359                        if (moved) {
360                            if (!block.equals(adjacentBlock)) {
361                                int result = JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("repositionPortal",
362                                        name, _homeBlock.getDisplayName(), block.getDisplayName()),
363                                        Bundle.getMessage("makePortal"), JmriJOptionPane.YES_NO_OPTION,
364                                        JmriJOptionPane.QUESTION_MESSAGE);
365                                if (result == JmriJOptionPane.YES_OPTION) {
366                                    boolean valid;
367                                    if (_homeBlock.equals(fromBlock)) {
368                                        valid = portal.setToBlock(block, true);
369                                    } else {
370                                        valid = portal.setFromBlock(block, true);
371                                    }
372                                    _portalList.dataChange();
373                                    log.debug("Adjacent block change of {} to {} is {} valid.",
374                                            adjacentBlock.getDisplayName(), block.getDisplayName(), (valid?"":"NOT"));
375                                    adjacentBlock = block;
376                                    if (homeCovered) {
377                                        return null;    // home and adjacent covered by icon
378                                    }
379                                }
380                            }
381                        } else {
382                            if (!block.equals(adjacentBlock)) {
383                                log.error("Icon NOT moved, but Adjacent block change of {} to {}!",
384                                         adjacentBlock.getDisplayName(), block.getDisplayName());
385                            }
386                        }
387                        adjacentCovered = true;
388                    } else {
389                        adjacentCovered = false;
390                    }
391                }
392                if (homeCovered) {
393                    homeBlockCovered = true;
394                }
395                if (adjacentCovered) {
396                    adjacentBlockCovered = true;
397                }
398                log.debug("checkPortalIcons for {} homeCovered= {} adjacentCovered= {}", name, homeBlockCovered, adjacentBlockCovered);
399            }
400        }
401        if (!homeBlockCovered) {
402            return showIntersectMessage(_homeBlock, portal, moved); 
403        }
404        if (!adjacentBlockCovered) {
405            return showIntersectMessage(adjacentBlock, portal, moved); 
406        }
407        return null;
408    }
409
410    private String showIntersectMessage(OBlock block, Portal portal, boolean moved) {
411        String msg = null;
412        if (block == null) {
413            msg = Bundle.getMessage("icondNeedsAdjacent", portal.getDescription());
414        } else {
415            List<Positionable> list = _parent.getCircuitIcons(block);
416            if (list.isEmpty()) {
417                msg = Bundle.getMessage("needIcons", block.getDisplayName(), Bundle.getMessage("BlockPortals"));
418            } else {
419                msg = Bundle.getMessage("iconNotOnBlock", block.getDisplayName(), portal.getDescription());
420            }
421        }
422        if (moved) {
423            JmriJOptionPane.showMessageDialog(this, msg,
424                    Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE);
425        }
426        return msg;
427    }
428
429    /*
430     * If icon is on the home block, find another intersecting block.
431     */
432    private OBlock findAdjacentBlock(PortalIcon icon) {
433        ArrayList<OBlock> neighbors = new ArrayList<>();
434        OBlockManager manager = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class);
435        SortedSet<OBlock> oblocks = manager.getNamedBeanSet();
436        for (OBlock block : oblocks) {
437            if (block.equals(_homeBlock)) {
438                continue;
439            }
440            if (_parent.iconIntersectsBlock(icon, block)) {
441                neighbors.add(block);
442            }
443        }
444        OBlock block = null;
445        if (neighbors.size() == 1) {
446            block = neighbors.get(0);
447        } else if (neighbors.size() > 1) {
448            // show list
449            block = neighbors.get(0);
450            String[] selects = new String[neighbors.size()];
451            Iterator<OBlock> iter = neighbors.iterator();
452            int i = 0;
453            while (iter.hasNext()) {
454                selects[i++] = iter.next().getDisplayName();
455            }
456            Object select = JmriJOptionPane.showInputDialog(this, Bundle.getMessage("multipleBlockSelections",
457                    _homeBlock.getDisplayName()), Bundle.getMessage("QuestionTitle"),
458                    JmriJOptionPane.QUESTION_MESSAGE, null, selects, null);
459            if (select != null) {
460                iter = neighbors.iterator();
461                while (iter.hasNext()) {
462                    block = iter.next();
463                    if (((String) select).equals(block.getDisplayName())) {
464                        break;
465                    }
466                }
467            }
468        }
469/*        if (log.isDebugEnabled()) {
470            log.debug("findAdjacentBlock: neighbors.size()= {} return {}",
471                    neighbors.size(), (block == null ? "null" : block.getDisplayName()));
472        }*/
473        return block;
474    }
475
476    //////////////////////////// DnD ////////////////////////////
477    protected JPanel makeDndIconPanel() {
478        JPanel dndPanel = new JPanel();
479        dndPanel.setLayout(new BoxLayout(dndPanel, BoxLayout.Y_AXIS));
480
481        JPanel p = new JPanel();
482        JLabel l = new JLabel(Bundle.getMessage("dragIcon"));
483        p.add(l);
484        dndPanel.add(p);
485
486        NamedIcon icon = _parent._editor.getPortalIconMap().get(PortalIcon.VISIBLE);
487        JPanel panel = new JPanel();
488        panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black),
489                Bundle.getMessage("BeanNamePortal")));
490        try {
491            JLabel label = new IconDragJLabel(new DataFlavor(Editor.POSITIONABLE_FLAVOR));
492            label.setIcon(icon);
493            label.setName(Bundle.getMessage("BeanNamePortal"));
494            panel.add(label);
495        } catch (java.lang.ClassNotFoundException cnfe) {
496            log.error("Unable to find class supporting {}", Editor.POSITIONABLE_FLAVOR, cnfe);
497        }
498        dndPanel.add(panel);
499        return dndPanel;
500    }
501
502    public class IconDragJLabel extends DragJLabel {
503
504        boolean addSecondIcon = false;
505
506        public IconDragJLabel(DataFlavor flavor) {
507            super(flavor);
508        }
509
510        @Override
511        protected boolean okToDrag() {
512            String name = _portalName.getText();
513            if (name == null || name.trim().length() == 0) {
514                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("needPortalName"),
515                        Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE);
516                return false;
517            }
518            PortalManager portalMgr = InstanceManager.getDefault(jmri.jmrit.logix.PortalManager.class);
519            Portal portal = portalMgr.getPortal(name);
520            if (portal == null) {
521                return true;
522            }
523            OBlock toBlock = portal.getToBlock();
524            OBlock fromBlock = portal.getFromBlock();
525            if (!_homeBlock.equals(fromBlock) && !_homeBlock.equals(toBlock)) {
526                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("portalNeedsBlock", name, fromBlock, toBlock),
527                        Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE);
528                return false;
529            }
530            List<PortalIcon> piArray = _parent.getPortalIcons(portal);
531            for (PortalIcon pi : piArray) {
532                _parent._editor.highlight(pi);
533            }
534            switch (piArray.size()) {
535                case 0:
536                    return true;
537                case 1:
538                    PortalIcon i = piArray.get(0);
539                    if (_parent.iconIntersectsBlock(i, toBlock) && _parent.iconIntersectsBlock(i,fromBlock)) {
540                        JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("portalIconExists", name),
541                                Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE);
542                        return false;
543                    }
544                    if (addSecondIcon) {
545                        return true;
546                    }
547                    int result = JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("portalWant2Icons", name),
548                            Bundle.getMessage("makePortal"), JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.QUESTION_MESSAGE);
549                    if (result == JmriJOptionPane.YES_OPTION) {
550                        addSecondIcon = true;
551                        return true;
552                    }
553                    break;
554                default:
555                    JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("portalIconExists", name),
556                            Bundle.getMessage("makePortal"), JmriJOptionPane.INFORMATION_MESSAGE);
557            }
558            return false;
559        }
560
561        @Override
562        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
563            if (!isDataFlavorSupported(flavor)) {
564                return null;
565            }
566            if (DataFlavor.stringFlavor.equals(flavor)) {
567                return null;
568            }
569            String name = _portalName.getText();
570            Portal portal = _homeBlock.getPortalByName(name);
571            if (portal == null) {
572                PortalManager portalMgr = InstanceManager.getDefault(PortalManager.class);
573                portal = portalMgr.createNewPortal(name);
574                portal.setFromBlock(_homeBlock, false);
575                _portalList.dataChange();
576            }
577            addSecondIcon = false;
578            PortalIcon icon = new PortalIcon(_parent._editor, portal);
579            ArrayList<Positionable> group = _parent.getCircuitIcons(_homeBlock);
580            group.add(icon);
581            _parent.getPortalIcons(portal).add(icon);
582            _parent._editor.setSelectionGroup(group);
583            icon.setLevel(Editor.MARKERS);
584            icon.setStatus(PortalIcon.VISIBLE);
585            return icon;
586        }
587    }
588
589    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EditPortalFrame.class);
590
591}