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