001package jmri.jmrit.beantable;
002
003import java.awt.*;
004import java.awt.event.ActionEvent;
005import java.beans.PropertyChangeEvent;
006import java.lang.reflect.InvocationTargetException;
007import java.text.MessageFormat;
008import java.util.ArrayList;
009import java.util.Vector;
010
011import javax.annotation.Nonnull;
012
013import javax.swing.*;
014import javax.swing.table.TableRowSorter;
015
016import jmri.*;
017import jmri.swing.RowSorterUtil;
018import jmri.util.AlphanumComparator;
019import jmri.util.gui.GuiLafPreferencesManager;
020import jmri.util.swing.*;
021
022/**
023 * Provide access to the various tables in the tabbed Tables interface via a listed pane (normally to the left).
024 * <p>
025 * Based upon the {@link apps.gui3.tabbedpreferences.TabbedPreferences} by Bob Jacobsen
026 *
027 * @author Kevin Dickerson Copyright 2010
028 * @author Bob Jacobsen Copyright 2010
029 */
030public class ListedTableFrame<E extends NamedBean> extends BeanTableFrame<E> {
031
032    ActionJList actionList;
033
034    public boolean isMultipleInstances() {
035        return true;
036    }
037
038    static ArrayList<TabbedTableItemListArray> tabbedTableItemListArrayArray = new ArrayList<>();
039    ArrayList<TabbedTableItem<E>> tabbedTableArray = new ArrayList<>();
040
041    final UserPreferencesManager pref = InstanceManager.getDefault(UserPreferencesManager.class);
042    JSplitPane cardHolder;
043    JList<String> list;
044    JScrollPane listScroller;
045    JPanel listPanel;
046    JPanel detailPanel;
047    static boolean init = false;
048
049    /**
050     * Create a new Listed Table Frame.
051     * Call initTables() before initComponents()
052     */
053    public ListedTableFrame() {
054        this(Bundle.getMessage("TitleListedTable"));
055    }
056
057    /**
058     * Create a new Listed Table Frame.
059     * Call initTables() before initComponents()
060     * @param s Initial Frame Title
061     */
062    public ListedTableFrame(String s) {
063        super(s);
064        if (InstanceManager.getNullableDefault(jmri.jmrit.beantable.ListedTableFrame.class) == null) {
065            // We add this to the InstanceManager so that other components can add to the table
066            InstanceManager.store(ListedTableFrame.this, jmri.jmrit.beantable.ListedTableFrame.class);
067        }
068    }
069
070    /**
071     * Initialise all tables to be added to Frame.
072     * Should be called after ListedTableFrame construction and before initComponents()
073     */
074    public void initTables() {
075        if (!init) {
076            // Add the default tables to the static list array,
077            // this should only be done once on first loading
078            addTable("jmri.jmrit.beantable.TurnoutTableTabAction", Bundle.getMessage("MenuItemTurnoutTable"), false);
079            addTable("jmri.jmrit.beantable.SensorTableTabAction", Bundle.getMessage("MenuItemSensorTable"), false);
080            addTable("jmri.jmrit.beantable.LightTableTabAction", Bundle.getMessage("MenuItemLightTable"), false);
081            addTable("jmri.jmrit.beantable.SignalHeadTableAction", Bundle.getMessage("MenuItemSignalTable"), true);
082            addTable("jmri.jmrit.beantable.SignalMastTableAction", Bundle.getMessage("MenuItemSignalMastTable"), true);
083            addTable("jmri.jmrit.beantable.SignalGroupTableAction", Bundle.getMessage("MenuItemSignalGroupTable"), true);
084            addTable("jmri.jmrit.beantable.SignalMastLogicTableAction", Bundle.getMessage("MenuItemSignalMastLogicTable"), true);
085            addTable("jmri.jmrit.beantable.ReporterTableTabAction", Bundle.getMessage("MenuItemReporterTable"), false);
086            addTable("jmri.jmrit.beantable.MemoryTableAction", Bundle.getMessage("MenuItemMemoryTable"), true);
087            addTable("jmri.jmrit.beantable.RouteTableAction", Bundle.getMessage("MenuItemRouteTable"), true);
088            addTable("jmri.jmrit.beantable.LRouteTableAction", Bundle.getMessage("MenuItemLRouteTable"), true);
089            addTable("jmri.jmrit.beantable.LogixTableAction", Bundle.getMessage("MenuItemLogixTable"), true);
090            addTable("jmri.jmrit.beantable.LogixNGTableAction", Bundle.getMessage("MenuItemLogixNGTable"), true);
091            addTable("jmri.jmrit.beantable.LogixNGModuleTableAction", Bundle.getMessage("MenuItemLogixNGModuleTable"), true);
092            addTable("jmri.jmrit.beantable.LogixNGTableTableAction", Bundle.getMessage("MenuItemLogixNGTableTable"), true);
093            addTable("jmri.jmrit.beantable.LogixNGGlobalVariableTableAction", Bundle.getMessage("MenuItemLogixNGGlobalVariableTableAction"), true);
094            addTable("jmri.jmrit.beantable.BlockTableAction", Bundle.getMessage("MenuItemBlockTable"), true);
095            if (InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed()) { // select _tabbed in prefs
096                addTable("jmri.jmrit.beantable.OBlockTableAction", Bundle.getMessage("MenuItemOBlockTable"), false);
097            } // requires restart after changing the interface setting (on Display tab)
098            addTable("jmri.jmrit.beantable.SectionTableAction", Bundle.getMessage("MenuItemSectionTable"), true);
099            addTable("jmri.jmrit.beantable.TransitTableAction", Bundle.getMessage("MenuItemTransitTable"), true);
100            addTable("jmri.jmrit.beantable.AudioTableAction", Bundle.getMessage("MenuItemAudioTable"), false);
101            addTable("jmri.jmrit.beantable.IdTagTableTabAction", Bundle.getMessage("MenuItemIdTagTable"), false);
102            addTable("jmri.jmrit.beantable.RailComTableAction", Bundle.getMessage("MenuItemRailComTable"), true);
103            ListedTableFrame.setInit(true);
104        }
105    }
106
107    /**
108     * Initialise Frame Components.
109     * Should be called after initTables()
110     * {@inheritDoc}
111     */
112    @Override
113    public void initComponents() {
114        if (tabbedTableItemListArrayArray.isEmpty()) {
115            log.error("No tables loaded: {}",this);
116            return;
117        }
118        actionList = new ActionJList(this);
119
120        detailPanel = new JPanel();
121        detailPanel.setLayout(new CardLayout());
122        tabbedTableArray = new ArrayList<>(tabbedTableItemListArrayArray.size());
123        ArrayList<TabbedTableItemListArray> removeItem = new ArrayList<>(5);
124        for (TabbedTableItemListArray item : tabbedTableItemListArrayArray) {
125            // Here we add all the tables into the panel
126            try {
127                TabbedTableItem<E> itemModel = new TabbedTableItem<>(
128                    item.getClassAsString(), item.getItemString(), item.getStandardTableModel());
129                detailPanel.add(itemModel.getPanel(), itemModel.getClassAsString());
130                tabbedTableArray.add(itemModel);
131                itemModel.getAAClass().addToFrame(this);
132            } catch (Exception ex) {
133                detailPanel.add(errorPanel(item.getItemString()), item.getClassAsString());
134                log.error("Error when adding {} to display", item.getClassAsString(), ex);
135                removeItem.add(item);
136            }
137        }
138
139        for (TabbedTableItemListArray dead : removeItem) {
140            tabbedTableItemListArrayArray.remove(dead);
141        }
142
143        list = new JList<>(new Vector<>(getChoices()));
144        listScroller = new JScrollPane(list);
145
146        list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
147        list.setLayoutOrientation(JList.VERTICAL);
148        list.addMouseListener(JmriMouseListener.adapt(actionList));
149
150        listPanel = new JPanel();
151        listPanel.setLayout(new BorderLayout(5, 0));
152        listPanel.setLayout(new BoxLayout(listPanel, BoxLayout.Y_AXIS));
153        listPanel.add(listScroller);
154        listPanel.setMinimumSize(new Dimension(140, 400)); // guarantees minimum width of left divider list
155
156        buildMenus(tabbedTableArray.get(0));
157        setTitle(tabbedTableArray.get(0).getItemString());
158
159        cardHolder = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
160                listPanel, detailPanel);
161
162        cardHolder.setDividerSize(8);
163        if (this.getDividerLocation() != 0) {
164            cardHolder.setDividerLocation(this.getDividerLocation());
165        } else { // if no specific size has been given we set it to the lists preferred width
166            cardHolder.setDividerLocation(listScroller.getPreferredSize().width);
167        }
168        cardHolder.addPropertyChangeListener((PropertyChangeEvent e) -> {
169            if (e.getPropertyName().equals("dividerLocation")) {
170                InstanceManager.getDefault(UserPreferencesManager.class)
171                        .setProperty(ListedTableFrame.class.getName(), "dividerLocation", e.getNewValue());
172            }
173        });
174
175        cardHolder.setOneTouchExpandable(true);
176        getContentPane().add(cardHolder);
177        pack();
178        actionList.selectListItem(0);
179    }
180
181    JPanel errorPanel(String text) {
182        JPanel error = new JPanel();
183        error.add(new JLabel(Bundle.getMessage("ErrorAddingTable", text)));
184        return error;
185    }
186
187    /* Method allows for the table to go to a specific list item */
188    public void gotoListItem(String selection) {
189        for (int x = 0; x < tabbedTableArray.size(); x++) {
190            try {
191                if (tabbedTableArray.get(x).getClassAsString().equals(selection)) {
192                    actionList.selectListItem(x);
193                    return;
194                }
195            } catch (Exception ex) {
196                log.error("An error occurred in the goto list for {}, {}", selection,ex.getMessage());
197            }
198        }
199    }
200
201    public void addTable(String aaClass, String choice, boolean stdModel) {
202        TabbedTableItemListArray itemToAdd = null;
203        for (TabbedTableItemListArray ttila : tabbedTableItemListArrayArray) {
204            if (ttila.getClassAsString().equals(aaClass)) {
205                log.info("Class {} is already added", aaClass);
206                itemToAdd = ttila;
207                break;
208            }
209        }
210        if (itemToAdd == null) {
211            itemToAdd = new TabbedTableItemListArray(aaClass, choice, stdModel);
212            tabbedTableItemListArrayArray.add(itemToAdd);
213        }
214    }
215
216    @Override
217    public void dispose() {
218        pref.setSaveAllowed(false);
219        for (TabbedTableItem<E> tti : tabbedTableArray) {
220            tti.dispose();
221        }
222        if (list != null && list.getListSelectionListeners().length > 0) {
223            list.removeListSelectionListener(list.getListSelectionListeners()[0]);
224        }
225        super.dispose();
226        pref.setSaveAllowed(true);
227    }
228
229    void buildMenus( @Nonnull final TabbedTableItem<E> item) {
230        JMenuBar menuBar = new JMenuBar();
231        JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
232        menuBar.add(fileMenu);
233
234        JMenuItem newItem = new JMenuItem(Bundle.getMessage("MenuNewWindow"));
235        fileMenu.add(newItem);
236        newItem.addActionListener((ActionEvent e) -> actionList.openNewTableWindow(list.getSelectedIndex()));
237
238        // do not display Store All Table Content in IdTag Table 
239        if (!( item.getAAClass() instanceof IdTagTableAction || 
240            item.getAAClass() instanceof IdTagTableTabAction ) ) {
241            fileMenu.add(new jmri.configurexml.StoreMenu());
242        }
243
244        JMenuItem printItem = new JMenuItem(Bundle.getMessage("PrintTable"));
245        fileMenu.add(printItem);
246        printItem.addActionListener((ActionEvent e) -> {
247            try {
248                // MessageFormat headerFormat = new MessageFormat(getTitle());  // not used below
249                MessageFormat footerFormat = new MessageFormat(getTitle() + " page {0,number}");
250                if (item.getStandardTableModel()) {
251                    item.getDataTable().print(JTable.PrintMode.FIT_WIDTH, null, footerFormat);
252                } else {
253                    item.getAAClass().print(JTable.PrintMode.FIT_WIDTH, null, footerFormat);
254                }
255            } catch (java.awt.print.PrinterException e1) {
256                log.warn("Printing error", e1);
257            } catch (NullPointerException ex) {
258                log.error("Trying to print returned a NPE error");
259            }
260        });
261
262        JMenu viewMenu = new JMenu(Bundle.getMessage("MenuView"));
263        menuBar.add(viewMenu);
264        for (final TabbedTableItemListArray itemList : tabbedTableItemListArrayArray) {
265            JMenuItem viewItem = new JMenuItem(itemList.getItemString());
266            viewMenu.add(viewItem);
267            viewItem.addActionListener((ActionEvent e) -> gotoListItem(itemList.getClassAsString()));
268        }
269
270        this.setJMenuBar(menuBar);
271        try {
272            item.getAAClass().setMenuBar(this);
273            this.addHelpMenu(item.getAAClass().helpTarget(), true);
274        } catch (Exception ex) {
275            log.error("Error when trying to set menu bar for {}", item.getClassAsString(), ex);
276        }
277        this.revalidate();
278    }
279
280    /* This is a bit of a bodge to add the contents to the bottom box and keep
281     * it backwardly compatible with the original views. When the original views
282     * are deprecated then this can be re-written
283     */
284    //@TODO Sort out the procedure to add to bottom box
285    @Override
286    protected void addToBottomBox(Component comp, String c) {
287        for (TabbedTableItem<E> tti : tabbedTableArray) {
288            if (tti.getClassAsString().equals(c)) {
289                tti.addToBottomBox(comp);
290                return;
291            }
292        }
293    }
294
295    protected static ArrayList<String> getChoices() {
296        ArrayList<String> choices = new ArrayList<>();
297        for (TabbedTableItemListArray ttila : tabbedTableItemListArrayArray) {
298            choices.add(ttila.getItemString());
299        }
300        return choices;
301    }
302
303    public void setDividerLocation(int loc) {
304        if (loc == 0) {
305            return;
306        }
307        cardHolder.setDividerLocation(loc);
308        InstanceManager.getDefault(UserPreferencesManager.class)
309                .setProperty(ListedTableFrame.class.getName(), "dividerLocation", loc);
310    }
311
312    public int getDividerLocation() {
313        try {
314            return Integer.parseInt(InstanceManager.getDefault(UserPreferencesManager.class)
315                    .getProperty(ListedTableFrame.class.getName(), "dividerLocation").toString());
316        } catch (NullPointerException | NumberFormatException ex) {
317            // ignore, this means the divider location has never been saved
318            return 0;
319        }
320    }
321
322    /**
323     * Flag Table initialisation started
324     * @param newVal true when started
325     */
326    private static synchronized void setInit(boolean newVal) {
327        init = newVal;
328    }
329
330    /**
331     * One tabbed item on the ListedTable containing the table(s) for a NamedBean class.
332     *
333     * @param <E> main class of the table(s)
334     */
335    public static class TabbedTableItem<E extends NamedBean> {
336
337        private AbstractTableAction<E> tableAction;
338        private final String className;
339        private String itemText;
340        private BeanTableDataModel<E> dataModel;
341        private JTable dataTable;
342        private JScrollPane dataScroll;
343        private final JPanel bottomBox;
344
345        private final boolean standardModel;
346
347        final JPanel dataPanel = new JPanel();
348
349        @SuppressWarnings("unchecked") // type ensured by reflection
350        TabbedTableItem(String aaClass, String choice, boolean stdModel) {
351            className = aaClass;
352            itemText = choice;
353            standardModel = stdModel;
354
355            bottomBox = new JPanel();
356            bottomBox.setLayout(new WrapLayout(WrapLayout.LEFT, 20, 5));
357
358            try {
359                Class<?> cl = Class.forName(aaClass);
360                java.lang.reflect.Constructor<?> co = cl.getConstructor(String.class);
361                tableAction = (AbstractTableAction<E>) co.newInstance(choice);  // this cast is handled by reflection
362            } catch (ClassNotFoundException | InstantiationException e1) {
363                log.error("Not a valid class : {}", aaClass);
364                return;
365            } catch (NoSuchMethodException e2) {
366                log.error("Not such method : {}", aaClass);
367                return;
368            } catch (ClassCastException e4) {
369                log.error("Not part of the abstractTableActions : {}", aaClass);
370                return;
371            } catch (IllegalAccessException | IllegalArgumentException | SecurityException | InvocationTargetException e) {
372                log.error("Exception accessing {}: {}", aaClass, e.getMessage());
373                return;
374            }
375
376            // If a panel model is used, it should really add to the bottom box
377            // but it can be done this way if required.
378            // In this case we "hijack" the TabbedTable for different (non-bean) tables to manage OBlocks.
379            dataPanel.setLayout(new BorderLayout());
380
381            if (stdModel) {
382                createDataModel(); // first table of a grouped set with the primary manager, see OBlockTable
383            } else {
384                addPanelModel(); // for any additional table using a different manager, see Audio, OBlock
385            }
386        }
387
388        void createDataModel() {
389            dataModel = tableAction.getTableDataModel();
390            TableRowSorter<BeanTableDataModel<E>> sorter = new TableRowSorter<>(dataModel);
391            dataTable = dataModel.makeJTable(dataModel.getMasterClassName() + ":" + getItemString(), dataModel, sorter);
392            dataScroll = new JScrollPane(dataTable);
393
394            // use NamedBean's built-in Comparator interface for sorting the system name column
395            RowSorterUtil.setSortOrder(sorter, BeanTableDataModel.SYSNAMECOL, SortOrder.ASCENDING);
396
397            sorter.setComparator(BeanTableDataModel.USERNAMECOL, new AlphanumComparator());
398            RowSorterUtil.setSortOrder(sorter, BeanTableDataModel.USERNAMECOL, SortOrder.ASCENDING);
399
400            dataModel.configureTable(dataTable);
401
402            java.awt.Dimension dataTableSize = dataTable.getPreferredSize();
403            // width is fine, but if table is empty, it's not high
404            // enough to reserve much space.
405            dataTableSize.height = Math.max(dataTableSize.height, 400);
406            dataScroll.getViewport().setPreferredSize(dataTableSize);
407
408            // set preferred scrolling options
409            dataScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
410            dataScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
411
412            dataPanel.add(dataScroll, BorderLayout.CENTER);
413
414            dataPanel.add(bottomBox, BorderLayout.SOUTH);
415            if (tableAction.includeAddButton()) {
416                JButton addButton = new JButton(Bundle.getMessage("ButtonAdd"));
417                addToBottomBox(addButton);
418                addButton.addActionListener((ActionEvent e) -> tableAction.addPressed(e));
419            }
420            if (dataModel.getPropertyColumnCount() > 0) {
421                final JCheckBox propertyVisible = new JCheckBox(Bundle.getMessage
422                        ("ShowSystemSpecificProperties"));
423                propertyVisible.setToolTipText(Bundle.getMessage
424                        ("ShowSystemSpecificPropertiesToolTip"));
425                addToBottomBox(propertyVisible);
426                propertyVisible.addActionListener((ActionEvent e) ->
427                    dataModel.setPropertyColumnsVisible(dataTable, propertyVisible.isSelected()));
428                dataModel.setPropertyColumnsVisible(dataTable, false);
429            }
430            tableAction.addToFrame(this);
431            dataModel.persistTable(dataTable);
432        }
433
434        void addPanelModel() {
435            try {
436                dataPanel.add(tableAction.getPanel(), BorderLayout.CENTER);
437                if ( bottomBox.getComponentCount()>0 ) {
438                    dataPanel.add(bottomBox, BorderLayout.SOUTH);
439                }
440            } catch (NullPointerException e) {
441                log.error("An error occurred while trying to create the table for {}", itemText, e);
442            }
443        }
444
445        boolean getStandardTableModel() {
446            return standardModel;
447        }
448
449        String getClassAsString() {
450            return className;
451        }
452
453        String getItemString() {
454            return itemText;
455        }
456
457        AbstractTableAction<E> getAAClass() {
458            return tableAction;
459        }
460
461        JPanel getPanel() {
462            return dataPanel;
463        }
464
465        JTable getDataTable() {
466            return dataTable;
467        }
468
469        public void addToBottomBox(Component comp) {
470            bottomBox.add(comp);
471        }
472
473        void dispose() {
474            if (dataModel != null) {
475                dataModel.stopPersistingTable(dataTable);
476                dataModel.dispose();
477            }
478            if (tableAction != null) {
479                tableAction.dispose();
480            }
481            dataModel = null;
482            dataTable = null;
483            dataScroll = null;
484        }
485    }
486
487    private static class TabbedTableItemListArray {
488
489        String className;
490        String itemText;
491        boolean standardModel;
492
493        TabbedTableItemListArray(String aaClass, String choice, boolean stdModel) {
494            className = aaClass;
495            itemText = choice;
496            standardModel = stdModel;
497        }
498
499        boolean getStandardTableModel() {
500            return standardModel;
501        }
502
503        String getClassAsString() {
504            return className;
505        }
506
507        String getItemString() {
508            return itemText;
509        }
510
511    }
512
513    /**
514     * ActionJList This deals with handling non-default mouse operations on the
515     * List panel and allows for right click popups and double click to open new
516     * windows of the items we are hovering over.
517     */
518    private class ActionJList extends JmriMouseAdapter {
519
520        JPopupMenu popUp;
521        JMenuItem menuItem;
522
523        protected BeanTableFrame<E> frame;
524
525        ActionJList(BeanTableFrame<E> f) {
526            frame = f;
527            popUp = new JPopupMenu();
528            menuItem = new JMenuItem(Bundle.getMessage("MenuOpenInNewWindow"));
529            popUp.add(menuItem);
530            menuItem.addActionListener((ActionEvent e) -> openNewTableWindow(mouseItem));
531            currentItemSelected = 0;
532        }
533
534        private int currentItemSelected;
535
536        @Override
537        public void mousePressed(JmriMouseEvent e) {
538            if (e.isPopupTrigger()) {
539                showPopup(e);
540            }
541        }
542
543        @Override
544        public void mouseReleased(JmriMouseEvent e) {
545            if (e.isPopupTrigger()) {
546                showPopup(e);
547            }
548        }
549
550        // records the original pre-click index
551        private int beforeClickIndex;
552
553        //Records the item index that the mouse is currently over
554        private int mouseItem;
555
556        void showPopup(JmriMouseEvent e) {
557            popUp.show(e.getComponent(), e.getX(), e.getY());
558            mouseItem = list.locationToIndex(e.getPoint());
559        }
560
561        @Override
562        public void mouseClicked(JmriMouseEvent e) {
563
564            mouseItem = list.locationToIndex(e.getPoint());
565            if (popUp.isVisible()) {
566                return;
567            }
568            if (e.isPopupTrigger()) {
569                showPopup(e);
570                return;
571            }
572            if (e.getClickCount() == 1) {
573                beforeClickIndex = currentItemSelected;
574                selectListItem(mouseItem);
575            } else if (e.getClickCount() == 2) {
576                list.setSelectedIndex(beforeClickIndex);
577                selectListItem(beforeClickIndex);
578                openNewTableWindow(mouseItem);
579            }
580        }
581
582        void openNewTableWindow(int index) {
583            TabbedTableItem<E> item = tabbedTableArray.get(index);
584            class WindowMaker implements Runnable {
585
586                final TabbedTableItem<E> item;
587
588                WindowMaker(TabbedTableItem<E> tItem) {
589                    item = tItem;
590                }
591
592                @Override
593                public void run() {
594                    ListedTableAction tmp = new ListedTableAction(
595                        item.getItemString(), item.getClassAsString(), cardHolder.getDividerLocation());
596                    tmp.actionPerformed();
597                }
598            }
599            WindowMaker t = new WindowMaker(item);
600            SwingUtilities.invokeLater(t);
601        }
602
603        void selectListItem(int index) {
604            currentItemSelected = index;
605            TabbedTableItem<E> item = tabbedTableArray.get(index);
606            CardLayout cl = (CardLayout) (detailPanel.getLayout());
607            cl.show(detailPanel, item.getClassAsString());
608            frame.setTitle(item.getItemString());
609            frame.generateWindowRef();
610            try {
611                item.getAAClass().setFrame(frame);
612                buildMenus(item);
613            } catch (Exception ex) {
614                log.error("Could not build table {}", item, ex);
615            }
616            list.ensureIndexIsVisible(index);
617            list.setSelectedIndex(index);
618        }
619    }
620
621    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ListedTableFrame.class);
622
623}