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