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}