001package jmri.jmrit.beantable; 002 003import jmri.jmrit.beantable.oblock.*; 004import jmri.swing.RowSorterUtil; 005import jmri.util.swing.XTableColumnModel; 006import jmri.util.table.ButtonEditor; 007import jmri.util.table.ButtonRenderer; 008import jmri.util.table.ToggleButtonEditor; 009import jmri.util.table.ToggleButtonRenderer; 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013import javax.annotation.CheckForNull; 014import javax.annotation.Nonnull; 015import javax.swing.*; 016import javax.swing.table.TableModel; 017import javax.swing.table.TableRowSorter; 018import java.awt.*; 019import java.util.Objects; 020 021/** 022 * GUI for tabbed OBlock editing since 2020. Based on AudioTablePanel. 023 * OBlock parts adapted from {@link jmri.jmrit.beantable.oblock.TableFrames} 024 * Which interface will be presented is user settable in Display prefs. 025 * 026 * @author Bob Jacobsen Copyright (C) 2003 027 * @author Matthew Harris copyright (c) 2009 028 * @author Egbert Broerse copyright (c) 2020 029 */ 030public class OBlockTablePanel extends JPanel { 031 032 private OBlockTableModel oblockDataModel; 033 private PortalTableModel portalDataModel; 034 private SignalTableModel signalDataModel; 035 private BlockPortalTableModel blockportalDataModel; 036 037 private JTable oblockTable; 038 private JTable portalTable; 039 private JTable signalTable; 040 private JTable blockportalTable; 041 042 private JScrollPane oblockDataScroll; 043 private JScrollPane portalDataScroll; 044 private JScrollPane signalDataScroll; 045 private JScrollPane blockportalDataScroll; 046 047 private final JTabbedPane oblockTabs; 048 TableFrames _tf; 049 Box bottomBox; // panel at bottom for extra buttons etc 050 int bottomBoxIndex; // index to insert extra stuff 051 052 private static final int bottomStrutWidth = 20; 053 054// @SuppressWarnings("OverridableMethodCallInConstructor") 055 public OBlockTablePanel(OBlockTableModel oblocks, 056 PortalTableModel portals, 057 SignalTableModel signals, 058 BlockPortalTableModel blockportals, 059 TableFrames tf, 060 String helpTarget) { 061 062 super(); // required? nothing set 063 _tf = tf; 064 065 log.debug("Building tables"); 066 067 // OBlock Table 068 oblockDataModel = oblocks; 069 TableRowSorter<OBlockTableModel> sorter = new TableRowSorter<>(oblockDataModel); 070 // use NamedBean's built-in Comparator interface for sorting the system name column 071 RowSorterUtil.setSortOrder(sorter, OBlockTableModel.SYSNAMECOL, SortOrder.ASCENDING); 072 oblockTable = makeJTable(OBlockTableAction.class.getName(), oblockDataModel, sorter); // use our own 073 // style table, check overlap with configureWarrantTable done next 074 oblockTable.setDefaultEditor(JButton.class, new ButtonEditor(new JButton())); 075 oblockTable.setDefaultRenderer(JButton.class, new ButtonRenderer()); 076 oblockTable.getColumnModel().getColumn(OBlockTableModel.UNITSCOL).setCellRenderer( 077 new ToggleButtonRenderer(Bundle.getMessage("cm"), Bundle.getMessage("in"))); 078 oblockTable.getColumnModel().getColumn(OBlockTableModel.UNITSCOL).setCellEditor( 079 new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("cm"), Bundle.getMessage("in"))); 080 oblocks.configCurveColumn(oblockTable); // use real combo 081 oblockTable.getColumnModel().getColumn(OBlockTableModel.REPORT_CURRENTCOL).setCellRenderer( 082 new ToggleButtonRenderer(Bundle.getMessage("Current"), Bundle.getMessage("Last"))); 083 oblockTable.getColumnModel().getColumn(OBlockTableModel.REPORT_CURRENTCOL).setCellEditor( 084 new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("Current"), Bundle.getMessage("Last"))); 085 oblocks.configSpeedColumn(oblockTable); // use real combo 086 oblockTable.getColumnModel().getColumn(OBlockTableModel.PERMISSIONCOL).setCellRenderer( 087 new ToggleButtonRenderer(Bundle.getMessage("Permissive"), Bundle.getMessage("Absolute"))); 088 oblockTable.getColumnModel().getColumn(OBlockTableModel.PERMISSIONCOL).setCellEditor( 089 new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("Permissive"), Bundle.getMessage("Absolute"))); 090 // Use XTableColumnModel so we can control which columns are visible 091 XTableColumnModel tcm = new XTableColumnModel(); 092 oblockTable.setColumnModel(tcm); 093 oblockTable.getTableHeader().setReorderingAllowed(true); // makeJTable not used for oblockTable 094 oblockTable.createDefaultColumnsFromModel(); 095 tcm.setColumnVisible(tcm.getColumnByModelIndex(OBlockTableModel.REPORTERCOL), false); // doesn't hide them? 096 tcm.setColumnVisible(tcm.getColumnByModelIndex(OBlockTableModel.REPORT_CURRENTCOL), false); 097 tcm.setColumnVisible(tcm.getColumnByModelIndex(OBlockTableModel.PERMISSIONCOL), false); 098 tcm.setColumnVisible(tcm.getColumnByModelIndex(OBlockTableModel.WARRANTCOL), false); 099 tcm.setColumnVisible(tcm.getColumnByModelIndex(OBlockTableModel.ERR_SENSORCOL), false); 100 tcm.setColumnVisible(tcm.getColumnByModelIndex(OBlockTableModel.CURVECOL), false); 101 for (int i = 0; i < tcm.getColumnCount(); i++) { 102 int width = oblockDataModel.getPreferredWidth(i); 103 tcm.getColumn(i).setPreferredWidth(width); 104 } 105 oblockDataModel.addHeaderListener(oblockTable); // HeaderListeners not set up for the other 3 small tables 106 oblockTable.setPreferredScrollableViewportSize(new java.awt.Dimension(550, 300)); // a wide table 107 oblockDataScroll = new JScrollPane(oblockTable); 108 109 // Portal Table 110 portalDataModel = portals; 111 TableRowSorter<PortalTableModel> portalsorter = new TableRowSorter<>(portalDataModel); 112 RowSorterUtil.setSortOrder(portalsorter, portalDataModel.NAME_COLUMN, SortOrder.ASCENDING); 113 portalTable = makeJTable("Portal", portalDataModel, portalsorter); 114 // style table 115 portalTable.setDefaultEditor(JButton.class, new ButtonEditor(new JButton())); 116 portalTable.setDefaultRenderer(JButton.class, new ButtonRenderer()); 117 portalTable.doLayout(); 118 //portalTable.setColumnModel(new XTableColumnModel()); 119 portalTable.createDefaultColumnsFromModel(); 120 for (int i = 0; i < portalDataModel.getColumnCount(); i++) { 121 int width = portalDataModel.getPreferredWidth(i); 122 portalTable.getColumnModel().getColumn(i).setPreferredWidth(width); 123 } 124 portalDataScroll = new JScrollPane(portalTable); 125 126 // Signal Table 127 signalDataModel = signals; 128 TableRowSorter<SignalTableModel> sigsorter = new TableRowSorter<>(signalDataModel); 129 RowSorterUtil.setSortOrder(sigsorter, SignalTableModel.NAME_COLUMN, SortOrder.ASCENDING); 130 signalTable = makeJTable("Signals", signalDataModel, sigsorter); 131 // style table 132 signalTable.setDefaultEditor(JButton.class, new ButtonEditor(new JButton())); 133 signalTable.setDefaultRenderer(JButton.class, new ButtonRenderer()); 134 signalTable.getColumnModel().getColumn(SignalTableModel.UNITSCOL).setCellRenderer( 135 new ToggleButtonRenderer(Bundle.getMessage("cm"), Bundle.getMessage("in"))); 136 signalTable.getColumnModel().getColumn(SignalTableModel.UNITSCOL).setCellEditor( 137 new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("cm"), Bundle.getMessage("in"))); 138 signalTable.doLayout(); 139 //signalTable.setColumnModel(new XTableColumnModel()); 140 signalTable.createDefaultColumnsFromModel(); 141 for (int i = 0; i < signalDataModel.getColumnCount(); i++) { 142 int width = SignalTableModel.getPreferredWidth(i); 143 signalTable.getColumnModel().getColumn(i).setPreferredWidth(width); 144 } 145 signalDataScroll = new JScrollPane(signalTable); 146 147 // Block-Portal Xreference table 148 blockportalDataModel = blockportals; // cross-reference (not editable) 149 //sorter = new TableRowSorter<>(blockportalDataModel); 150 RowSorterUtil.setSortOrder(sorter, BlockPortalTableModel.BLOCK_NAME_COLUMN, SortOrder.ASCENDING); 151 blockportalTable = makeJTable("Block-Portal X-ref", blockportalDataModel, sorter); // cannot directly access 152 // style table 153 blockportalTable.setDefaultRenderer(String.class, new jmri.jmrit.symbolicprog.ValueRenderer()); 154 blockportalTable.doLayout(); 155 //blockportalTable.setColumnModel(new XTableColumnModel()); 156 blockportalTable.createDefaultColumnsFromModel(); 157 for (int i = 0; i < blockportalDataModel.getColumnCount(); i++) { 158 int width = blockportalDataModel.getPreferredWidth(i); 159 blockportalTable.getColumnModel().getColumn(i).setPreferredWidth(width); 160 } 161 blockportalDataScroll = new JScrollPane(blockportalTable); 162 163 // configure items for GUI 164 configureWarrantTable(oblockTable); // only class to extend BeanTableDataModel 165 //oblockDataModel.configEditColumn(oblockTable); 166 for (int i = 0; i < oblockTable.getColumnCount(); i++) { 167 // copied from TableFrames#makeOBlockTable() l729 as it needs table so can't copy to oblockDataModel 168 int width = oblockDataModel.getPreferredWidth(i); 169 oblockTable.getColumnModel().getColumn(i).setPreferredWidth(width); 170 } 171 oblockDataModel.persistTable(oblockTable); // only oblockDataModel contains this method 172 173 configureWarrantTable(signalTable); 174 // pathDataModel.configEditColumn(pathTable); 175 //oblockDataModel.persistTable(signalTable); 176 177 configureWarrantTable(portalTable); 178 // portalDataModel.configEditColumn(portalTable); 179 //oblockDataModel.persistTable(portalTable); 180 181 configureWarrantTable(blockportalTable); 182 // portalDataModel.configEditColumn(blockportalTable); 183 //oblockDataModel.persistTable(blockportalTable); 184 185 // add more changeListeners for table (example load, created) to update tables? 186 187 // general GUI config 188 this.setLayout(new BorderLayout()); 189 190 // install the four items in GUI as tabs 191 oblockTabs = new JTabbedPane(); 192 oblockTabs.addTab(Bundle.getMessage("BeanNameOBlocks"), oblockDataScroll); 193 oblockTabs.addTab(Bundle.getMessage("BeanNamePortals"), portalDataScroll); 194 oblockTabs.addTab(Bundle.getMessage("Signals"), signalDataScroll); 195 oblockTabs.addTab(Bundle.getMessage("TitleBlockPortalXRef"), blockportalDataScroll); 196 // turnouts not on a tab: via Edit button in Path Edit pane (or a Tables submenu) 197 198 add(oblockTabs, BorderLayout.CENTER); 199 log.debug("tabs complete"); 200 201 bottomBox = Box.createHorizontalBox(); 202 bottomBox.add(Box.createHorizontalGlue()); // stays at end of box 203 bottomBoxIndex = 0; 204 205 add(bottomBox, BorderLayout.SOUTH); 206 207 // add extras, if desired by subclass 208 extras(); 209 210 log.debug("bottomBox complete"); 211 // set preferred scrolling options 212 oblockDataScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 213// portalDataScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 214// signalDataScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 215// blockportalDataScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); 216 } 217 218 /** 219 * Hook to allow sub-types to install more items in GUI 220 */ 221 void extras() { 222 } 223 224 protected Box getBottomBox() { 225 return bottomBox; 226 } 227 228 public JMenuItem getPrintItem() { // copied from AudioTablePanel 229 log.debug("OBLOCK TABBED getPrintItem() called"); 230 return _tf.getPrintMenuItems(oblockTable, portalTable, signalTable, blockportalTable); 231 } 232 233 public JMenu getOptionMenu() { 234 log.debug("OBLOCK TABBED getOptionMenu() called"); 235 return _tf.getOptionMenu(); 236 } 237 238 public JMenu getTablesMenu() { 239 log.debug("OBLOCK TABBED getTablesMenu() called"); 240 return _tf.getTablesMenu(); 241 } 242 243 /** 244 * Add a component to the bottom box. Takes care of organising glue, struts 245 * etc 246 * 247 * @param comp {@link Component} to add 248 */ 249 protected void addToBottomBox(Component comp) { 250 bottomBox.add(Box.createHorizontalStrut(bottomStrutWidth), bottomBoxIndex); 251 ++bottomBoxIndex; 252 bottomBox.add(comp, bottomBoxIndex); 253 ++bottomBoxIndex; 254 } 255 256 public void dispose() { 257 if (oblockDataModel != null) { 258 oblockDataModel.stopPersistingTable(oblockTable); 259 oblockDataModel.dispose(); 260 } 261 oblockDataModel = null; 262 oblockTable = null; 263 oblockDataScroll = null; 264 265 //if (portalDataModel != null) { 266 // portalDataModel.stopPersistingTable(portalTable); 267 // portalDataModel.dispose(); 268 //} 269 portalDataModel = null; 270 portalTable = null; 271 portalDataScroll = null; 272 273 //if (signalDataModel != null) { 274 // signalDataModel.stopPersistingTable(signalTable); 275 // signalDataModel.dispose(); 276 //} 277 signalDataModel = null; 278 signalTable = null; 279 signalDataScroll = null; 280 281 //if (blockportalDataModel != null) { 282 // blockportalDataModel.stopPersistingTable(blockportalTable); 283 // blockportalDataModel.dispose(); 284 //} 285 blockportalDataModel = null; 286 blockportalTable = null; 287 blockportalDataScroll = null; 288 } 289 290 /** 291 * Create and configure a new table using the given model and row sorter. 292 * 293 * @param name the name of the table 294 * @param model the data model for the table 295 * @param sorter the row sorter for the table; if null, the table will not 296 * be sortable 297 * @return the table 298 * @throws NullPointerException if name or model is null 299 */ 300 public JTable makeJTable(@Nonnull String name, @Nonnull TableModel model, @CheckForNull RowSorter<? extends TableModel> sorter) { 301 Objects.requireNonNull(name, "the table name must be nonnull " + name); 302 Objects.requireNonNull(model, "the table model must be nonnull " + name); 303 JTable table = this.configureJTable(name, new JTable(model), sorter); 304 //model.addHeaderListener(table); 305 return table; 306 } 307 308 /** 309 * Configure a new table using the given model and row sorter. 310 * 311 * @param table the table to configure 312 * @param name the table name 313 * @param sorter the row sorter for the table; if null, the table will not 314 * be sortable 315 * @return the table 316 * @throws NullPointerException if table or the table name is null 317 */ 318 protected JTable configureJTable(@Nonnull String name, @Nonnull JTable table, @CheckForNull RowSorter<? extends TableModel> sorter) { 319 Objects.requireNonNull(table, "the table must be nonnull"); 320 Objects.requireNonNull(name, "the table name must be nonnull"); 321 table.setRowSorter(sorter); 322 table.setName(name); 323 //table.getTableHeader().setReorderingAllowed(true); // already assigned per table above 324 //table.setColumnModel(new XTableColumnModel()); 325 //table.createDefaultColumnsFromModel(); 326 return table; 327 } 328 329 /** 330 * Configure a table to have our standard rows and columns. 331 * This also persists the table user interface state. 332 * Adapted from {@link BeanTableDataModel} for tables 1-4, EBR 2020 333 * 334 * @param table {@link JTable} to configure 335 */ 336 public void configureWarrantTable(JTable table) { 337 // ignore Property columns 338 table.setDefaultRenderer(JButton.class, new ButtonRenderer()); 339 table.setDefaultEditor(JButton.class, new ButtonEditor(new JButton())); 340 table.setDefaultRenderer(JToggleButton.class, new ToggleButtonRenderer(Bundle.getMessage("cm"), Bundle.getMessage("in"))); // overrides 341 table.setDefaultEditor(JToggleButton.class, new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("cm"), Bundle.getMessage("in"))); 342 table.setDefaultRenderer(JRadioButton.class, new ToggleButtonRenderer(Bundle.getMessage("Current"), Bundle.getMessage("Last"))); // overrides 343 table.setDefaultEditor(JRadioButton.class, new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("Current"), Bundle.getMessage("Last"))); 344 table.setDefaultRenderer(JCheckBox.class, new ToggleButtonRenderer(Bundle.getMessage("Permissive"), Bundle.getMessage("Absolute"))); 345 table.setDefaultEditor(JCheckBox.class, new ToggleButtonEditor(new JToggleButton(), Bundle.getMessage("Permissive"), Bundle.getMessage("Absolute"))); 346 table.setDefaultEditor(OBlockTableModel.SpeedComboBoxPanel.class, new OBlockTableModel.SpeedComboBoxPanel()); 347 table.setDefaultRenderer(OBlockTableModel.SpeedComboBoxPanel.class, new OBlockTableModel.SpeedComboBoxPanel()); 348 table.setDefaultEditor(OBlockTableModel.CurveComboBoxPanel.class, new OBlockTableModel.CurveComboBoxPanel()); 349 table.setDefaultRenderer(OBlockTableModel.CurveComboBoxPanel.class, new OBlockTableModel.CurveComboBoxPanel()); 350 // allow reordering of the columns 351 //table.getTableHeader().setReorderingAllowed(true); 352 // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541) 353 table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 354 table.setRowHeight(TableFrames.ROW_HEIGHT); 355 // resize columns per table 356// table.doLayout(); 357 // resize columns as requested (for OBlocks tabbed: throws java.lang.IllegalArgumentException: "Identifier not found") 358// for (int i = 0; i < table.getColumnCount(); i++) { 359// int width = table.getColumn(i).getPreferredWidth(); 360// table.getColumnModel().getColumn(i).setPreferredWidth(width); 361// } 362 } 363 364 private static final Logger log = LoggerFactory.getLogger(OBlockTablePanel.class); 365 366}