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