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}