001package jmri.jmrit.beantable.oblock;
002
003import java.awt.*;
004import java.awt.event.*;
005import java.beans.PropertyVetoException;
006import java.text.MessageFormat;
007import java.util.HashMap;
008import java.util.List;
009import java.util.SortedSet;
010import javax.annotation.CheckForNull;
011import javax.annotation.Nonnull;
012import javax.swing.*;
013import javax.swing.event.InternalFrameEvent;
014import javax.swing.event.InternalFrameListener;
015import javax.swing.table.AbstractTableModel;
016import javax.swing.table.TableColumn;
017import javax.swing.table.TableRowSorter;
018
019import jmri.*;
020import jmri.jmrit.logix.OBlock;
021import jmri.jmrit.logix.OBlockManager;
022import jmri.jmrit.logix.OPath;
023import jmri.jmrit.logix.Portal;
024import jmri.jmrit.logix.PortalManager;
025import jmri.jmrit.logix.WarrantTableAction;
026import jmri.swing.NamedBeanComboBox;
027import jmri.util.JmriJFrame;
028import jmri.util.SystemType;
029import jmri.util.com.sun.TransferActionListener;
030import jmri.util.gui.GuiLafPreferencesManager;
031import jmri.util.swing.XTableColumnModel;
032import jmri.util.swing.JmriJOptionPane;
033import jmri.util.table.ButtonEditor;
034import jmri.util.table.ButtonRenderer;
035import jmri.util.table.ToggleButtonEditor;
036import jmri.util.table.ToggleButtonRenderer;
037
038/**
039 * GUI to define OBlocks.
040 * <p>
041 * Core code can be used with two interfaces:
042 * <ul>
043 *     <li>original "desktop" InternalFrames (displays as InternalJFrames inside a JmriJFrame)
044 *     <li>JMRI standard Tabbed tables (displays as Js inside a ListedTableFrame)
045 * </ul>
046 * The _tabbed field decides, it is set in prefs (restart required). TableFrames itself has no UI.
047 * <hr>
048 * This file is part of JMRI.
049 * <p>
050 * JMRI is free software; you can redistribute it and/or modify it under the
051 * terms of version 2 of the GNU General Public License as published by the Free
052 * Software Foundation. See the "COPYING" file for a copy of this license.
053 * <p>
054 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
055 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
056 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
057 *
058 * @author Pete Cressman (C) 2010
059 * @author Egbert Broerse (C) 2020
060 */
061public class TableFrames implements InternalFrameListener {
062
063    public static final int ROW_HEIGHT = (new JButton("X").getPreferredSize().height)*9/10;
064    public static final int STRUT_SIZE = 10;
065    protected static final String SET_CLOSED = jmri.InstanceManager.turnoutManagerInstance().getClosedText();
066    protected static final String SET_THROWN = jmri.InstanceManager.turnoutManagerInstance().getThrownText();
067    private static String oblockPrefix;
068    private final static String portalPrefix = "IP";
069    private String _title;
070
071    private JTable _oBlockTable;
072    private final OBlockTableModel _oBlockModel;
073    private JTable _portalTable;
074    private final PortalTableModel _portalModel;
075    private JTable _blockPortalTable;
076    private final BlockPortalTableModel _blockPortalXRefModel;
077    private JTable _signalTable;
078    private final SignalTableModel _signalModel;
079
080    private final boolean _tabbed; // updated from prefs (restart required)
081    private boolean pathEdit = false;
082
083    private JmriJFrame desktopframe;
084    private JDesktopPane _desktop;
085    private final int maxHeight = 600;
086    private JInternalFrame _blockTableFrame;
087    private JInternalFrame _portalTableFrame;
088    private JInternalFrame _blockPortalXRefFrame;
089    private JInternalFrame _signalTableFrame;
090
091    private boolean _showWarnings = true;
092    private JMenuItem _showWarnItem;
093    private JMenu tablesMenu;
094    private JMenuItem openBlock;
095    private JMenuItem openPortal;
096    private JMenuItem openXRef;
097    private JMenuItem openSignal;
098    private JMenuItem _setUnits;
099
100    private final HashMap<String, BlockPathFrame> _blockPathMap = new HashMap<>();
101    private final HashMap<String, PathTurnoutFrame> _pathTurnoutMap = new HashMap<>();
102    // _tabbed edit panes are not stored in a map
103
104    public TableFrames() {
105        this("OBlock Tables");
106    } // NOI18N, title will be updated during init
107
108    public TableFrames(String actionName) {
109        _tabbed = InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed();
110        _title = actionName;
111        if (!_tabbed) {
112            desktopframe = new JmriJFrame(actionName);
113        }
114        // create the tables
115        _oBlockModel = new OBlockTableModel(this);
116        _portalModel = new PortalTableModel(this);
117        _blockPortalXRefModel = new BlockPortalTableModel(_oBlockModel);
118        _signalModel = new SignalTableModel(this);
119        _signalModel.init();
120    }
121
122    public OBlockTableModel getOblockTableModel() {
123        return _oBlockModel;
124    }
125    public PortalTableModel getPortalTableModel() {
126        return _portalModel;
127    }
128    public BlockPortalTableModel getPortalXRefTableModel() {
129        return _blockPortalXRefModel;
130    }
131    public BlockPathTableModel getBlockPathTableModel(OBlock block) {
132        return new BlockPathTableModel(block, this);
133    }
134    public SignalTableModel getSignalTableModel() {
135        return _signalModel;
136    }
137
138    public void initComponents() {
139        // build and display the classic floating "OBlock and its..." desktop interface
140        if (!_tabbed) { // just to be sure
141            setTitle(Bundle.getMessage("TitleOBlocks"));
142
143            // build tables
144            _blockTableFrame = buildFrame(_oBlockModel, Bundle.getMessage("TitleBlockTable"), Bundle.getMessage("AddBlockPrompt"));
145            _blockTableFrame.setVisible(true);
146
147            _portalTableFrame = buildFrame(_portalModel, Bundle.getMessage("TitlePortalTable"), Bundle.getMessage("AddPortalPrompt"));
148            _portalTableFrame.setVisible(true);
149
150            _signalTableFrame = buildFrame(_signalModel, Bundle.getMessage("TitleSignalTable"), Bundle.getMessage("AddSignalPrompt"));
151            _signalTableFrame.setVisible(false);
152
153            _blockPortalXRefFrame = buildFrame(_blockPortalXRefModel, Bundle.getMessage("TitleBlockPortalXRef"), Bundle.getMessage("XRefPrompt"));
154            _blockPortalXRefFrame.setVisible(false); // start with frame hidden
155
156            // build the print menu after the tables have been created
157            desktopframe.setTitle(getTitle());
158            desktopframe.setJMenuBar(addMenus(desktopframe.getJMenuBar()));
159            desktopframe.addHelpMenu("package.jmri.jmrit.logix.OBlockTable", true);
160
161            createDesktop(); // adds tables as windows on desktopframe._desktop
162            desktopframe.setLocation(10, 30);
163            desktopframe.setVisible(true);
164            desktopframe.pack();
165            addCloseListener(desktopframe);
166
167            // finally check table contents for errors
168            WarrantTableAction.getDefault().errorCheck();
169        }
170    }
171
172    public JMenuBar addMenus(JMenuBar mBar) {
173        if (mBar == null) {
174            mBar = new JMenuBar();
175        }
176        // create and add the menus
177        if (!_tabbed) { // _tabbed Print is handled via getPrintItem() in OBlockTablePanel
178            // File menu
179            JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
180            fileMenu.add(new jmri.configurexml.StoreMenu());
181            fileMenu.add(getPrintMenuItems(_oBlockTable, _portalTable, _signalTable, _blockPortalTable)); // add the print items
182            mBar.add(fileMenu);
183
184            // Edit menu
185            JMenu editMenu = new JMenu(Bundle.getMessage("MenuEdit"));
186            editMenu.setMnemonic(KeyEvent.VK_E);
187            TransferActionListener actionListener = new TransferActionListener();
188
189            JMenuItem menuItem = new JMenuItem(Bundle.getMessage("MenuItemCut"));
190            menuItem.setActionCommand((String) TransferHandler.getCutAction().getValue(Action.NAME));
191            menuItem.addActionListener(actionListener);
192            if (SystemType.isMacOSX()) {
193                menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.META_DOWN_MASK));
194            } else {
195                menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK));
196            }
197            menuItem.setMnemonic(KeyEvent.VK_T);
198            editMenu.add(menuItem);
199
200            menuItem = new JMenuItem(Bundle.getMessage("MenuItemCopy"));
201            menuItem.setActionCommand((String) TransferHandler.getCopyAction().getValue(Action.NAME));
202            menuItem.addActionListener(actionListener);
203            if (SystemType.isMacOSX()) {
204                menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.META_DOWN_MASK));
205            } else {
206                menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK));
207            }
208            menuItem.setMnemonic(KeyEvent.VK_C);
209            editMenu.add(menuItem);
210
211            menuItem = new JMenuItem(Bundle.getMessage("MenuItemPaste"));
212            menuItem.setActionCommand((String) TransferHandler.getPasteAction().getValue(Action.NAME));
213            menuItem.addActionListener(actionListener);
214            if (SystemType.isMacOSX()) {
215                menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.META_DOWN_MASK));
216            } else {
217                menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK));
218            }
219            menuItem.setMnemonic(KeyEvent.VK_P);
220            editMenu.add(menuItem);
221            mBar.add(editMenu);
222        }
223
224        mBar.add(getOptionMenu());
225        mBar.add(getTablesMenu());
226        return mBar;
227    }
228
229    public JMenu getPrintMenuItems(JTable oBlockTable, JTable portalTable, JTable signalTable, JTable blockPortalTable) {
230        JMenu print = new JMenu(Bundle.getMessage("PrintTable"));
231        JMenuItem printItem = new JMenuItem(Bundle.getMessage("PrintOBlockTable"));
232        print.add(printItem);
233        printItem.addActionListener(e -> {
234            try {
235                MessageFormat headerFormat = new MessageFormat(Bundle.getMessage("TitleOBlockTable"));
236                MessageFormat footerFormat = new MessageFormat(getTitle() + " page {0,number}");
237                oBlockTable.print(JTable.PrintMode.FIT_WIDTH, headerFormat, footerFormat);
238            } catch (java.awt.print.PrinterException e1) {
239                log.warn("error printing: {}", e1, e1);
240            }
241        });
242        printItem = new JMenuItem(Bundle.getMessage("PrintPortalTable"));
243        print.add(printItem);
244        printItem.addActionListener(e -> {
245            try {
246                MessageFormat headerFormat = new MessageFormat(Bundle.getMessage("TitlePortalTable"));
247                MessageFormat footerFormat = new MessageFormat(getTitle() + " page {0,number}");
248                portalTable.print(JTable.PrintMode.FIT_WIDTH, headerFormat, footerFormat);
249            } catch (java.awt.print.PrinterException e1) {
250                log.warn("error printing: {}", e1, e1);
251            }
252        });
253        printItem = new JMenuItem(Bundle.getMessage("PrintSignalTable"));
254        print.add(printItem);
255        printItem.addActionListener(e -> {
256            try {
257                MessageFormat headerFormat = new MessageFormat(Bundle.getMessage("TitleSignalTable"));
258                MessageFormat footerFormat = new MessageFormat(getTitle() + " page {0,number}");
259                signalTable.print(JTable.PrintMode.FIT_WIDTH, headerFormat, footerFormat);
260            } catch (java.awt.print.PrinterException e1) {
261                log.warn("error printing: {}", e1, e1);
262            }
263        });
264        printItem = new JMenuItem(Bundle.getMessage("PrintXRef"));
265        print.add(printItem);
266        printItem.addActionListener(e -> {
267            try {
268                MessageFormat headerFormat = new MessageFormat(Bundle.getMessage("OpenXRefMenu", ""));
269                MessageFormat footerFormat = new MessageFormat(getTitle() + " page {0,number}");
270                blockPortalTable.print(JTable.PrintMode.FIT_WIDTH, headerFormat, footerFormat);
271            } catch (java.awt.print.PrinterException e1) {
272                log.warn("error printing: {}", e1, e1);
273            }
274        });
275        return print;
276    }
277
278    // for desktop style interface, ignored for _tabbed
279    private void createDesktop() {
280        _desktop = new JDesktopPane();
281        _desktop.putClientProperty("JDesktopPane.dragMode", "outline"); // slower or faster?
282        int deskWidth = _blockTableFrame.getWidth();
283        int deskHeight = _blockTableFrame.getHeight();
284//        _desktop.setPreferredSize(new Dimension(deskWidth,
285//                deskHeight + _portalTableFrame.getHeight() + 100));
286        _desktop.setBackground(new Color(180,180,180));
287        desktopframe.setContentPane(_desktop);
288        desktopframe.setPreferredSize(new Dimension(deskWidth + 16,
289                deskHeight + _portalTableFrame.getHeight() + 64));
290
291        // placed at 0,0
292        _desktop.add(_blockTableFrame);
293        _portalTableFrame.setLocation(0, deskHeight);
294        _desktop.add(_portalTableFrame);
295        _signalTableFrame.setLocation(200, deskHeight+100);
296        _desktop.add(_signalTableFrame);
297        _blockPortalXRefFrame.setLocation(deskWidth - _blockPortalXRefFrame.getWidth(), deskHeight);
298        _desktop.add(_blockPortalXRefFrame);
299    }
300
301    public JMenu getOptionMenu() {
302        // Options menu
303        JMenu optionMenu = new JMenu(Bundle.getMessage("MenuOptions"));
304        _showWarnItem = new JMenuItem(Bundle.getMessage("SuppressWarning"));
305        _showWarnItem.addActionListener(event -> {
306            String cmd = event.getActionCommand();
307            setShowWarnings(cmd);
308        });
309        optionMenu.add(_showWarnItem);
310        setShowWarnings("ShowWarning");
311
312        JMenuItem importBlocksItem = new JMenuItem(Bundle.getMessage("ImportBlocksMenu"));
313        importBlocksItem.addActionListener((ActionEvent event) -> importBlocks());
314        optionMenu.add(importBlocksItem);
315        // disable ourself if there is no primary Block manager available
316        if (jmri.InstanceManager.getNullableDefault(jmri.BlockManager.class) == null) { // means Block list is empty
317            importBlocksItem.setEnabled(false);
318        }
319        _setUnits = new JMenuItem(Bundle.getMessage("changeUnits",
320                (_oBlockModel.isMetric() ? Bundle.getMessage("LengthInches") : Bundle.getMessage("LengthCentimeters"))));
321        _setUnits.addActionListener(event -> setUnits());
322        optionMenu.add(_setUnits);
323        return optionMenu;
324    }
325
326    public JMenu getTablesMenu() {
327        // Tables menu
328        tablesMenu = new JMenu(Bundle.getMessage("OpenMenu"));
329        updateOBlockTablesMenu(); // replaces the last 2 menu items with appropriate submenus
330        return tablesMenu;
331    }
332
333    private String oblockPrefix() {
334        if (oblockPrefix == null) {
335            oblockPrefix = InstanceManager.getDefault(OBlockManager.class).getSystemNamePrefix();
336        }
337        return oblockPrefix;
338    }
339
340    /**
341     * Get the JFrame containig all UI windows.
342     *
343     * @return the contentframe
344     */
345    protected JmriJFrame getDesktopFrame() {
346        return desktopframe;
347    }
348
349    /**
350     * Convert a copy of your current JMRI Blocks to OBlocks and connect them with Portals and Paths.
351     * Accessed from the Options menu.
352     * @throws IllegalArgumentException exception
353     * @author Egbert Broerse 2019
354     */
355    protected void importBlocks() throws IllegalArgumentException {
356        Manager<Block> bm = InstanceManager.getDefault(jmri.BlockManager.class);
357        OBlockManager obm = InstanceManager.getDefault(OBlockManager.class);
358        PortalManager pom = InstanceManager.getDefault(PortalManager.class);
359        SortedSet<Block> blkList = bm.getNamedBeanSet();
360        // don't return an element if there are no Blocks to include
361        if (blkList.isEmpty()) {
362            log.warn("no Blocks to convert"); // NOI18N
363            JmriJOptionPane.showMessageDialog(desktopframe, Bundle.getMessage("ImportNoBlocks"),
364                    Bundle.getMessage("InfoTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
365            return;
366        } else {
367            if (_showWarnings) {
368                int reply = JmriJOptionPane.showOptionDialog(null,
369                        Bundle.getMessage("ImportBlockConfirm", oblockPrefix(), blkList.size()),
370                        Bundle.getMessage("QuestionTitle"),
371                        JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
372                        new Object[]{Bundle.getMessage("ButtonYes"),
373                                Bundle.getMessage("ButtonCancel")},
374                        Bundle.getMessage("ButtonYes")); // standard JOptionPane can't be found in Jemmy log4J
375                if (reply > 0) {
376                    return;
377                }
378            }
379        }
380        for (Block b : blkList) {
381            try {
382                // read Block properties
383                String sName = b.getSystemName();
384                String uName = b.getUserName();
385                String blockNumber = sName.substring(sName.startsWith("IB:AUTO:") ? 8 : 3);
386                String oBlockName = oblockPrefix() + blockNumber;
387                String sensor = "";
388                Sensor s = b.getSensor();
389                if (s != null) {
390                    sensor = s.getDisplayName();
391                }
392                float length = b.getLengthMm(); // length is stored in Mm in OBlock.setLength(float)
393                int curve = b.getCurvature();
394                List<Path> blockPaths = b.getPaths();
395                String toBlockName;
396                Portal port = null;
397                int n = 0;
398                Portal prevPortal = null;
399
400                log.debug("start creating OBlock {} from Block {}", oBlockName, sName);
401                if ((uName != null) && (obm.getOBlock(uName) != null)) {
402                    log.warn("an OBlock with this user name already exists, replacing {}", uName);
403                }
404                // create the OBlock by systemName
405                OBlock oBlock = obm.provideOBlock(oBlockName);
406                oBlock.setUserName(uName);
407                if (!sensor.isEmpty()) {
408                    oBlock.setSensor(sensor);
409                }
410                oBlock.setMetricUnits(true); // length always stored in Mm in Block, so copy that for OBlock
411                oBlock.setLength(length);
412                oBlock.setCurvature(curve);
413
414                for (Path pa : blockPaths) {
415                    log.debug("Start loop: Path {} on Block {}", n, oBlockName);
416                    String toBlockNumber = pa.getBlock().getSystemName().substring(sName.startsWith("IB:AUTO:") ? 8 : 3);
417                    toBlockName = oblockPrefix() + toBlockNumber;
418                    String portalName = portalPrefix + toBlockNumber + "-" + blockNumber; // reversed name for new Portal
419                    port = pom.getPortal(portalName);
420                    if (port == null) {
421                        portalName = portalPrefix + blockNumber + "-" + toBlockNumber; // normal name for new Portal
422                        log.debug("new Portal {} on block {}, path #{}", portalName, toBlockName, n);
423                        port = pom.providePortal(portalName); // normally, will create a new Portal
424                        port.setFromBlock(oBlock, false);
425                        port.setToBlock(obm.provideOBlock(toBlockName), false); // create one if required
426                    } else {
427                        log.debug("duplicate Portal {} on block {}, path #{}", portalName, toBlockName, n);
428                        // Portal port already set
429                    }
430                    oBlock.addPortal(port);
431
432                    // create OPath from this Path
433                    OPath opa = new OPath(oBlock, "IP" + n++); // only needs to be unique within oBlock
434                    opa.setLength(oBlock.getLengthMm()); // simple assumption, works for default OBlock/OPath
435                    log.debug("new OPath #{} - {} on OBlock {}", n, opa.getName(), opa.getBlock().getDisplayName());
436                    oBlock.addPath(opa); // checks for duplicates, will add OPath to any Portals on oBlock as well
437                    log.debug("number of paths: {}", oBlock.getPaths().size());
438
439                    // set _fromPortal and _toPortal for each OPath in OBlock
440                    if (opa.getFromPortal() == null) {
441                        opa.setFromPortal(port);
442                    }
443                    for (BeanSetting bs : pa.getSettings()) {
444                        opa.addSetting(bs);
445                    }
446                    if ((opa.getToPortal() == null) && (prevPortal != null)) {
447                        opa.setToPortal(prevPortal);
448                        // leaves ToPortal in previously (first) created OPath n-1 empty
449                    }
450                    prevPortal = port; // remember the new portal for use as ToPortal in opposing OPath
451                    // user must remove nonsense manually unless...
452                }
453                // we use the last FromPortal as ToPortal in OPath P0
454                OPath p0 = oBlock.getPathByName("IP0");
455                if ((p0 != null) && (n > 1) && (p0.getToPortal() == null)) {
456                    p0.setToPortal(port);
457                }
458            } catch (IllegalArgumentException iae) {
459                log.error("Could not convert Block {} to OBlock. {}",
460                    b.getDisplayName(NamedBean.DisplayOptions.USERNAME_SYSTEMNAME), iae.getMessage());
461            }
462            // finished setting up 1 OBlock
463        }
464        // add recursive Path elements to FromBlock/ToBlock
465        SortedSet<OBlock> oblkList = obm.getNamedBeanSet();
466        for (OBlock oblk : oblkList) {
467            for (Portal po : oblk.getPortals()) {
468                OBlock oob = obm.getByUserName(po.getFromBlockName());
469                if (oob !=null) {
470                    oob.addPortal(po);
471                }
472                oob = obm.getByUserName(po.getToBlockName());
473                if (oob !=null) {
474                    oob.addPortal(po);
475                }
476            }
477        }
478        // storing and reloading will add in these items
479        WarrantTableAction.getDefault().errorCheck();
480        if (_showWarnings) {
481            JmriJOptionPane.showMessageDialog(null,
482                    Bundle.getMessage("ImportBlockComplete", blkList.size(), oblkList.size()),
483                    Bundle.getMessage("MessageTitle"),
484                    JmriJOptionPane.INFORMATION_MESSAGE); // standard JOptionPane can't be found in Jemmy log4J
485        }
486    }
487    // End of importBlocks() menu method
488
489    protected void setShowWarnings(String cmd) {
490        if (cmd.equals("ShowWarning")) {
491            _showWarnings = true;
492            _showWarnItem.setActionCommand("SuppressWarning");
493            _showWarnItem.setText(Bundle.getMessage("SuppressWarning"));
494        } else {
495            _showWarnings = false;
496            _showWarnItem.setActionCommand("ShowWarning");
497            _showWarnItem.setText(Bundle.getMessage("ShowWarning"));
498        }
499        log.debug("setShowWarnings: _showWarnings= {}", _showWarnings);
500    }
501
502    private void setUnits() {
503        _oBlockModel.changeUnits();
504        _setUnits.setText(Bundle.getMessage("changeUnits",
505                (_oBlockModel.isMetric() ? Bundle.getMessage("LengthInches") : Bundle.getMessage("LengthCentimeters"))));
506    }
507
508    // listen for _desktopframe closing
509    void addCloseListener(JmriJFrame desktop) {
510        desktop.addWindowListener(new java.awt.event.WindowAdapter() {
511            @Override
512            public void windowClosing(java.awt.event.WindowEvent e) {
513                WarrantTableAction.getDefault().errorCheck();
514                desktop.setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
515                // closing instead of hiding removes name from Windows menu.handle menu to read Show...
516                log.debug("windowClosing: {}", toString());
517                desktop.dispose();
518            }
519        });
520    }
521
522    private String getTitle() {
523        return _title;
524    }
525
526    private void setTitle(String title) {
527        _title = title;
528    }
529
530    /**
531     * Fill in the Open/Hide Tables menu on tablesMenu.
532     */
533    protected void updateOBlockTablesMenu() {
534        if (tablesMenu == null) {
535            return;
536        }
537        tablesMenu.removeAll();
538        if (!_tabbed) { // full menu in _desktop, open/show not available in _tabbed interface
539            // use string Bundle.getMessage("HideTable") to correct action in menu for all table open at start
540            openBlock = new JMenuItem(Bundle.getMessage("OpenBlockMenu", Bundle.getMessage("HideTable")));
541            tablesMenu.add(openBlock);
542            openBlock.addActionListener(event -> showHideFrame(_blockTableFrame, openBlock, "OpenBlockMenu"));
543
544            openPortal = new JMenuItem(Bundle.getMessage("OpenPortalMenu", Bundle.getMessage("HideTable")));
545            tablesMenu.add(openPortal);
546            openPortal.addActionListener(event -> showHideFrame(_portalTableFrame, openPortal, "OpenPortalMenu"));
547
548            openXRef = new JMenuItem(Bundle.getMessage("OpenXRefMenu", Bundle.getMessage("ShowTable")));
549            tablesMenu.add(openXRef);
550            openXRef.addActionListener(event -> showHideFrame(_blockPortalXRefFrame, openXRef, "OpenXRefMenu"));
551
552            openSignal = new JMenuItem(Bundle.getMessage("OpenSignalMenu", Bundle.getMessage("ShowTable")));
553            tablesMenu.add(openSignal);
554            openSignal.addActionListener(event -> showHideFrame(_signalTableFrame, openSignal, "OpenSignalMenu"));
555        }
556
557        OBlockManager manager = InstanceManager.getDefault(OBlockManager.class);
558
559        // Block-Path submenus
560        JMenu openBlockPath = new JMenu(Bundle.getMessage("OpenBlockPathMenu"));
561        ActionListener openFrameAction = e -> {
562            String blockSystemName = e.getActionCommand();
563            openBlockPathPane(blockSystemName, Bundle.getMessage("TitlePaths")); // handles both interfaces
564        };
565
566        if (manager.getNamedBeanSet().size() == 0) {
567            JMenuItem mi = new JMenuItem(Bundle.getMessage("NoBlockPathYet"));
568            mi.setEnabled(false);
569            openBlockPath.add(mi);
570        } else {
571            for (OBlock block : manager.getNamedBeanSet()) {
572                JMenuItem mi = new JMenuItem(Bundle.getMessage("OpenPathMenu", block.getDisplayName()));
573                mi.setActionCommand(block.getSystemName());
574                mi.addActionListener(openFrameAction);
575                openBlockPath.add(mi);
576            }
577        }
578        tablesMenu.add(openBlockPath);
579
580        // Path-Turnout submenus
581        JMenu openTurnoutPath = new JMenu(Bundle.getMessage("OpenBlockPathTurnoutMenu"));
582        if (manager.getNamedBeanSet().size() == 0) {
583            JMenuItem mi = new JMenuItem(Bundle.getMessage("NoPathTurnoutYet"));
584            mi.setEnabled(false);
585            openTurnoutPath.add(mi);
586        } else {
587            for (OBlock block : manager.getNamedBeanSet()) {
588                JMenu openTurnoutMenu = new JMenu(Bundle.getMessage("OpenTurnoutMenu", block.getDisplayName()));
589                openTurnoutPath.add(openTurnoutMenu);
590                openFrameAction = e -> {
591                    String pathTurnoutName = e.getActionCommand();
592                    openPathTurnoutEditPane(pathTurnoutName); // handles both interfaces
593                };
594                for (Path p : block.getPaths()) {
595                    if (p instanceof OPath) {
596                        OPath path = (OPath) p;
597                        JMenuItem mi = new JMenuItem(Bundle.getMessage("OpenPathTurnoutMenu", path.getName()));
598                        mi.setActionCommand(makePathTurnoutName(block.getSystemName(), path.getName()));
599                        mi.addActionListener(openFrameAction);
600                        openTurnoutMenu.add(mi);
601                    }
602                }
603            }
604        }
605        tablesMenu.add(openTurnoutPath);
606    }
607
608    public void openPathTurnoutEditPane(String pathTurnoutName) {
609        if (_tabbed) {
610            log.debug("openPathTurnoutEditPane for {}", pathTurnoutName);
611            openPathTurnoutEditor(pathTurnoutName);
612        } else { // stand alone frame only used for _desktop, created from/stored in Portal
613            openPathTurnoutFrame(pathTurnoutName);
614        }
615    }
616
617    /**
618     * Show or hide a table in the _desktop interface.
619     *
620     * @param frame JInternalFrame to show (or hide, name property value contains {} var handled by frame)
621     * @param menu menu item object
622     * @param menuName base i18n string containing table name
623     */
624    private void showHideFrame(JInternalFrame frame, JMenuItem menu, String menuName) {
625        if (!frame.isVisible()) {
626            frame.setVisible(true);
627            try {
628                frame.setIcon(false);
629            } catch (PropertyVetoException pve) {
630                log.warn("{} Frame vetoed setIcon {}", frame.getTitle(), pve.toString());
631            }
632            frame.moveToFront();
633        } else {
634            frame.setVisible(false);
635        }
636        menu.setText(Bundle.getMessage(menuName,
637                (frame.isVisible() ? Bundle.getMessage("HideTable") : Bundle.getMessage("ShowTable"))));
638    }
639
640    /**
641     * Wrapper for shared code around each Table in a JInternal window on _desktop interface.
642     *
643     * @param tableModel underlying model for the table
644     * @param title text displayed as title of frame
645     * @param prompt text below bottom line
646     * @return iframe to put on _desktop interface
647     */
648    protected JInternalFrame buildFrame(AbstractTableModel tableModel, String title, String prompt) {
649        JInternalFrame iframe = new JInternalFrame(title, true, false, false, true);
650
651        // specifics for table
652        JTable table = new JTable();
653        if (tableModel instanceof OBlockTableModel) {
654            table = makeOBlockTable((OBlockTableModel) tableModel);
655        } else if (tableModel instanceof PortalTableModel) {
656            table = makePortalTable((PortalTableModel) tableModel);
657        } else if (tableModel instanceof BlockPortalTableModel) {
658            table = makeBlockPortalTable((BlockPortalTableModel) tableModel);
659        } else if (tableModel instanceof SignalTableModel) {
660            table = makeSignalTable((SignalTableModel) tableModel);
661        } // no case here for BlockPathTableModel, it is handled directly from OBlockTable
662
663        JScrollPane scroll = new JScrollPane(table);
664        JPanel contentPane = new JPanel();
665        contentPane.setLayout(new BorderLayout(5, 5));
666        JLabel _prompt = new JLabel(prompt);
667        contentPane.add(_prompt, BorderLayout.NORTH);
668        contentPane.add(scroll, BorderLayout.CENTER);
669
670        iframe.setContentPane(contentPane);
671        iframe.pack();
672        return iframe;
673    }
674
675    /*
676     * ********************* OBlock Table for _desktop ****************
677     */
678    protected JTable makeOBlockTable(OBlockTableModel model) {
679        _oBlockTable = new JTable(model);
680        TableRowSorter<OBlockTableModel> sorter = new TableRowSorter<>(_oBlockModel);
681        // use NamedBean's built-in Comparator interface for sorting
682        _oBlockTable.setRowSorter(sorter);
683        _oBlockTable.setTransferHandler(new jmri.util.DnDTableImportExportHandler(new int[]{OBlockTableModel.EDIT_COL,
684            OBlockTableModel.DELETE_COL, OBlockTableModel.REPORT_CURRENTCOL, OBlockTableModel.SPEEDCOL,
685            OBlockTableModel.PERMISSIONCOL, OBlockTableModel.UNITSCOL}));
686        _oBlockTable.setDragEnabled(true);
687
688        // Use XTableColumnModel so we can control which columns are visible
689        XTableColumnModel tcm = new XTableColumnModel();
690        _oBlockTable.setColumnModel(tcm);
691        _oBlockTable.getTableHeader().setReorderingAllowed(true);
692        _oBlockTable.createDefaultColumnsFromModel();
693        _oBlockModel.addHeaderListener(_oBlockTable);
694
695        _oBlockTable.setDefaultEditor(JComboBox.class, new jmri.jmrit.symbolicprog.ValueEditor());
696        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.EDIT_COL).setCellEditor(new ButtonEditor(new JButton()));
697        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.EDIT_COL).setCellRenderer(new ButtonRenderer());
698        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.DELETE_COL).setCellEditor(new ButtonEditor(new JButton()));
699        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.DELETE_COL).setCellRenderer(new ButtonRenderer());
700        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.UNITSCOL).setCellRenderer(
701                new ToggleButtonRenderer(Bundle.getMessage("cm"), Bundle.getMessage("in")));
702        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.UNITSCOL).setCellEditor(
703                new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("cm"), Bundle.getMessage("in")));
704        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.REPORT_CURRENTCOL).setCellRenderer(
705                new ToggleButtonRenderer(Bundle.getMessage("Current"), Bundle.getMessage("Last")));
706        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.REPORT_CURRENTCOL).setCellEditor(
707                new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("Current"), Bundle.getMessage("Last")));
708        model.configSpeedColumn(_oBlockTable); // use real combo
709        //        JComboBox<String> box = new JComboBox<>(OBlockTableModel.curveOptions);
710        //        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.CURVECOL).setCellEditor(new DefaultCellEditor(box));
711        model.configCurveColumn(_oBlockTable); // use real combo
712        //        box = new JComboBox<>(jmri.InstanceManager.getDefault(SignalSpeedMap.class).getValidSpeedNames());
713//        box.addItem("");
714//        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.SPEEDCOL).setCellRenderer(new DefaultCellRenderer(new _oBlockModel.SpeedComboBoxPanel()));
715//        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.SPEEDCOL).setCellEditor(new DefaultCellEditor(box));
716        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.PERMISSIONCOL).setCellRenderer(
717                new ToggleButtonRenderer(Bundle.getMessage("Permissive"), Bundle.getMessage("Absolute")));
718        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.PERMISSIONCOL).setCellEditor(
719                new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("Permissive"), Bundle.getMessage("Absolute")));
720        _oBlockTable.addMouseListener(new MouseAdapter() {
721            @Override
722            public void mousePressed(MouseEvent me) { // for macOS, Linux
723                showPopup(me);
724            }
725
726            @Override
727            public void mouseReleased(MouseEvent me) { // for Windows
728                showPopup(me);
729            }
730        });
731
732        for (int i = 0; i < _oBlockModel.getColumnCount(); i++) {
733            int width = _oBlockModel.getPreferredWidth(i);
734            _oBlockTable.getColumnModel().getColumn(i).setPreferredWidth(width);
735        }
736        _oBlockTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
737        _oBlockTable.setRowHeight(ROW_HEIGHT);
738
739        TableColumn column = tcm.getColumnByModelIndex(OBlockTableModel.REPORTERCOL);
740        tcm.setColumnVisible(column, false);
741        column = tcm.getColumnByModelIndex(OBlockTableModel.REPORT_CURRENTCOL);
742        tcm.setColumnVisible(column, false);
743        column = tcm.getColumnByModelIndex(OBlockTableModel.PERMISSIONCOL);
744        tcm.setColumnVisible(column, false);
745        column = tcm.getColumnByModelIndex(OBlockTableModel.ERR_SENSORCOL);
746        tcm.setColumnVisible(column, false);
747        column = tcm.getColumnByModelIndex(OBlockTableModel.CURVECOL);
748        tcm.setColumnVisible(column, false);
749
750        _oBlockTable.setPreferredScrollableViewportSize(new java.awt.Dimension(_oBlockTable.getPreferredSize().width,
751                ROW_HEIGHT * Math.min(20, InstanceManager.getDefault(OBlockManager.class).getObjectCount())));
752        return _oBlockTable;
753    }
754
755    private void showPopup(MouseEvent me) {
756        Point p = me.getPoint();
757        int col = _oBlockTable.columnAtPoint(p);
758        if (!me.isPopupTrigger() && !me.isMetaDown() && !me.isAltDown() && col == OBlockTableModel.STATECOL) {
759            int row = _oBlockTable.rowAtPoint(p);
760            String stateStr = (String) _oBlockModel.getValueAt(row, col);
761            int state = Integer.parseInt(stateStr, 2);
762            stateStr = OBlockTableModel.getValue(state);
763            JPopupMenu popupMenu = new JPopupMenu();
764            popupMenu.add(new JMenuItem(stateStr));
765            popupMenu.show(_oBlockTable, me.getX(), me.getY());
766        }
767    }
768
769    // Opens the Edit OBlock panel for _tabbed
770    protected boolean openOBlockEditor(String blockSystemName, String tabname) {
771        boolean result = false;
772        if (blockSystemName != null) {
773            // this is for Edit (new OBlocks are created from [Add OBlock...] button in table)
774            OBlock oblock = InstanceManager.getDefault(OBlockManager.class).getBySystemName(blockSystemName);
775            if (oblock != null) {
776                BlockPathJPanel panel = makeBlockPathEditPanel(oblock);
777                // BeanEdit UI, adapted from jmri.jmrit.beantable.BlockTableAction
778                jmri.jmrit.beantable.beanedit.OBlockEditAction beanEdit = new jmri.jmrit.beantable.beanedit.OBlockEditAction(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, tabname));
779                beanEdit.setBean(oblock);
780                beanEdit.setTablePanel(panel);
781                beanEdit.actionPerformed(null);
782                // run on separate thread? does not update new Paths in table!
783                //                class WindowMaker implements Runnable {
784                //                    final OBlock ob;
785                //                    final BlockPathJPanel panel;
786                //                    WindowMaker(OBlock oblock, BlockPathJPanel panel) {
787                //                        ob = oblock;
788                //                        this.panel = panel;
789                //                    }
790                //                    @Override
791                //                    public void run() {
792                //                        jmri.jmrit.beantable.beanedit.OBlockEditAction beanEdit = new jmri.jmrit.beantable.beanedit.OBlockEditAction();
793                //                        beanEdit.setBean(oblock);
794                //                        beanEdit.setTablePanel(panel);
795                //                        beanEdit.actionPerformed(null);
796                //                    }
797                //                }
798                //                WindowMaker t = new WindowMaker(oblock, panel);
799                //                javax.swing.SwingUtilities.invokeLater(t);
800                log.debug("path table created for oblock {}", blockSystemName);
801                result = true;
802            }
803        }
804        return result;
805    }
806
807    /**
808     * Open the Edit Path panel for _tabbed.
809     * Compare with openOBlockEditor(block, selectedtabname) and OBlockTableAction.
810     *
811     * @param blockName system or user name of the owning oblock
812     * @param pathName name of the path under edit, or null to create a new path
813     * @param bpmodel blockpathtablemodel that should be informed about changes
814     * @return true if successful
815     */
816    protected boolean openPathEditor(@Nonnull String blockName, @CheckForNull String pathName, BlockPathTableModel bpmodel) {
817        OBlock block = InstanceManager.getDefault(OBlockManager.class).getOBlock(blockName);
818        if (block == null) {
819            log.error("OBlock {} not found", blockName);
820            return false;
821        }
822        OPath path;
823        String title;
824        PathTurnoutJPanel turnouttable = makePathTurnoutPanel(block, pathName); // shows the turnouts on path, includes Add Turnout button, checks for null path
825        if (pathName == null) { // new Path, empty TurnoutTable
826            // a new Path is created from [Add Path...] button in Path table on OBlock Editor pane.
827            path = null;
828            title = Bundle.getMessage("AddPathTitle", blockName);
829        } else {
830            path = block.getPathByName(pathName);
831            title = Bundle.getMessage("EditPathTitle", pathName, blockName);
832        }
833        BlockPathEditFrame bpef = new BlockPathEditFrame(title, block, path, turnouttable, bpmodel, this);
834        bpef.setVisible(true);
835        // run on separate thread? combos are final, difficult to store Partals in Path/see them show up in the table
836        //        class WindowMaker implements Runnable {
837        //            final String title;
838        //            final OBlock ob;
839        //            final OPath path;
840        //            final PathTurnoutTableModel tomodel;
841        //            final BlockPathTableModel bpmodel;
842        //            final TableFrames parent;
843        //            WindowMaker(String title, OBlock ob, OPath path, PathTurnoutTableModel turnoutmodel, BlockPathTableModel blockpathmodel, TableFrames tf) {
844        //                this.title = title;
845        //                this.ob = ob;
846        //                this.path = path;
847        //                this.tomodel = turnoutmodel;
848        //                this.bpmodel = blockpathmodel;
849        //                parent = tf;
850        //            }
851        //            @Override
852        //            public void run() {
853        //                BlockPathEditFrame bpef = new BlockPathEditFrame(title, block, path, turnouttable, bpmodel, parent);
854        //                bpef.setVisible(true);
855        //            }
856        //        }
857        //        WindowMaker t = new WindowMaker(title, block, path, turnouttable.getModel(), bpmodel, this);
858        //        javax.swing.SwingUtilities.invokeLater(t);
859
860        log.debug("Path editor created for path {} on block {}", pathName, blockName);
861        return true;
862    }
863
864    /*
865     * ********************* PortalTable for _desktop *****************************
866     */
867    protected JTable makePortalTable(PortalTableModel model) {
868        _portalTable = new JTable(model);
869        TableRowSorter<PortalTableModel> sorter = new TableRowSorter<>(model);
870        _portalTable.setRowSorter(sorter);
871        _portalTable.setTransferHandler(new jmri.util.DnDTableImportExportHandler(new int[]{PortalTableModel.DELETE_COL}));
872        _portalTable.setDragEnabled(true);
873
874        _portalTable.getColumnModel().getColumn(PortalTableModel.DELETE_COL).setCellEditor(new ButtonEditor(new JButton()));
875        _portalTable.getColumnModel().getColumn(PortalTableModel.DELETE_COL).setCellRenderer(new ButtonRenderer());
876        for (int i = 0; i < model.getColumnCount(); i++) {
877            int width = model.getPreferredWidth(i);
878            _portalTable.getColumnModel().getColumn(i).setPreferredWidth(width);
879        }
880        _portalTable.doLayout();
881        int tableWidth = _portalTable.getPreferredSize().width;
882        _portalTable.setRowHeight(ROW_HEIGHT);
883        _portalTable.setPreferredScrollableViewportSize(new java.awt.Dimension(tableWidth,
884                ROW_HEIGHT * Math.min(20, InstanceManager.getDefault(PortalManager.class).getPortalCount())));
885        return _portalTable;
886    }
887
888    /*
889     * ********************* Block-Portal (XRef) Table for _desktop *****************************
890     */
891    protected JTable makeBlockPortalTable(BlockPortalTableModel model) {
892        _blockPortalTable = new JTable(model);
893        _blockPortalTable.setTransferHandler(new jmri.util.DnDTableExportHandler());
894        _blockPortalTable.setDragEnabled(true);
895
896        _blockPortalTable.setDefaultRenderer(String.class, new jmri.jmrit.symbolicprog.ValueRenderer());
897        _blockPortalTable.setDefaultEditor(String.class, new jmri.jmrit.symbolicprog.ValueEditor()); // useful on non-editable cell?
898        for (int i = 0; i < model.getColumnCount(); i++) {
899            int width = model.getPreferredWidth(i);
900            _blockPortalTable.getColumnModel().getColumn(i).setPreferredWidth(width);
901        }
902        _blockPortalTable.doLayout();
903        _blockPortalTable.setRowHeight(ROW_HEIGHT);
904        int tableWidth = _blockPortalTable.getPreferredSize().width;
905        _blockPortalTable.setPreferredScrollableViewportSize(new java.awt.Dimension(tableWidth,
906                ROW_HEIGHT * Math.min(20, InstanceManager.getDefault(PortalManager.class).getPortalCount())));
907
908        return _blockPortalTable;
909    }
910
911    /*
912     * ********************* Signal Table for _desktop *****************************
913     */
914    protected JTable makeSignalTable(SignalTableModel model) {
915        _signalTable = new JTable(model);
916        TableRowSorter<SignalTableModel> sorter = new TableRowSorter<>(model);
917        _signalTable.setRowSorter(sorter);
918        _signalTable.setTransferHandler(new jmri.util.DnDTableImportExportHandler(
919                new int[]{SignalTableModel.UNITSCOL, SignalTableModel.DELETE_COL}));
920        _signalTable.setDragEnabled(true);
921
922        _signalTable.getColumnModel().getColumn(SignalTableModel.UNITSCOL).setCellRenderer(
923                new ToggleButtonRenderer(Bundle.getMessage("cm"), Bundle.getMessage("in")));
924        _signalTable.getColumnModel().getColumn(SignalTableModel.UNITSCOL).setCellEditor(
925                new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("cm"), Bundle.getMessage("in")));
926        _signalTable.getColumnModel().getColumn(SignalTableModel.DELETE_COL).setCellEditor(new ButtonEditor(new JButton()));
927        _signalTable.getColumnModel().getColumn(SignalTableModel.DELETE_COL).setCellRenderer(new ButtonRenderer());
928        for (int i = 0; i < model.getColumnCount(); i++) {
929            int width = SignalTableModel.getPreferredWidth(i);
930            _signalTable.getColumnModel().getColumn(i).setPreferredWidth(width);
931        }
932        _signalTable.doLayout();
933        int tableWidth = _signalTable.getPreferredSize().width;
934        _signalTable.setRowHeight(ROW_HEIGHT);
935        _signalTable.setPreferredScrollableViewportSize(new java.awt.Dimension(tableWidth,
936                ROW_HEIGHT * Math.min(10, _signalTable.getRowCount())));
937        return _signalTable;
938    }
939
940    /*
941     * ***************** end of permanent Tables + InternalFrame definitions *****************
942     */
943
944
945    /*
946     * ***************** On Demand Tables + InternalFrame definitions *****************
947     */
948
949    /*
950     * ********************* Block-Path Frame *****************************
951     */
952
953    // called from Tables menu and the OBlockTable EDIT buttons
954    public void openBlockPathPane(String blockSystemName, String editorTabName) {
955        if (_tabbed) {
956            if (!openOBlockEditor(blockSystemName, editorTabName)) {
957                // pass on to Per OBlock Edit panel, includes a BlockPath table
958                log.error("Failed to open OBlock Path table for {}", blockSystemName);
959            }
960        } else {
961            openBlockPathFrame(blockSystemName); // an editable table of all paths on this block
962        }
963    }
964
965    // ***************** Block-Path Frame for _desktop **************************
966    /**
967     * Open a block-specific Block-Path table in _desktop interface.
968     *
969     * @param blockSystemName of the OBlock
970     */
971    protected void openBlockPathFrame(String blockSystemName) {
972        BlockPathFrame frame = _blockPathMap.get(blockSystemName);
973        if (frame == null) {
974            OBlock block = InstanceManager.getDefault(OBlockManager.class).getBySystemName(blockSystemName);
975            if (block == null) {
976                return;
977            }
978            frame = makeBlockPathFrame(block);
979            // store frame in Map
980            _blockPathMap.put(blockSystemName, frame);
981            frame.setVisible(true);
982            desktopframe.getContentPane().add(frame);
983        } else {
984            frame.setVisible(true);
985            try {
986                frame.setIcon(false);
987            } catch (PropertyVetoException pve) {
988                log.warn("BlockPath Table Frame for \"{}\" vetoed setIcon", blockSystemName, pve);
989            }
990        }
991        frame.moveToFront();
992    }
993
994    // common dispose
995    protected void disposeBlockPathFrame(OBlock block) {
996        if (!_tabbed) {
997            //BlockPathFrame frame = _blockPathMap.get(block.getSystemName());
998            // TODO frame.getModel().removeListener();
999            //_blockPathMap.remove(block.getSystemName()); // block not stored in map, required to remove listener?
1000            // frame.dispose(); not required (closeable window)
1001            //} else {
1002            BlockPathFrame frame = _blockPathMap.get(block.getSystemName());
1003            frame.getModel().removeListener();
1004            _blockPathMap.remove(block.getSystemName());
1005            frame.dispose();
1006        }
1007    }
1008
1009    // *************** Block-Path InternalFrame for _desktop ***********************
1010
1011    protected BlockPathFrame makeBlockPathFrame(OBlock block) {
1012        String title = Bundle.getMessage("TitleBlockPathTable", block.getDisplayName());
1013        // create table
1014        BlockPathTableModel model = new BlockPathTableModel(block, this);
1015        JPanel contentPane = makeBlockPathTablePanel(model);
1016
1017        BlockPathFrame frame = new BlockPathFrame(title, true, true, false, true);
1018        frame.setModel(model, block.getSystemName());
1019        frame.addInternalFrameListener(this);
1020        frame.setContentPane(contentPane);
1021        //frame.setClosable(true); // set in ctor
1022        frame.setLocation(50, 30);
1023        frame.pack();
1024        return frame;
1025    }
1026
1027    // *************** Block-Path Edit Panel for _tabbed ***********************
1028
1029    protected BlockPathJPanel makeBlockPathEditPanel(OBlock block) {
1030        // Path Table placed on jmri.jmrit.beanedit OBlockEditAction - Paths tab
1031        String title = Bundle.getMessage("TitleBlockPathEditor", block.getDisplayName());
1032        // create table
1033        BlockPathTableModel model = new BlockPathTableModel(block, this);
1034        JPanel bpTablePane = makeBlockPathTablePanel(model);
1035        BlockPathJPanel panel = new BlockPathJPanel(title);
1036        panel.setModel(model, block.getSystemName());
1037        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
1038        panel.add(bpTablePane);
1039
1040        // Add Path Button
1041        JPanel tblButtons = new JPanel();
1042        tblButtons.setLayout(new BorderLayout(10, 10));
1043        tblButtons.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));
1044        tblButtons.setLayout(new BoxLayout(tblButtons, BoxLayout.Y_AXIS));
1045
1046        JButton addPathButton = new JButton(Bundle.getMessage("ButtonAddPath"));
1047        ActionListener addPathAction = e -> {
1048            // New Path uses the same editor pane as Edit Path
1049            if (!isPathEdit()) {
1050                setPathEdit(true);
1051                log.debug("makeBlockPathEditPanel pathEdit=True");
1052                openPathEditor(block.getDisplayName(), null, model);
1053            } else {
1054                log.warn("Close BlockPath Editor to reopen");
1055            }
1056        };
1057        addPathButton.addActionListener(addPathAction);
1058        addPathButton.setToolTipText(Bundle.getMessage("AddPathTabbedPrompt"));
1059        tblButtons.add(addPathButton);
1060        panel.add(tblButtons);
1061
1062        //panel.pack();
1063        return panel;
1064    }
1065
1066    // prevent more than 1 edit pane being opened at the same time
1067    protected void setPathEdit(boolean edit) {
1068        pathEdit = edit;
1069    }
1070
1071    protected boolean isPathEdit() {
1072        return pathEdit;
1073    }
1074
1075
1076    // ***************** Block-Path Frame class for _desktop **************************
1077    protected static class BlockPathFrame extends JInternalFrame {
1078
1079        BlockPathTableModel blockPathModel;
1080
1081        BlockPathFrame(String title, boolean resizable, boolean closable,
1082                       boolean maximizable, boolean iconifiable) {
1083            super(title, resizable, closable, maximizable, iconifiable);
1084        }
1085
1086        BlockPathTableModel getModel() {
1087            return blockPathModel;
1088        }
1089
1090        void setModel(BlockPathTableModel model, String blockName) {
1091            blockPathModel = model;
1092            setName(blockName);
1093        }
1094    }
1095
1096    // ***************** Block-Path JPanel class for _tabbed **************************
1097    public static class BlockPathJPanel extends JPanel {
1098
1099        BlockPathTableModel blockPathModel;
1100
1101        BlockPathJPanel(String title) {
1102            super();
1103            super.setName(title);
1104        }
1105
1106        BlockPathTableModel getModel() {
1107            return blockPathModel;
1108        }
1109
1110        void setModel(BlockPathTableModel model, String blockName) {
1111            blockPathModel = model;
1112            setName(blockName);
1113        }
1114    }
1115
1116    /*
1117     * ********************* Block-Path Table Panel for _desktop and _tabbed ***********************
1118     */
1119    protected JPanel makeBlockPathTablePanel(BlockPathTableModel _model) {
1120        JTable blockPathTable = makeBlockPathTable(_model); // styled
1121
1122        // get table
1123        JScrollPane tablePane = new JScrollPane(blockPathTable);
1124        JPanel contentPane = new JPanel();
1125        contentPane.setLayout(new BorderLayout(5, 5));
1126        if (_tabbed) {
1127            // a bit more styling
1128            blockPathTable.setPreferredScrollableViewportSize(new Dimension(600, 100));
1129        } else {
1130            JLabel prompt = new JLabel(Bundle.getMessage("AddPathPrompt"));
1131            contentPane.add(prompt, BorderLayout.NORTH);
1132        }
1133        contentPane.add(tablePane, BorderLayout.CENTER);
1134
1135        return contentPane;
1136    }
1137
1138    protected JTable makeBlockPathTable(BlockPathTableModel _model) {
1139        JTable blockPathTable = new JTable(_model);
1140        // configure DnD
1141        blockPathTable.setTransferHandler(new jmri.util.DnDTableImportExportHandler(new int[]{BlockPathTableModel.EDIT_COL, BlockPathTableModel.DELETE_COL, BlockPathTableModel.UNITSCOL}));
1142        blockPathTable.setDragEnabled(true);
1143        // style table
1144        blockPathTable.getColumnModel().getColumn(BlockPathTableModel.UNITSCOL).setCellRenderer(new ToggleButtonRenderer(Bundle.getMessage("cm"), Bundle.getMessage("in")));
1145        blockPathTable.getColumnModel().getColumn(BlockPathTableModel.UNITSCOL).setCellEditor(new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("cm"), Bundle.getMessage("in")));
1146        blockPathTable.getColumnModel().getColumn(BlockPathTableModel.EDIT_COL).setCellEditor(new ButtonEditor(new JButton()));
1147        blockPathTable.getColumnModel().getColumn(BlockPathTableModel.EDIT_COL).setCellRenderer(new ButtonRenderer());
1148        blockPathTable.getColumnModel().getColumn(BlockPathTableModel.DELETE_COL).setCellEditor(new ButtonEditor(new JButton()));
1149        blockPathTable.getColumnModel().getColumn(BlockPathTableModel.DELETE_COL).setCellRenderer(new ButtonRenderer());
1150        // units, etc renderer
1151
1152        for (int i = 0; i < _model.getColumnCount(); i++) {
1153            int width = _model.getPreferredWidth(i);
1154            blockPathTable.getColumnModel().getColumn(i).setPreferredWidth(width);
1155        }
1156        blockPathTable.doLayout();
1157        int tableWidth = blockPathTable.getPreferredSize().width;
1158        blockPathTable.setRowHeight(ROW_HEIGHT);
1159        blockPathTable.setPreferredScrollableViewportSize(new java.awt.Dimension(tableWidth, Math.min(TableFrames.ROW_HEIGHT * 10, maxHeight)));
1160
1161        return blockPathTable;
1162    }
1163
1164    /**
1165     * ********************* Path-Turnout Frame ***********************************
1166     */
1167
1168    // ********************* Path-Turnout Frame class for _desktop ****************
1169    protected static class PathTurnoutFrame extends JInternalFrame {
1170
1171        /**
1172         * Remember the tableModel
1173         */
1174        PathTurnoutTableModel pathTurnoutModel;
1175
1176        PathTurnoutFrame(String title, boolean resizable, boolean closable,
1177                boolean maximizable, boolean iconifiable) {
1178            super(title, resizable, closable, maximizable, iconifiable);
1179        }
1180
1181        PathTurnoutTableModel getModel() {
1182            return pathTurnoutModel;
1183        }
1184
1185        void setModel(PathTurnoutTableModel model) {
1186            pathTurnoutModel = model;
1187        }
1188    }
1189
1190    /**
1191     * ********************* Path-Turnout JPanel class for _tabbed *****************
1192     */
1193    protected static class PathTurnoutJPanel extends JPanel {
1194
1195        /**
1196         * Remember the tableModel
1197         */
1198        PathTurnoutTableModel pathTurnoutModel;
1199
1200        PathTurnoutJPanel(String pathname) {
1201            super();
1202            setName(pathname);
1203        }
1204
1205        PathTurnoutTableModel getModel() {
1206            return pathTurnoutModel;
1207        }
1208
1209        void setModel(PathTurnoutTableModel model) {
1210            pathTurnoutModel = model;
1211        }
1212    }
1213
1214    /*
1215     * ********************* Path-TurnoutFrame for _desktop *************************
1216     */
1217    protected PathTurnoutFrame makePathTurnoutFrame(OBlock block, String pathName) {
1218        String title = Bundle.getMessage("TitlePathTurnoutTable", block.getDisplayName(), pathName);
1219        PathTurnoutFrame frame = new PathTurnoutFrame(title, true, true, false, true);
1220        if (log.isDebugEnabled()) {
1221            log.debug("makePathTurnoutFrame for Block {} and Path {} on _desktop", block.getDisplayName(), pathName);
1222        }
1223        frame.setName(makePathTurnoutName(block.getSystemName(), pathName));
1224        OPath path = block.getPathByName(pathName);
1225        if (path == null) {
1226            return null;
1227        }
1228        PathTurnoutTableModel pathTurnoutModel = new PathTurnoutTableModel(path, frame);
1229        frame.setModel(pathTurnoutModel);
1230
1231        JTable pathTurnoutTable = makePathTurnoutTable(pathTurnoutModel);
1232
1233        JScrollPane tablePane = new JScrollPane(pathTurnoutTable);
1234
1235        JPanel contentPane = new JPanel();
1236        contentPane.setLayout(new BorderLayout(5, 5));
1237        JLabel prompt = new JLabel(Bundle.getMessage("AddTurnoutPrompt"));
1238        contentPane.add(prompt, BorderLayout.NORTH);
1239        contentPane.add(tablePane, BorderLayout.CENTER);
1240
1241        frame.addInternalFrameListener(this);
1242        frame.setContentPane(contentPane);
1243        //frame.setClosable(true); // is set in ctor
1244        frame.setLocation(10, 270);
1245        frame.pack();
1246        return frame;
1247    }
1248
1249    /*
1250     * ********************* Path-TurnoutPanel for _tabbed *****************************
1251     */
1252    protected PathTurnoutJPanel makePathTurnoutPanel(@Nonnull OBlock block, @CheckForNull String pathName) {
1253        String title = Bundle.getMessage("TitlePathTurnoutTable", block.getDisplayName(), pathName);
1254        PathTurnoutJPanel panel = new PathTurnoutJPanel(title);
1255        PathTurnoutTableModel pathTurnoutModel;
1256        JTable pathTurnoutTable;
1257        JButton addTurnoutButton = new JButton(Bundle.getMessage("ButtonAddTurnout"));
1258        addTurnoutButton.setToolTipText(Bundle.getMessage("AddTurnoutTabbedPrompt"));
1259        JLabel prompt = new JLabel();
1260        prompt.setFont(prompt.getFont().deriveFont(0.9f * new JLabel().getFont().getSize())); // a bit smaller
1261        prompt.setForeground(Color.gray);
1262
1263        if (pathName == null) {
1264            panel.setName(makePathTurnoutName(block.getSystemName(), "<new Path>"));
1265            String[] columnHeaders = {Bundle.getMessage("Turnouts")};
1266            String[][] emptyTable = new String[][] {{Bundle.getMessage("None")}};
1267            pathTurnoutTable = new JTable(emptyTable, columnHeaders); // dummy table
1268            addTurnoutButton.setEnabled(false);
1269            prompt.setText(Bundle.getMessage("TurnoutTablePromptNew"));
1270        } else {
1271            panel.setName(makePathTurnoutName(block.getSystemName(), pathName));
1272            final OPath path = block.getPathByName(pathName); // final for actionhandler
1273            if (path == null) {
1274                return null; // unexpected
1275            }
1276            pathTurnoutModel = new PathTurnoutTableModel(path);
1277            pathTurnoutTable = makePathTurnoutTable(pathTurnoutModel);
1278            panel.setModel(pathTurnoutModel);
1279            ActionListener addTurnoutAction= e -> addTurnoutPane(path, pathTurnoutModel);
1280            addTurnoutButton.addActionListener(addTurnoutAction);
1281            prompt.setText(Bundle.getMessage("TurnoutTablePrompt"));
1282        }
1283        JScrollPane tablePane = new JScrollPane(pathTurnoutTable);
1284
1285        JPanel tblButtons = new JPanel();
1286        tblButtons.setLayout(new BorderLayout(10, 10));
1287        tblButtons.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));
1288        tblButtons.setLayout(new BoxLayout(tblButtons, BoxLayout.Y_AXIS));
1289        tblButtons.add(addTurnoutButton);
1290        // add more to frame?
1291
1292        panel.setLayout(new BorderLayout(5, 5));
1293
1294        panel.add(prompt, BorderLayout.NORTH);
1295        panel.add(tablePane, BorderLayout.CENTER);
1296        panel.add(tblButtons, BorderLayout.SOUTH);
1297
1298        return panel;
1299    }
1300
1301    /*
1302     * ********************* Path-Turnout Table *****************************
1303     */
1304    protected JTable makePathTurnoutTable(PathTurnoutTableModel model) {
1305        JTable pathTurnoutTable = new JTable(model);
1306        pathTurnoutTable.setTransferHandler(new jmri.util.DnDTableImportExportHandler(
1307                new int[]{PathTurnoutTableModel.STATE_COL, PathTurnoutTableModel.DELETE_COL}));
1308        pathTurnoutTable.setDragEnabled(true);
1309
1310        model.configTurnoutStateColumn(pathTurnoutTable); // use real combo
1311        pathTurnoutTable.getColumnModel().getColumn(PathTurnoutTableModel.DELETE_COL).setCellEditor(new ButtonEditor(new JButton()));
1312        pathTurnoutTable.getColumnModel().getColumn(PathTurnoutTableModel.DELETE_COL).setCellRenderer(new ButtonRenderer());
1313        //pathTurnoutTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
1314        for (int i = 0; i < model.getColumnCount(); i++) {
1315            int width = model.getPreferredWidth(i);
1316            pathTurnoutTable.getColumnModel().getColumn(i).setPreferredWidth(width);
1317        }
1318        pathTurnoutTable.doLayout();
1319        int tableWidth = pathTurnoutTable.getPreferredSize().width;
1320        pathTurnoutTable.setRowHeight(ROW_HEIGHT);
1321        pathTurnoutTable.setPreferredScrollableViewportSize(new java.awt.Dimension(tableWidth,
1322            Math.min(TableFrames.ROW_HEIGHT * 5, maxHeight)));
1323
1324        return pathTurnoutTable;
1325    }
1326
1327    /**
1328     * Create a coded id for a path turnout.
1329     *
1330     * @param blockSysName oblock system name
1331     * @param pathName the path through the oblock for which to display turnouts set
1332     * @return name of the pathTurnout, example "%path 1-3&amp;block-1"
1333     */
1334    protected String makePathTurnoutName(String blockSysName, String pathName) {
1335        return "%" + pathName + "&" + blockSysName;
1336    }
1337
1338    // ********************* Open Path-Turnout Frame for _desktop *****************************
1339    /**
1340     * Open a block-specific PathTurnouts table as a JInternalFrame for _desktop from BlockPathTableModel
1341     *
1342     * @param pathTurnoutName name of turnout configured on Path
1343     */
1344    protected void openPathTurnoutFrame(String pathTurnoutName) {
1345        PathTurnoutFrame frame = _pathTurnoutMap.get(pathTurnoutName);
1346        if (frame == null) {
1347            int index = pathTurnoutName.indexOf('&');
1348            String pathName = pathTurnoutName.substring(1, index);
1349            String blockName = pathTurnoutName.substring(index + 1);
1350            OBlock block = InstanceManager.getDefault(OBlockManager.class).getBySystemName(blockName);
1351            if (block == null) {
1352                return;
1353            }
1354            frame = makePathTurnoutFrame(block, pathName);
1355            if (frame == null) {
1356                return;
1357            }
1358            _pathTurnoutMap.put(pathTurnoutName, frame);
1359            frame.setVisible(true);
1360            desktopframe.getContentPane().add(frame);
1361        } else {
1362            frame.setVisible(true);
1363            try {
1364                frame.setIcon(false);
1365            } catch (PropertyVetoException pve) {
1366                log.warn("PathTurnout Table Frame for \"{}\" vetoed setIcon", pathTurnoutName, pve);
1367            }
1368        }
1369        frame.moveToFront();
1370    }
1371
1372    // *********** Open stand alone Path-Turnout Edit Panel for _tabbed *********************
1373    /**
1374     * Open a block-specific PathTurnouts edit pane as a JmriJFrame for _tabbed from menu.
1375     * TODO fix menu access to pathturnouts on _tabbed in ListedTableView, single table menus OK
1376     *
1377     * @param pathTurnoutName name of turnout configured on Path
1378     */
1379    protected void openPathTurnoutEditor(String pathTurnoutName) {
1380        int index = pathTurnoutName.indexOf('&');
1381        String pathName = pathTurnoutName.substring(1, index);
1382        String blockName = pathTurnoutName.substring(index + 1);
1383        OBlock block = InstanceManager.getDefault(OBlockManager.class).getBySystemName(blockName);
1384        if (block == null) {
1385            return;
1386        }
1387        OPath path = block.getPathByName(pathName);
1388        if (path == null) {
1389            return;
1390        }
1391        PathTurnoutJPanel turnouttable = makePathTurnoutPanel(block, pathName);
1392        // shows the turnouts on this path, already includes [Add Turnout...] button
1393        JmriJFrame frame = new JmriJFrame(Bundle.getMessage("TitlePathTurnoutTable", block.getDisplayName(), pathName));
1394        frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.PAGE_AXIS));
1395        frame.setSize(370, 250);
1396
1397        JPanel p = new JPanel();
1398        p.setLayout(new BoxLayout(p, BoxLayout.PAGE_AXIS));
1399        p.add(turnouttable);
1400        JButton ok;
1401        p.add(ok = new JButton(Bundle.getMessage("ButtonOK"))); // no need to save things, handled by TurnoutTable
1402        ok.addActionListener((ActionEvent e) -> frame.dispose());
1403        frame.getContentPane().add(p);
1404        frame.pack();
1405        frame.setVisible(true);
1406    }
1407
1408    /**
1409     * Add new Turnout pane, called from makePathTurnoutPanel on _tabbed interface.
1410     *
1411     * @param path to link this turnout setting to
1412     * @param pathTurnoutModel displayed table of turnouts currently set on this path
1413     */
1414    protected void addTurnoutPane(OPath path, PathTurnoutTableModel pathTurnoutModel) {
1415        JmriJFrame frame = new JmriJFrame(Bundle.getMessage("NewTurnoutTitle", path.getName()));
1416        frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.PAGE_AXIS));
1417        frame.setSize(200, 150);
1418
1419        JPanel p = new JPanel();
1420
1421        final NamedBeanComboBox<Turnout> turnoutBox = new NamedBeanComboBox<>(InstanceManager.getDefault(TurnoutManager.class), null, NamedBean.DisplayOptions.DISPLAYNAME);
1422        JComboBox<String> stateCombo = new JComboBox<>();
1423        JLabel statusBar = new JLabel(Bundle.getMessage("AddXStatusInitial1", Bundle.getMessage("BeanNameTurnout"), Bundle.getMessage("ButtonOK")), JLabel.LEADING);
1424        stateCombo.addItem(SET_THROWN);
1425        stateCombo.addItem(SET_CLOSED);
1426        turnoutBox.setToolTipText(Bundle.getMessage("TurnoutEditToolTip"));
1427
1428        JPanel p1 = new JPanel();
1429        p1.setLayout(new BoxLayout(p1, BoxLayout.LINE_AXIS));
1430        p1.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameTurnout"))));
1431        p1.add(turnoutBox);
1432        p.add(p1);
1433
1434        p1 = new JPanel();
1435        p1.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("ColumnLabelSetState"))));
1436        p1.add(stateCombo);
1437        p.add(p1);
1438
1439        p.add(Box.createVerticalGlue());
1440
1441        JPanel p2 = new JPanel();
1442        statusBar.setFont(statusBar.getFont().deriveFont(0.9f * (new JLabel()).getFont().getSize())); // a bit smaller
1443        if (turnoutBox.getItemCount() < 1) {
1444            statusBar.setText(Bundle.getMessage("NotEnoughTurnouts"));
1445            statusBar.setForeground(Color.red);
1446        } else {
1447            statusBar.setForeground(Color.gray);
1448        }
1449        p2.add(statusBar);
1450        p.add(p2);
1451
1452        JPanel btns = new JPanel();
1453        btns.setLayout(new BoxLayout(btns, BoxLayout.LINE_AXIS));
1454        JButton cancel;
1455        btns.add(cancel = new JButton(Bundle.getMessage("ButtonCancel")));
1456        cancel.addActionListener((ActionEvent e) -> frame.dispose());
1457        JButton ok;
1458        btns.add(ok = new JButton(Bundle.getMessage("ButtonOK")));
1459        ok.addActionListener((ActionEvent e) -> {
1460            if (turnoutBox.getSelectedItem() == null || turnoutBox.getSelectedIndex() < 0) {
1461                statusBar.setText(Bundle.getMessage("WarningSelectionEmpty"));
1462                statusBar.setForeground(Color.red);
1463            } else {
1464                String user = turnoutBox.getSelectedItemDisplayName();
1465                Turnout t = InstanceManager.turnoutManagerInstance().getTurnout(user);
1466                if (t != null) {
1467                    int s;
1468                    if (stateCombo.getSelectedItem() != null && stateCombo.getSelectedItem().equals(SET_CLOSED)) {
1469                        s = Turnout.CLOSED;
1470                    } else {
1471                        s = Turnout.THROWN;
1472                    }
1473                    BeanSetting bs = new BeanSetting(t, user, s);
1474                    path.addSetting(bs);
1475                    if (pathTurnoutModel != null) {
1476                        pathTurnoutModel.fireTableDataChanged();
1477                    }
1478                } else {
1479                    log.error("PathTurnout {} not found", user);
1480                }
1481                frame.dispose();
1482            }
1483        });
1484        p.add(btns, BorderLayout.SOUTH);
1485
1486        frame.getContentPane().add(p);
1487        frame.pack();
1488        frame.setVisible(true);
1489    }
1490
1491    /*
1492     * ********************* End of tables and frames methods *****************************
1493     */
1494
1495    // Shared warning dialog method. Store user pref to suppress further mentions.
1496    protected int verifyWarning(String message) {
1497        int val = 0;
1498        if (_showWarnings) {
1499            // verify deletion
1500            val = JmriJOptionPane.showOptionDialog(null,
1501                    message, Bundle.getMessage("WarningTitle"),
1502                    JmriJOptionPane.YES_NO_CANCEL_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null,
1503                    new Object[]{Bundle.getMessage("ButtonYes"),
1504                        Bundle.getMessage("ButtonYesPlus"),
1505                        Bundle.getMessage("ButtonNo")},
1506                    Bundle.getMessage("ButtonNo")); // default choice = No
1507            if (val == 1) { // suppress future warnings
1508                _showWarnings = false;
1509            }
1510        }
1511        return val;
1512    }
1513
1514    /*
1515     * ********************* InternalFrameListener implementation for _desktop *****************
1516     */
1517    @Override
1518    public void internalFrameClosing(InternalFrameEvent e) {
1519        JInternalFrame frame = (JInternalFrame)e.getSource();
1520        log.debug("Internal frame closing: {}", frame.getTitle());
1521        if (frame.getTitle().equals(Bundle.getMessage("TitleBlockTable"))) {
1522            showHideFrame(_blockTableFrame, openBlock, "OpenBlockMenu");
1523        }
1524    }
1525
1526    // clean up on close on _desktop
1527    // for _tabbed this is handled in the Edit pane applyPressed() method
1528    @Override
1529    public void internalFrameClosed(InternalFrameEvent e) {
1530        JInternalFrame frame = (JInternalFrame) e.getSource();
1531        String name = frame.getName();
1532        if (log.isDebugEnabled()) {
1533            log.debug("Internal frame closed: {}, name= {} size ({}, {})",
1534                    frame.getTitle(), name,
1535                    frame.getSize().getWidth(), frame.getSize().getHeight());
1536        }
1537        if (name != null && name.startsWith("OB")) {
1538            _blockPathMap.remove(name);
1539            if (frame instanceof BlockPathFrame) {
1540                String msg = WarrantTableAction.getDefault().checkPathPortals(((BlockPathFrame) frame).getModel().getBlock());
1541                if (!msg.isEmpty()) {
1542                    JmriJOptionPane.showMessageDialog(desktopframe, msg,
1543                            Bundle.getMessage("InfoTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
1544                }
1545                ((BlockPathFrame) frame).getModel().removeListener();
1546            }
1547        } else {
1548            if (frame instanceof PathTurnoutFrame) {
1549                ((PathTurnoutFrame) frame).getModel().removeListener();
1550            }
1551            _pathTurnoutMap.remove(name);
1552        }
1553    }
1554
1555    @Override
1556    public void internalFrameOpened(InternalFrameEvent e) {
1557        /*  JInternalFrame frame = (JInternalFrame)e.getSource();
1558         if (log.isDebugEnabled()) {
1559             log.debug("Internal frame Opened: {}, name= {} size ({}, {})",
1560                    frame.getTitle(), frame.getName(),
1561                    frame.getSize().getWidth(), frame.getSize().getHeight());
1562          }*/
1563    }
1564
1565    @Override
1566    public void internalFrameIconified(InternalFrameEvent e) {
1567        JInternalFrame frame = (JInternalFrame) e.getSource();
1568        String name = frame.getName();
1569        if (log.isDebugEnabled()) {
1570            log.debug("Internal frame Iconified: {}, name= {} size ({}, {})",
1571                    frame.getTitle(), name,
1572                    frame.getSize().getWidth(), frame.getSize().getHeight());
1573        }
1574        if (name != null && name.startsWith(oblockPrefix())) {
1575            if (frame instanceof BlockPathFrame) {
1576                String msg = WarrantTableAction.getDefault().checkPathPortals(((BlockPathFrame) frame).getModel().getBlock());
1577                JmriJOptionPane.showMessageDialog(desktopframe, msg,
1578                    Bundle.getMessage("InfoTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
1579            }
1580        }
1581    }
1582
1583    @Override
1584    public void internalFrameDeiconified(InternalFrameEvent e) {
1585        //JInternalFrame frame = (JInternalFrame)e.getSource();
1586        //log.debug("Internal frame deiconified: {}", frame.getTitle());
1587    }
1588
1589    @Override
1590    public void internalFrameActivated(InternalFrameEvent e) {
1591        //JInternalFrame frame = (JInternalFrame)e.getSource();
1592        //log.debug("Internal frame activated: {}", frame.getTitle());
1593    }
1594
1595    @Override
1596    public void internalFrameDeactivated(InternalFrameEvent e) {
1597        //JInternalFrame frame = (JInternalFrame)e.getSource();
1598        //log.debug("Internal frame deactivated: {}", frame.getTitle());
1599    }
1600
1601    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TableFrames.class);
1602
1603}