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.table.ButtonEditor;
033import jmri.util.table.ButtonRenderer;
034import jmri.util.table.ToggleButtonEditor;
035import jmri.util.table.ToggleButtonRenderer;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039/**
040 * GUI to define OBlocks.
041 * <p>
042 * Core code can be used with two interfaces:
043 * <ul>
044 *     <li>original "desktop" InternalFrames (displays as InternalJFrames inside a JmriJFrame)
045 *     <li>JMRI standard Tabbed tables (displays as Js inside a ListedTableFrame)
046 * </ul>
047 * The _tabbed field decides, it is set in prefs (restart required). TableFrames itself has no UI.
048 * <hr>
049 * This file is part of JMRI.
050 * <p>
051 * JMRI is free software; you can redistribute it and/or modify it under the
052 * terms of version 2 of the GNU General Public License as published by the Free
053 * Software Foundation. See the "COPYING" file for a copy of this license.
054 * <p>
055 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
056 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
057 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
058 *
059 * @author Pete Cressman (C) 2010
060 * @author Egbert Broerse (C) 2020
061 */
062public class TableFrames implements InternalFrameListener {
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 final static String portalPrefix = "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 JDesktopPane _desktop;
086    private final int maxHeight = 600;
087    private JInternalFrame _blockTableFrame;
088    private JInternalFrame _portalTableFrame;
089    private JInternalFrame _blockPortalXRefFrame;
090    private JInternalFrame _signalTableFrame;
091
092    private boolean _showWarnings = true;
093    private JMenuItem _showWarnItem;
094    private JMenu tablesMenu;
095    private JMenuItem openBlock;
096    private JMenuItem openPortal;
097    private JMenuItem openXRef;
098    private JMenuItem openSignal;
099    private JMenuItem _setUnits;
100
101    private final HashMap<String, BlockPathFrame> _blockPathMap = new HashMap<>();
102    private final HashMap<String, PathTurnoutFrame> _pathTurnoutMap = new HashMap<>();
103    // _tabbed edit panes are not stored in a map
104
105    public TableFrames() {
106        this("OBlock Tables");
107    } // NOI18N, title will be updated during init
108
109    public TableFrames(String actionName) {
110        _tabbed = InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed();
111        _title = actionName;
112        if (!_tabbed) {
113            desktopframe = new JmriJFrame(actionName);
114        }
115        // create the tables
116        _oBlockModel = new OBlockTableModel(this);
117        _portalModel = new PortalTableModel(this);
118        _blockPortalXRefModel = new BlockPortalTableModel(_oBlockModel);
119        _signalModel = new SignalTableModel(this);
120        _signalModel.init();
121    }
122
123    public OBlockTableModel getOblockTableModel() {
124        return _oBlockModel;
125    }
126    public PortalTableModel getPortalTableModel() {
127        return _portalModel;
128    }
129    public BlockPortalTableModel getPortalXRefTableModel() {
130        return _blockPortalXRefModel;
131    }
132    public BlockPathTableModel getBlockPathTableModel(OBlock block) {
133        return new BlockPathTableModel(block, this);
134    }
135    public SignalTableModel getSignalTableModel() {
136        return _signalModel;
137    }
138
139    public void initComponents() {
140        // build and display the classic floating "OBlock and its..." desktop interface
141        if (!_tabbed) { // just to be sure
142            setTitle(Bundle.getMessage("TitleOBlocks"));
143
144            // build tables
145            _blockTableFrame = buildFrame(_oBlockModel, Bundle.getMessage("TitleBlockTable"), Bundle.getMessage("AddBlockPrompt"));
146            _blockTableFrame.setVisible(true);
147
148            _portalTableFrame = buildFrame(_portalModel, Bundle.getMessage("TitlePortalTable"), Bundle.getMessage("AddPortalPrompt"));
149            _portalTableFrame.setVisible(true);
150
151            _signalTableFrame = buildFrame(_signalModel, Bundle.getMessage("TitleSignalTable"), Bundle.getMessage("AddSignalPrompt"));
152            _signalTableFrame.setVisible(false);
153
154            _blockPortalXRefFrame = buildFrame(_blockPortalXRefModel, Bundle.getMessage("TitleBlockPortalXRef"), Bundle.getMessage("XRefPrompt"));
155            _blockPortalXRefFrame.setVisible(false); // start with frame hidden
156
157            // build the print menu after the tables have been created
158            desktopframe.setTitle(getTitle());
159            desktopframe.setJMenuBar(addMenus(desktopframe.getJMenuBar()));
160            desktopframe.addHelpMenu("package.jmri.jmrit.logix.OBlockTable", true);
161
162            createDesktop(); // adds tables as windows on desktopframe._desktop
163            desktopframe.setLocation(10, 30);
164            desktopframe.setVisible(true);
165            desktopframe.pack();
166            addCloseListener(desktopframe);
167
168            // finally check table contents for errors
169            WarrantTableAction.getDefault().errorCheck();
170        }
171    }
172
173    public JMenuBar addMenus(JMenuBar mBar) {
174        if (mBar == null) {
175            mBar = new JMenuBar();
176        }
177        // create and add the menus
178        if (!_tabbed) { // _tabbed Print is handled via getPrintItem() in OBlockTablePanel
179            // File menu
180            JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
181            fileMenu.add(new jmri.configurexml.StoreMenu());
182            fileMenu.add(getPrintMenuItems(_oBlockTable, _portalTable, _signalTable, _blockPortalTable)); // add the print items
183            mBar.add(fileMenu);
184
185            // Edit menu
186            JMenu editMenu = new JMenu(Bundle.getMessage("MenuEdit"));
187            editMenu.setMnemonic(KeyEvent.VK_E);
188            TransferActionListener actionListener = new TransferActionListener();
189
190            JMenuItem menuItem = new JMenuItem(Bundle.getMessage("MenuItemCut"));
191            menuItem.setActionCommand((String) TransferHandler.getCutAction().getValue(Action.NAME));
192            menuItem.addActionListener(actionListener);
193            if (SystemType.isMacOSX()) {
194                menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.META_DOWN_MASK));
195            } else {
196                menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK));
197            }
198            menuItem.setMnemonic(KeyEvent.VK_T);
199            editMenu.add(menuItem);
200
201            menuItem = new JMenuItem(Bundle.getMessage("MenuItemCopy"));
202            menuItem.setActionCommand((String) TransferHandler.getCopyAction().getValue(Action.NAME));
203            menuItem.addActionListener(actionListener);
204            if (SystemType.isMacOSX()) {
205                menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.META_DOWN_MASK));
206            } else {
207                menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK));
208            }
209            menuItem.setMnemonic(KeyEvent.VK_C);
210            editMenu.add(menuItem);
211
212            menuItem = new JMenuItem(Bundle.getMessage("MenuItemPaste"));
213            menuItem.setActionCommand((String) TransferHandler.getPasteAction().getValue(Action.NAME));
214            menuItem.addActionListener(actionListener);
215            if (SystemType.isMacOSX()) {
216                menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.META_DOWN_MASK));
217            } else {
218                menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK));
219            }
220            menuItem.setMnemonic(KeyEvent.VK_P);
221            editMenu.add(menuItem);
222            mBar.add(editMenu);
223        }
224
225        mBar.add(getOptionMenu());
226        mBar.add(getTablesMenu());
227        return mBar;
228    }
229
230    public JMenu getPrintMenuItems(JTable oBlockTable, JTable portalTable, JTable signalTable, JTable blockPortalTable) {
231        JMenu print = new JMenu(Bundle.getMessage("PrintTable"));
232        JMenuItem printItem = new JMenuItem(Bundle.getMessage("PrintOBlockTable"));
233        print.add(printItem);
234        printItem.addActionListener(e -> {
235            try {
236                MessageFormat headerFormat = new MessageFormat(Bundle.getMessage("TitleOBlockTable"));
237                MessageFormat footerFormat = new MessageFormat(getTitle() + " page {0,number}");
238                oBlockTable.print(JTable.PrintMode.FIT_WIDTH, headerFormat, footerFormat);
239            } catch (java.awt.print.PrinterException e1) {
240                log.warn("error printing: {}", e1, e1);
241            }
242        });
243        printItem = new JMenuItem(Bundle.getMessage("PrintPortalTable"));
244        print.add(printItem);
245        printItem.addActionListener(e -> {
246            try {
247                MessageFormat headerFormat = new MessageFormat(Bundle.getMessage("TitlePortalTable"));
248                MessageFormat footerFormat = new MessageFormat(getTitle() + " page {0,number}");
249                portalTable.print(JTable.PrintMode.FIT_WIDTH, headerFormat, footerFormat);
250            } catch (java.awt.print.PrinterException e1) {
251                log.warn("error printing: {}", e1, e1);
252            }
253        });
254        printItem = new JMenuItem(Bundle.getMessage("PrintSignalTable"));
255        print.add(printItem);
256        printItem.addActionListener(e -> {
257            try {
258                MessageFormat headerFormat = new MessageFormat(Bundle.getMessage("TitleSignalTable"));
259                MessageFormat footerFormat = new MessageFormat(getTitle() + " page {0,number}");
260                signalTable.print(JTable.PrintMode.FIT_WIDTH, headerFormat, footerFormat);
261            } catch (java.awt.print.PrinterException e1) {
262                log.warn("error printing: {}", e1, e1);
263            }
264        });
265        printItem = new JMenuItem(Bundle.getMessage("PrintXRef"));
266        print.add(printItem);
267        printItem.addActionListener(e -> {
268            try {
269                MessageFormat headerFormat = new MessageFormat(Bundle.getMessage("OpenXRefMenu", ""));
270                MessageFormat footerFormat = new MessageFormat(getTitle() + " page {0,number}");
271                blockPortalTable.print(JTable.PrintMode.FIT_WIDTH, headerFormat, footerFormat);
272            } catch (java.awt.print.PrinterException e1) {
273                log.warn("error printing: {}", e1, e1);
274            }
275        });
276        return print;
277    }
278
279    // for desktop style interface, ignored for _tabbed
280    private void createDesktop() {
281        _desktop = new JDesktopPane();
282        _desktop.putClientProperty("JDesktopPane.dragMode", "outline"); // slower or faster?
283        int deskWidth = _blockTableFrame.getWidth();
284        int deskHeight = _blockTableFrame.getHeight();
285//        _desktop.setPreferredSize(new Dimension(deskWidth,
286//                deskHeight + _portalTableFrame.getHeight() + 100));
287        _desktop.setBackground(new Color(180,180,180));
288        desktopframe.setContentPane(_desktop);
289        desktopframe.setPreferredSize(new Dimension(deskWidth + 16,
290                deskHeight + _portalTableFrame.getHeight() + 64));
291
292        // placed at 0,0
293        _desktop.add(_blockTableFrame);
294        _portalTableFrame.setLocation(0, deskHeight);
295        _desktop.add(_portalTableFrame);
296        _signalTableFrame.setLocation(200, deskHeight+100);
297        _desktop.add(_signalTableFrame);
298        _blockPortalXRefFrame.setLocation(deskWidth - _blockPortalXRefFrame.getWidth(), deskHeight);
299        _desktop.add(_blockPortalXRefFrame);
300    }
301
302    public JMenu getOptionMenu() {
303        // Options menu
304        JMenu optionMenu = new JMenu(Bundle.getMessage("MenuOptions"));
305        _showWarnItem = new JMenuItem(Bundle.getMessage("SuppressWarning"));
306        _showWarnItem.addActionListener(event -> {
307            String cmd = event.getActionCommand();
308            setShowWarnings(cmd);
309        });
310        optionMenu.add(_showWarnItem);
311        setShowWarnings("ShowWarning");
312
313        JMenuItem importBlocksItem = new JMenuItem(Bundle.getMessage("ImportBlocksMenu"));
314        importBlocksItem.addActionListener((ActionEvent event) -> importBlocks());
315        optionMenu.add(importBlocksItem);
316        // disable ourself if there is no primary Block manager available
317        if (jmri.InstanceManager.getNullableDefault(jmri.BlockManager.class) == null) { // means Block list is empty
318            importBlocksItem.setEnabled(false);
319        }
320        _setUnits = new JMenuItem(Bundle.getMessage("changeUnits",
321                (_oBlockModel.isMetric() ? Bundle.getMessage("LengthInches") : Bundle.getMessage("LengthCentimeters"))));
322        _setUnits.addActionListener(event -> setUnits());
323        optionMenu.add(_setUnits);
324        return optionMenu;
325    }
326
327    public JMenu getTablesMenu() {
328        // Tables menu
329        tablesMenu = new JMenu(Bundle.getMessage("OpenMenu"));
330        updateOBlockTablesMenu(); // replaces the last 2 menu items with appropriate submenus
331        return tablesMenu;
332    }
333
334    private String oblockPrefix() {
335        if (oblockPrefix == null) {
336            oblockPrefix = InstanceManager.getDefault(OBlockManager.class).getSystemNamePrefix();
337        }
338        return oblockPrefix;
339    }
340
341    /**
342     * Get the JFrame containig all UI windows.
343     *
344     * @return the contentframe
345     */
346    protected JmriJFrame getDesktopFrame() {
347        return desktopframe;
348    }
349
350    /**
351     * Convert a copy of your current JMRI Blocks to OBlocks and connect them with Portals and Paths.
352     * Accessed from the Options menu.
353     * @throws IllegalArgumentException exception
354     * @author Egbert Broerse 2019
355     */
356    protected void importBlocks() throws IllegalArgumentException {
357        Manager<Block> bm = InstanceManager.getDefault(jmri.BlockManager.class);
358        OBlockManager obm = InstanceManager.getDefault(OBlockManager.class);
359        PortalManager pom = InstanceManager.getDefault(PortalManager.class);
360        SortedSet<Block> blkList = bm.getNamedBeanSet();
361        // don't return an element if there are no Blocks to include
362        if (blkList.isEmpty()) {
363            log.warn("no Blocks to convert"); // NOI18N
364            JOptionPane.showMessageDialog(desktopframe, Bundle.getMessage("ImportNoBlocks"),
365                    Bundle.getMessage("InfoTitle"), JOptionPane.INFORMATION_MESSAGE);
366            return;
367        } else {
368            if (_showWarnings) {
369                int reply = JOptionPane.showOptionDialog(null,
370                        Bundle.getMessage("ImportBlockConfirm", oblockPrefix(), blkList.size()),
371                        Bundle.getMessage("QuestionTitle"),
372                        JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null,
373                        new Object[]{Bundle.getMessage("ButtonYes"),
374                                Bundle.getMessage("ButtonCancel")},
375                        Bundle.getMessage("ButtonYes")); // standard JOptionPane can't be found in Jemmy log4J
376                if (reply > 0) {
377                    return;
378                }
379            }
380        }
381        for (Block b : blkList) {
382            try {
383                // read Block properties
384                String sName = b.getSystemName();
385                String uName = b.getUserName();
386                String blockNumber = sName.substring(sName.startsWith("IB:AUTO:") ? 8 : 3);
387                String oBlockName = oblockPrefix() + blockNumber;
388                String sensor = "";
389                Sensor s = b.getSensor();
390                if (s != null) {
391                    sensor = s.getDisplayName();
392                }
393                float length = b.getLengthMm(); // length is stored in Mm in OBlock.setLength(float)
394                int curve = b.getCurvature();
395                List<Path> blockPaths = b.getPaths();
396                String toBlockName;
397                Portal port = null;
398                int n = 0;
399                Portal prevPortal = null;
400
401                log.debug("start creating OBlock {} from Block {}", oBlockName, sName);
402                if ((uName != null) && (obm.getOBlock(uName) != null)) {
403                    log.warn("an OBlock with this user name already exists, replacing {}", uName);
404                }
405                // create the OBlock by systemName
406                OBlock oBlock = obm.provideOBlock(oBlockName);
407                oBlock.setUserName(uName);
408                if (!sensor.isEmpty()) {
409                    oBlock.setSensor(sensor);
410                }
411                oBlock.setMetricUnits(true); // length always stored in Mm in Block, so copy that for OBlock
412                oBlock.setLength(length);
413                oBlock.setCurvature(curve);
414
415                for (Path pa : blockPaths) {
416                    log.debug("Start loop: Path {} on Block {}", n, oBlockName);
417                    String toBlockNumber = pa.getBlock().getSystemName().substring(sName.startsWith("IB:AUTO:") ? 8 : 3);
418                    toBlockName = oblockPrefix() + toBlockNumber;
419                    String portalName = portalPrefix + toBlockNumber + "-" + blockNumber; // reversed name for new Portal
420                    port = pom.getPortal(portalName);
421                    if (port == null) {
422                        portalName = portalPrefix + blockNumber + "-" + toBlockNumber; // normal name for new Portal
423                        log.debug("new Portal {} on block {}, path #{}", portalName, toBlockName, n);
424                        port = pom.providePortal(portalName); // normally, will create a new Portal
425                        port.setFromBlock(oBlock, false);
426                        port.setToBlock(obm.provideOBlock(toBlockName), false); // create one if required
427                    } else {
428                        log.debug("duplicate Portal {} on block {}, path #{}", portalName, toBlockName, n);
429                        // Portal port already set
430                    }
431                    oBlock.addPortal(port);
432
433                    // create OPath from this Path
434                    OPath opa = new OPath(oBlock, "IP" + n++); // only needs to be unique within oBlock
435                    opa.setLength(oBlock.getLengthMm()); // simple assumption, works for default OBlock/OPath
436                    log.debug("new OPath #{} - {} on OBlock {}", n, opa.getName(), opa.getBlock().getDisplayName());
437                    oBlock.addPath(opa); // checks for duplicates, will add OPath to any Portals on oBlock as well
438                    log.debug("number of paths: {}", oBlock.getPaths().size());
439
440                    // set _fromPortal and _toPortal for each OPath in OBlock
441                    if (opa.getFromPortal() == null) {
442                        opa.setFromPortal(port);
443                    }
444                    for (BeanSetting bs : pa.getSettings()) {
445                        opa.addSetting(bs);
446                    }
447                    if ((opa.getToPortal() == null) && (prevPortal != null)) {
448                        opa.setToPortal(prevPortal);
449                        // leaves ToPortal in previously (first) created OPath n-1 empty
450                    }
451                    prevPortal = port; // remember the new portal for use as ToPortal in opposing OPath
452                    // user must remove nonsense manually unless...
453                }
454                // we use the last FromPortal as ToPortal in OPath P0
455                OPath p0 = oBlock.getPathByName("IP0");
456                if ((p0 != null) && (n > 1) && (p0.getToPortal() == null)) {
457                    p0.setToPortal(port);
458                }
459            } catch (IllegalArgumentException iae) {
460                log.error("Could not convert Block {} to OBlock. {}",
461                    b.getDisplayName(NamedBean.DisplayOptions.USERNAME_SYSTEMNAME), iae.getMessage());
462            }
463            // finished setting up 1 OBlock
464        }
465        // add recursive Path elements to FromBlock/ToBlock
466        SortedSet<OBlock> oblkList = obm.getNamedBeanSet();
467        for (OBlock oblk : oblkList) {
468            for (Portal po : oblk.getPortals()) {
469                OBlock oob = obm.getByUserName(po.getFromBlockName());
470                if (oob !=null) {
471                    oob.addPortal(po);
472                }
473                oob = obm.getByUserName(po.getToBlockName());
474                if (oob !=null) {
475                    oob.addPortal(po);
476                }
477            }
478        }
479        // storing and reloading will add in these items
480        WarrantTableAction.getDefault().errorCheck();
481        if (_showWarnings) {
482            JOptionPane.showMessageDialog(null,
483                    Bundle.getMessage("ImportBlockComplete", blkList.size(), oblkList.size()),
484                    Bundle.getMessage("MessageTitle"),
485                    JOptionPane.INFORMATION_MESSAGE); // standard JOptionPane can't be found in Jemmy log4J
486        }
487    }
488    // End of importBlocks() menu method
489
490    protected void setShowWarnings(String cmd) {
491        if (cmd.equals("ShowWarning")) {
492            _showWarnings = true;
493            _showWarnItem.setActionCommand("SuppressWarning");
494            _showWarnItem.setText(Bundle.getMessage("SuppressWarning"));
495        } else {
496            _showWarnings = false;
497            _showWarnItem.setActionCommand("ShowWarning");
498            _showWarnItem.setText(Bundle.getMessage("ShowWarning"));
499        }
500        log.debug("setShowWarnings: _showWarnings= {}", _showWarnings);
501    }
502
503    private void setUnits() {
504        _oBlockModel.changeUnits();
505        _setUnits.setText(Bundle.getMessage("changeUnits",
506                (_oBlockModel.isMetric() ? Bundle.getMessage("LengthInches") : Bundle.getMessage("LengthCentimeters"))));
507    }
508
509    // listen for _desktopframe closing
510    void addCloseListener(JmriJFrame desktop) {
511        desktop.addWindowListener(new java.awt.event.WindowAdapter() {
512            @Override
513            public void windowClosing(java.awt.event.WindowEvent e) {
514                WarrantTableAction.getDefault().errorCheck();
515                desktop.setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
516                // closing instead of hiding removes name from Windows menu.handle menu to read Show...
517                log.debug("windowClosing: {}", toString());
518                desktop.dispose();
519            }
520        });
521    }
522
523    private String getTitle() {
524        return _title;
525    }
526
527    private void setTitle(String title) {
528        _title = title;
529    }
530
531    /**
532     * Fill in the Open/Hide Tables menu on tablesMenu.
533     */
534    protected void updateOBlockTablesMenu() {
535        if (tablesMenu == null) {
536            return;
537        }
538        tablesMenu.removeAll();
539        if (!_tabbed) { // full menu in _desktop, open/show not available in _tabbed interface
540            // use string Bundle.getMessage("HideTable") to correct action in menu for all table open at start
541            openBlock = new JMenuItem(Bundle.getMessage("OpenBlockMenu", Bundle.getMessage("HideTable")));
542            tablesMenu.add(openBlock);
543            openBlock.addActionListener(event -> showHideFrame(_blockTableFrame, openBlock, "OpenBlockMenu"));
544
545            openPortal = new JMenuItem(Bundle.getMessage("OpenPortalMenu", Bundle.getMessage("HideTable")));
546            tablesMenu.add(openPortal);
547            openPortal.addActionListener(event -> showHideFrame(_portalTableFrame, openPortal, "OpenPortalMenu"));
548
549            openXRef = new JMenuItem(Bundle.getMessage("OpenXRefMenu", Bundle.getMessage("ShowTable")));
550            tablesMenu.add(openXRef);
551            openXRef.addActionListener(event -> showHideFrame(_blockPortalXRefFrame, openXRef, "OpenXRefMenu"));
552
553            openSignal = new JMenuItem(Bundle.getMessage("OpenSignalMenu", Bundle.getMessage("ShowTable")));
554            tablesMenu.add(openSignal);
555            openSignal.addActionListener(event -> showHideFrame(_signalTableFrame, openSignal, "OpenSignalMenu"));
556        }
557
558        OBlockManager manager = InstanceManager.getDefault(OBlockManager.class);
559
560        // Block-Path submenus
561        JMenu openBlockPath = new JMenu(Bundle.getMessage("OpenBlockPathMenu"));
562        ActionListener openFrameAction = e -> {
563            String blockSystemName = e.getActionCommand();
564            openBlockPathPane(blockSystemName, Bundle.getMessage("TitlePaths")); // handles both interfaces
565        };
566
567        if (manager.getNamedBeanSet().size() == 0) {
568            JMenuItem mi = new JMenuItem(Bundle.getMessage("NoBlockPathYet"));
569            mi.setEnabled(false);
570            openBlockPath.add(mi);
571        } else {
572            for (OBlock block : manager.getNamedBeanSet()) {
573                JMenuItem mi = new JMenuItem(Bundle.getMessage("OpenPathMenu", block.getDisplayName()));
574                mi.setActionCommand(block.getSystemName());
575                mi.addActionListener(openFrameAction);
576                openBlockPath.add(mi);
577            }
578        }
579        tablesMenu.add(openBlockPath);
580
581        // Path-Turnout submenus
582        JMenu openTurnoutPath = new JMenu(Bundle.getMessage("OpenBlockPathTurnoutMenu"));
583        if (manager.getNamedBeanSet().size() == 0) {
584            JMenuItem mi = new JMenuItem(Bundle.getMessage("NoPathTurnoutYet"));
585            mi.setEnabled(false);
586            openTurnoutPath.add(mi);
587        } else {
588            for (OBlock block : manager.getNamedBeanSet()) {
589                JMenu openTurnoutMenu = new JMenu(Bundle.getMessage("OpenTurnoutMenu", block.getDisplayName()));
590                openTurnoutPath.add(openTurnoutMenu);
591                openFrameAction = e -> {
592                    String pathTurnoutName = e.getActionCommand();
593                    openPathTurnoutEditPane(pathTurnoutName); // handles both interfaces
594                };
595                for (Path p : block.getPaths()) {
596                    if (p instanceof OPath) {
597                        OPath path = (OPath) p;
598                        JMenuItem mi = new JMenuItem(Bundle.getMessage("OpenPathTurnoutMenu", path.getName()));
599                        mi.setActionCommand(makePathTurnoutName(block.getSystemName(), path.getName()));
600                        mi.addActionListener(openFrameAction);
601                        openTurnoutMenu.add(mi);
602                    }
603                }
604            }
605        }
606        tablesMenu.add(openTurnoutPath);
607    }
608
609    public void openPathTurnoutEditPane(String pathTurnoutName) {
610        if (_tabbed) {
611            log.debug("openPathTurnoutEditPane for {}", pathTurnoutName);
612            openPathTurnoutEditor(pathTurnoutName);
613        } else { // stand alone frame only used for _desktop, created from/stored in Portal
614            openPathTurnoutFrame(pathTurnoutName);
615        }
616    }
617
618    /**
619     * Show or hide a table in the _desktop interface.
620     *
621     * @param frame JInternalFrame to show (or hide, name property value contains {} var handled by frame)
622     * @param menu menu item object
623     * @param menuName base i18n string containing table name
624     */
625    private void showHideFrame(JInternalFrame frame, JMenuItem menu, String menuName) {
626        if (!frame.isVisible()) {
627            frame.setVisible(true);
628            try {
629                frame.setIcon(false);
630            } catch (PropertyVetoException pve) {
631                log.warn("{} Frame vetoed setIcon {}", frame.getTitle(), pve.toString());
632            }
633            frame.moveToFront();
634        } else {
635            frame.setVisible(false);
636        }
637        menu.setText(Bundle.getMessage(menuName,
638                (frame.isVisible() ? Bundle.getMessage("HideTable") : Bundle.getMessage("ShowTable"))));
639    }
640
641    /**
642     * Wrapper for shared code around each Table in a JInternal window on _desktop interface.
643     *
644     * @param tableModel underlying model for the table
645     * @param title text displayed as title of frame
646     * @param prompt text below bottom line
647     * @return iframe to put on _desktop interface
648     */
649    protected JInternalFrame buildFrame(AbstractTableModel tableModel, String title, String prompt) {
650        JInternalFrame iframe = new JInternalFrame(title, true, false, false, true);
651
652        // specifics for table
653        JTable table = new JTable();
654        if (tableModel instanceof OBlockTableModel) {
655            table = makeOBlockTable((OBlockTableModel) tableModel);
656        } else if (tableModel instanceof PortalTableModel) {
657            table = makePortalTable((PortalTableModel) tableModel);
658        } else if (tableModel instanceof BlockPortalTableModel) {
659            table = makeBlockPortalTable((BlockPortalTableModel) tableModel);
660        } else if (tableModel instanceof SignalTableModel) {
661            table = makeSignalTable((SignalTableModel) tableModel);
662        } // no case here for BlockPathTableModel, it is handled directly from OBlockTable
663
664        JScrollPane scroll = new JScrollPane(table);
665        JPanel contentPane = new JPanel();
666        contentPane.setLayout(new BorderLayout(5, 5));
667        JLabel _prompt = new JLabel(prompt);
668        contentPane.add(_prompt, BorderLayout.NORTH);
669        contentPane.add(scroll, BorderLayout.CENTER);
670
671        iframe.setContentPane(contentPane);
672        iframe.pack();
673        return iframe;
674    }
675
676    /*
677     * ********************* OBlock Table for _desktop ****************
678     */
679    protected JTable makeOBlockTable(OBlockTableModel model) {
680        _oBlockTable = new JTable(model);
681        TableRowSorter<OBlockTableModel> sorter = new TableRowSorter<>(_oBlockModel);
682        // use NamedBean's built-in Comparator interface for sorting
683        _oBlockTable.setRowSorter(sorter);
684        _oBlockTable.setTransferHandler(new jmri.util.DnDTableImportExportHandler(new int[]{OBlockTableModel.EDIT_COL,
685            OBlockTableModel.DELETE_COL, OBlockTableModel.REPORT_CURRENTCOL, OBlockTableModel.SPEEDCOL,
686            OBlockTableModel.PERMISSIONCOL, OBlockTableModel.UNITSCOL}));
687        _oBlockTable.setDragEnabled(true);
688
689        // Use XTableColumnModel so we can control which columns are visible
690        XTableColumnModel tcm = new XTableColumnModel();
691        _oBlockTable.setColumnModel(tcm);
692        _oBlockTable.getTableHeader().setReorderingAllowed(true);
693        _oBlockTable.createDefaultColumnsFromModel();
694        _oBlockModel.addHeaderListener(_oBlockTable);
695
696        _oBlockTable.setDefaultEditor(JComboBox.class, new jmri.jmrit.symbolicprog.ValueEditor());
697        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.EDIT_COL).setCellEditor(new ButtonEditor(new JButton()));
698        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.EDIT_COL).setCellRenderer(new ButtonRenderer());
699        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.DELETE_COL).setCellEditor(new ButtonEditor(new JButton()));
700        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.DELETE_COL).setCellRenderer(new ButtonRenderer());
701        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.UNITSCOL).setCellRenderer(
702                new ToggleButtonRenderer(Bundle.getMessage("cm"), Bundle.getMessage("in")));
703        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.UNITSCOL).setCellEditor(
704                new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("cm"), Bundle.getMessage("in")));
705        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.REPORT_CURRENTCOL).setCellRenderer(
706                new ToggleButtonRenderer(Bundle.getMessage("Current"), Bundle.getMessage("Last")));
707        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.REPORT_CURRENTCOL).setCellEditor(
708                new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("Current"), Bundle.getMessage("Last")));
709        model.configSpeedColumn(_oBlockTable); // use real combo
710        //        JComboBox<String> box = new JComboBox<>(OBlockTableModel.curveOptions);
711        //        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.CURVECOL).setCellEditor(new DefaultCellEditor(box));
712        model.configCurveColumn(_oBlockTable); // use real combo
713        //        box = new JComboBox<>(jmri.InstanceManager.getDefault(SignalSpeedMap.class).getValidSpeedNames());
714//        box.addItem("");
715//        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.SPEEDCOL).setCellRenderer(new DefaultCellRenderer(new _oBlockModel.SpeedComboBoxPanel()));
716//        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.SPEEDCOL).setCellEditor(new DefaultCellEditor(box));
717        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.PERMISSIONCOL).setCellRenderer(
718                new ToggleButtonRenderer(Bundle.getMessage("Permissive"), Bundle.getMessage("Absolute")));
719        _oBlockTable.getColumnModel().getColumn(OBlockTableModel.PERMISSIONCOL).setCellEditor(
720                new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("Permissive"), Bundle.getMessage("Absolute")));
721        _oBlockTable.addMouseListener(new MouseAdapter() {
722            @Override
723            public void mousePressed(MouseEvent me) { // for macOS, Linux
724                showPopup(me);
725            }
726
727            @Override
728            public void mouseReleased(MouseEvent me) { // for Windows
729                showPopup(me);
730            }
731        });
732
733        for (int i = 0; i < _oBlockModel.getColumnCount(); i++) {
734            int width = _oBlockModel.getPreferredWidth(i);
735            _oBlockTable.getColumnModel().getColumn(i).setPreferredWidth(width);
736        }
737        _oBlockTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
738        _oBlockTable.setRowHeight(ROW_HEIGHT);
739
740        TableColumn column = tcm.getColumnByModelIndex(OBlockTableModel.REPORTERCOL);
741        tcm.setColumnVisible(column, false);
742        column = tcm.getColumnByModelIndex(OBlockTableModel.REPORT_CURRENTCOL);
743        tcm.setColumnVisible(column, false);
744        column = tcm.getColumnByModelIndex(OBlockTableModel.PERMISSIONCOL);
745        tcm.setColumnVisible(column, false);
746        column = tcm.getColumnByModelIndex(OBlockTableModel.ERR_SENSORCOL);
747        tcm.setColumnVisible(column, false);
748        column = tcm.getColumnByModelIndex(OBlockTableModel.CURVECOL);
749        tcm.setColumnVisible(column, false);
750
751        _oBlockTable.setPreferredScrollableViewportSize(new java.awt.Dimension(_oBlockTable.getPreferredSize().width,
752                ROW_HEIGHT * Math.min(20, InstanceManager.getDefault(OBlockManager.class).getObjectCount())));
753        return _oBlockTable;
754    }
755
756    private void showPopup(MouseEvent me) {
757        Point p = me.getPoint();
758        int col = _oBlockTable.columnAtPoint(p);
759        if (!me.isPopupTrigger() && !me.isMetaDown() && !me.isAltDown() && col == OBlockTableModel.STATECOL) {
760            int row = _oBlockTable.rowAtPoint(p);
761            String stateStr = (String) _oBlockModel.getValueAt(row, col);
762            int state = Integer.parseInt(stateStr, 2);
763            stateStr = OBlockTableModel.getValue(state);
764            JPopupMenu popupMenu = new JPopupMenu();
765            popupMenu.add(new JMenuItem(stateStr));
766            popupMenu.show(_oBlockTable, me.getX(), me.getY());
767        }
768    }
769
770    // Opens the Edit OBlock panel for _tabbed
771    protected boolean openOBlockEditor(String blockSystemName, String tabname) {
772        boolean result = false;
773        if (blockSystemName != null) {
774            // this is for Edit (new OBlocks are created from [Add OBlock...] button in table)
775            OBlock oblock = InstanceManager.getDefault(OBlockManager.class).getBySystemName(blockSystemName);
776            if (oblock != null) {
777                BlockPathJPanel panel = makeBlockPathEditPanel(oblock);
778                // BeanEdit UI, adapted from jmri.jmrit.beantable.BlockTableAction
779                jmri.jmrit.beantable.beanedit.OBlockEditAction beanEdit = new jmri.jmrit.beantable.beanedit.OBlockEditAction(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, tabname));
780                beanEdit.setBean(oblock);
781                beanEdit.setTablePanel(panel);
782                beanEdit.actionPerformed(null);
783                // run on separate thread? does not update new Paths in table!
784                //                class WindowMaker implements Runnable {
785                //                    final OBlock ob;
786                //                    final BlockPathJPanel panel;
787                //                    WindowMaker(OBlock oblock, BlockPathJPanel panel) {
788                //                        ob = oblock;
789                //                        this.panel = panel;
790                //                    }
791                //                    @Override
792                //                    public void run() {
793                //                        jmri.jmrit.beantable.beanedit.OBlockEditAction beanEdit = new jmri.jmrit.beantable.beanedit.OBlockEditAction();
794                //                        beanEdit.setBean(oblock);
795                //                        beanEdit.setTablePanel(panel);
796                //                        beanEdit.actionPerformed(null);
797                //                    }
798                //                }
799                //                WindowMaker t = new WindowMaker(oblock, panel);
800                //                javax.swing.SwingUtilities.invokeLater(t);
801                log.debug("path table created for oblock {}", blockSystemName);
802                result = true;
803            }
804        }
805        return result;
806    }
807
808    /**
809     * Open the Edit Path panel for _tabbed.
810     * Compare with openOBlockEditor(block, selectedtabname) and OBlockTableAction.
811     *
812     * @param blockName system or user name of the owning oblock
813     * @param pathName name of the path under edit, or null to create a new path
814     * @param bpmodel blockpathtablemodel that should be informed about changes
815     * @return true if successful
816     */
817    protected boolean openPathEditor(@Nonnull String blockName, @CheckForNull String pathName, BlockPathTableModel bpmodel) {
818        OBlock block = InstanceManager.getDefault(OBlockManager.class).getOBlock(blockName);
819        if (block == null) {
820            log.error("OBlock {} not found", blockName);
821            return false;
822        }
823        OPath path;
824        String title;
825        PathTurnoutJPanel turnouttable = makePathTurnoutPanel(block, pathName); // shows the turnouts on path, includes Add Turnout button, checks for null path
826        if (pathName == null) { // new Path, empty TurnoutTable
827            // a new Path is created from [Add Path...] button in Path table on OBlock Editor pane.
828            path = null;
829            title = Bundle.getMessage("AddPathTitle", blockName);
830        } else {
831            path = block.getPathByName(pathName);
832            title = Bundle.getMessage("EditPathTitle", pathName, blockName);
833        }
834        BlockPathEditFrame bpef = new BlockPathEditFrame(title, block, path, turnouttable, bpmodel, this);
835        bpef.setVisible(true);
836        // run on separate thread? combos are final, difficult to store Partals in Path/see them show up in the table
837        //        class WindowMaker implements Runnable {
838        //            final String title;
839        //            final OBlock ob;
840        //            final OPath path;
841        //            final PathTurnoutTableModel tomodel;
842        //            final BlockPathTableModel bpmodel;
843        //            final TableFrames parent;
844        //            WindowMaker(String title, OBlock ob, OPath path, PathTurnoutTableModel turnoutmodel, BlockPathTableModel blockpathmodel, TableFrames tf) {
845        //                this.title = title;
846        //                this.ob = ob;
847        //                this.path = path;
848        //                this.tomodel = turnoutmodel;
849        //                this.bpmodel = blockpathmodel;
850        //                parent = tf;
851        //            }
852        //            @Override
853        //            public void run() {
854        //                BlockPathEditFrame bpef = new BlockPathEditFrame(title, block, path, turnouttable, bpmodel, parent);
855        //                bpef.setVisible(true);
856        //            }
857        //        }
858        //        WindowMaker t = new WindowMaker(title, block, path, turnouttable.getModel(), bpmodel, this);
859        //        javax.swing.SwingUtilities.invokeLater(t);
860
861        log.debug("Path editor created for path {} on block {}", pathName, blockName);
862        return true;
863    }
864
865    /*
866     * ********************* PortalTable for _desktop *****************************
867     */
868    protected JTable makePortalTable(PortalTableModel model) {
869        _portalTable = new JTable(model);
870        TableRowSorter<PortalTableModel> sorter = new TableRowSorter<>(model);
871        _portalTable.setRowSorter(sorter);
872        _portalTable.setTransferHandler(new jmri.util.DnDTableImportExportHandler(new int[]{PortalTableModel.DELETE_COL}));
873        _portalTable.setDragEnabled(true);
874
875        _portalTable.getColumnModel().getColumn(PortalTableModel.DELETE_COL).setCellEditor(new ButtonEditor(new JButton()));
876        _portalTable.getColumnModel().getColumn(PortalTableModel.DELETE_COL).setCellRenderer(new ButtonRenderer());
877        for (int i = 0; i < model.getColumnCount(); i++) {
878            int width = model.getPreferredWidth(i);
879            _portalTable.getColumnModel().getColumn(i).setPreferredWidth(width);
880        }
881        _portalTable.doLayout();
882        int tableWidth = _portalTable.getPreferredSize().width;
883        _portalTable.setRowHeight(ROW_HEIGHT);
884        _portalTable.setPreferredScrollableViewportSize(new java.awt.Dimension(tableWidth,
885                ROW_HEIGHT * Math.min(20, InstanceManager.getDefault(PortalManager.class).getPortalCount())));
886        return _portalTable;
887    }
888
889    /*
890     * ********************* Block-Portal (XRef) Table for _desktop *****************************
891     */
892    protected JTable makeBlockPortalTable(BlockPortalTableModel model) {
893        _blockPortalTable = new JTable(model);
894        _blockPortalTable.setTransferHandler(new jmri.util.DnDTableExportHandler());
895        _blockPortalTable.setDragEnabled(true);
896
897        _blockPortalTable.setDefaultRenderer(String.class, new jmri.jmrit.symbolicprog.ValueRenderer());
898        _blockPortalTable.setDefaultEditor(String.class, new jmri.jmrit.symbolicprog.ValueEditor()); // useful on non-editable cell?
899        for (int i = 0; i < model.getColumnCount(); i++) {
900            int width = model.getPreferredWidth(i);
901            _blockPortalTable.getColumnModel().getColumn(i).setPreferredWidth(width);
902        }
903        _blockPortalTable.doLayout();
904        _blockPortalTable.setRowHeight(ROW_HEIGHT);
905        int tableWidth = _blockPortalTable.getPreferredSize().width;
906        _blockPortalTable.setPreferredScrollableViewportSize(new java.awt.Dimension(tableWidth,
907                ROW_HEIGHT * Math.min(20, InstanceManager.getDefault(PortalManager.class).getPortalCount())));
908
909        return _blockPortalTable;
910    }
911
912    /*
913     * ********************* Signal Table for _desktop *****************************
914     */
915    protected JTable makeSignalTable(SignalTableModel model) {
916        _signalTable = new JTable(model);
917        TableRowSorter<SignalTableModel> sorter = new TableRowSorter<>(model);
918        _signalTable.setRowSorter(sorter);
919        _signalTable.setTransferHandler(new jmri.util.DnDTableImportExportHandler(
920                new int[]{SignalTableModel.UNITSCOL, SignalTableModel.DELETE_COL}));
921        _signalTable.setDragEnabled(true);
922
923        _signalTable.getColumnModel().getColumn(SignalTableModel.UNITSCOL).setCellRenderer(
924                new ToggleButtonRenderer(Bundle.getMessage("cm"), Bundle.getMessage("in")));
925        _signalTable.getColumnModel().getColumn(SignalTableModel.UNITSCOL).setCellEditor(
926                new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("cm"), Bundle.getMessage("in")));
927        _signalTable.getColumnModel().getColumn(SignalTableModel.DELETE_COL).setCellEditor(new ButtonEditor(new JButton()));
928        _signalTable.getColumnModel().getColumn(SignalTableModel.DELETE_COL).setCellRenderer(new ButtonRenderer());
929        for (int i = 0; i < model.getColumnCount(); i++) {
930            int width = SignalTableModel.getPreferredWidth(i);
931            _signalTable.getColumnModel().getColumn(i).setPreferredWidth(width);
932        }
933        _signalTable.doLayout();
934        int tableWidth = _signalTable.getPreferredSize().width;
935        _signalTable.setRowHeight(ROW_HEIGHT);
936        _signalTable.setPreferredScrollableViewportSize(new java.awt.Dimension(tableWidth,
937                ROW_HEIGHT * Math.min(10, _signalTable.getRowCount())));
938        return _signalTable;
939    }
940
941    /*
942     * ***************** end of permanent Tables + InternalFrame definitions *****************
943     */
944
945
946    /*
947     * ***************** On Demand Tables + InternalFrame definitions *****************
948     */
949
950    /*
951     * ********************* Block-Path Frame *****************************
952     */
953
954    // called from Tables menu and the OBlockTable EDIT buttons
955    public void openBlockPathPane(String blockSystemName, String editorTabName) {
956        if (_tabbed) {
957            if (!openOBlockEditor(blockSystemName, editorTabName)) {
958                // pass on to Per OBlock Edit panel, includes a BlockPath table
959                log.error("Failed to open OBlock Path table for {}", blockSystemName);
960            }
961        } else {
962            openBlockPathFrame(blockSystemName); // an editable table of all paths on this block
963        }
964    }
965
966    // ***************** Block-Path Frame for _desktop **************************
967    /**
968     * Open a block-specific Block-Path table in _desktop interface.
969     *
970     * @param blockSystemName of the OBlock
971     */
972    protected void openBlockPathFrame(String blockSystemName) {
973        BlockPathFrame frame = _blockPathMap.get(blockSystemName);
974        if (frame == null) {
975            OBlock block = InstanceManager.getDefault(OBlockManager.class).getBySystemName(blockSystemName);
976            if (block == null) {
977                return;
978            }
979            frame = makeBlockPathFrame(block);
980            // store frame in Map
981            _blockPathMap.put(blockSystemName, frame);
982            frame.setVisible(true);
983            desktopframe.getContentPane().add(frame);
984        } else {
985            frame.setVisible(true);
986            try {
987                frame.setIcon(false);
988            } catch (PropertyVetoException pve) {
989                log.warn("BlockPath Table Frame for \"{}\" vetoed setIcon", blockSystemName, pve);
990            }
991        }
992        frame.moveToFront();
993    }
994
995    // common dispose
996    protected void disposeBlockPathFrame(OBlock block) {
997        if (!_tabbed) {
998            //BlockPathFrame frame = _blockPathMap.get(block.getSystemName());
999            // TODO frame.getModel().removeListener();
1000            //_blockPathMap.remove(block.getSystemName()); // block not stored in map, required to remove listener?
1001            // frame.dispose(); not required (closeable window)
1002            //} else {
1003            BlockPathFrame frame = _blockPathMap.get(block.getSystemName());
1004            frame.getModel().removeListener();
1005            _blockPathMap.remove(block.getSystemName());
1006            frame.dispose();
1007        }
1008    }
1009
1010    // *************** Block-Path InternalFrame for _desktop ***********************
1011
1012    protected BlockPathFrame makeBlockPathFrame(OBlock block) {
1013        String title = Bundle.getMessage("TitleBlockPathTable", block.getDisplayName());
1014        // create table
1015        BlockPathTableModel model = new BlockPathTableModel(block, this);
1016        JPanel contentPane = makeBlockPathTablePanel(model);
1017
1018        BlockPathFrame frame = new BlockPathFrame(title, true, true, false, true);
1019        frame.setModel(model, block.getSystemName());
1020        frame.addInternalFrameListener(this);
1021        frame.setContentPane(contentPane);
1022        //frame.setClosable(true); // set in ctor
1023        frame.setLocation(50, 30);
1024        frame.pack();
1025        return frame;
1026    }
1027
1028    // *************** Block-Path Edit Panel for _tabbed ***********************
1029
1030    protected BlockPathJPanel makeBlockPathEditPanel(OBlock block) {
1031        // Path Table placed on jmri.jmrit.beanedit OBlockEditAction - Paths tab
1032        String title = Bundle.getMessage("TitleBlockPathEditor", block.getDisplayName());
1033        // create table
1034        BlockPathTableModel model = new BlockPathTableModel(block, this);
1035        JPanel bpTablePane = makeBlockPathTablePanel(model);
1036        BlockPathJPanel panel = new BlockPathJPanel(title);
1037        panel.setModel(model, block.getSystemName());
1038        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
1039        panel.add(bpTablePane);
1040
1041        // Add Path Button
1042        JPanel tblButtons = new JPanel();
1043        tblButtons.setLayout(new BorderLayout(10, 10));
1044        tblButtons.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));
1045        tblButtons.setLayout(new BoxLayout(tblButtons, BoxLayout.Y_AXIS));
1046
1047        JButton addPathButton = new JButton(Bundle.getMessage("ButtonAddPath"));
1048        ActionListener addPathAction = e -> {
1049            // New Path uses the same editor pane as Edit Path
1050            if (!isPathEdit()) {
1051                setPathEdit(true);
1052                log.debug("makeBlockPathEditPanel pathEdit=True");
1053                openPathEditor(block.getDisplayName(), null, model);
1054            } else {
1055                log.warn("Close BlockPath Editor to reopen");
1056            }
1057        };
1058        addPathButton.addActionListener(addPathAction);
1059        addPathButton.setToolTipText(Bundle.getMessage("AddPathTabbedPrompt"));
1060        tblButtons.add(addPathButton);
1061        panel.add(tblButtons);
1062
1063        //panel.pack();
1064        return panel;
1065    }
1066
1067    // prevent more than 1 edit pane being opened at the same time
1068    protected void setPathEdit(boolean edit) {
1069        pathEdit = edit;
1070    }
1071
1072    protected boolean isPathEdit() {
1073        return pathEdit;
1074    }
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, maxHeight)));
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, maxHeight)));
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;
1402        p.add(ok = new JButton(Bundle.getMessage("ButtonOK"))); // no need to save things, handled by TurnoutTable
1403        ok.addActionListener((ActionEvent 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;
1456        btns.add(cancel = new JButton(Bundle.getMessage("ButtonCancel")));
1457        cancel.addActionListener((ActionEvent e) -> frame.dispose());
1458        JButton ok;
1459        btns.add(ok = new JButton(Bundle.getMessage("ButtonOK")));
1460        ok.addActionListener((ActionEvent 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 = JOptionPane.showOptionDialog(null,
1502                    message, Bundle.getMessage("WarningTitle"),
1503                    JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.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                    JOptionPane.showMessageDialog(desktopframe, msg,
1544                            Bundle.getMessage("InfoTitle"), JOptionPane.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                JOptionPane.showMessageDialog(desktopframe, msg,
1579                    Bundle.getMessage("InfoTitle"), JOptionPane.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 final static Logger log = LoggerFactory.getLogger(TableFrames.class);
1603
1604}