001package jmri.jmrit.beantable;
002
003import java.awt.BorderLayout;
004import java.awt.event.ActionEvent;
005import java.util.ArrayList;
006import java.util.Enumeration;
007
008import javax.swing.*;
009import javax.swing.event.*;
010import javax.swing.table.*;
011
012import jmri.*;
013import jmri.swing.RowSorterUtil;
014import jmri.util.AlphanumComparator;
015import jmri.util.swing.TriStateJCheckBox;
016import jmri.util.swing.XTableColumnModel;
017
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020
021abstract public class AbstractTableTabAction<E extends NamedBean> extends AbstractTableAction<E> {
022
023    protected JPanel dataPanel;
024    protected JTabbedPane dataTabs;
025
026    protected boolean init = false;
027
028    public AbstractTableTabAction(String s) {
029        super(s);
030    }
031
032    @Override
033    protected void createModel() {
034        dataPanel = new JPanel();
035        dataTabs = new JTabbedPane();
036        dataPanel.setLayout(new BorderLayout());
037        Manager<E> mgr = getManager();
038        if (mgr instanceof jmri.managers.AbstractProxyManager) {
039            // build the list, with default at start and internal at end (if present)
040            jmri.managers.AbstractProxyManager<E> proxy = (jmri.managers.AbstractProxyManager<E>) mgr;
041
042            tabbedTableArray.add(new TabbedTableItem<>(Bundle.getMessage("All"), true, mgr, getNewTableAction("All"))); // NOI18N
043
044            proxy.getDisplayOrderManagerList().stream().map(manager -> {
045                String manuName = manager.getMemo().getUserName();
046                TabbedTableItem<E> itemModel = new TabbedTableItem<>(manuName, true, manager, getNewTableAction(manuName)); // connection name to display in Tab
047                return itemModel;
048            }).forEachOrdered(itemModel -> {
049                tabbedTableArray.add(itemModel);
050            });
051            
052        } else {
053            Manager<E> man = getManager();
054            String manuName = ( man!=null ? man.getMemo().getUserName() : "Unknown Manager");
055            tabbedTableArray.add(new TabbedTableItem<>(manuName, true, getManager(), getNewTableAction(manuName)));
056        }
057        for (int x = 0; x < tabbedTableArray.size(); x++) {
058            AbstractTableAction<E> table = tabbedTableArray.get(x).getAAClass();
059            table.addToPanel(this);
060            dataTabs.addTab(tabbedTableArray.get(x).getItemString(), null, tabbedTableArray.get(x).getPanel(), null);
061        }
062        dataTabs.addChangeListener((ChangeEvent evt) -> {
063            setMenuBar(f);
064        });
065        dataPanel.add(dataTabs, BorderLayout.CENTER);
066        init = true;
067    }
068
069    @Override
070    abstract protected Manager<E> getManager();
071
072    abstract protected AbstractTableAction<E> getNewTableAction(String choice);
073
074    @Override
075    public JPanel getPanel() {
076        if (!init) {
077            createModel();
078        }
079        return dataPanel;
080    }
081
082    protected ArrayList<TabbedTableItem<E>> tabbedTableArray = new ArrayList<>();
083
084    @Override
085    protected void setTitle() {
086        //atf.setTitle("multiple sensors");
087    }
088
089    @Override
090    abstract protected String helpTarget();
091
092    @Override
093    protected void addPressed(ActionEvent e) {
094        log.warn("This should not have happened");
095    }
096
097    @Override
098    public void addToFrame(BeanTableFrame<E> f) {
099        try {
100            TabbedTableItem<E> table = tabbedTableArray.get(dataTabs.getSelectedIndex());
101            if (table != null) {
102                table.getAAClass().addToFrame(f);
103            }
104        } catch (ArrayIndexOutOfBoundsException ex) {
105            log.error("{} in add to Frame {} {}", ex.toString(), dataTabs.getSelectedIndex(), dataTabs.getSelectedComponent());
106        }
107    }
108
109    @Override
110    public void setMenuBar(BeanTableFrame<E> f) {
111        try {
112            tabbedTableArray.get(dataTabs.getSelectedIndex()).getAAClass().setMenuBar(f);
113        } catch (ArrayIndexOutOfBoundsException ex) {
114            log.error("{} in add to Menu {} {}", ex.toString(), dataTabs.getSelectedIndex(), dataTabs.getSelectedComponent());
115        }
116    }
117
118    public void addToBottomBox(JComponent c, String str) {
119        tabbedTableArray.forEach((table) -> {
120            String item = table.getItemString();
121            if (item != null && item.equals(str)) {
122                table.addToBottomBox(c);
123            }
124        });
125    }
126
127    @Override
128    public void print(javax.swing.JTable.PrintMode mode, java.text.MessageFormat headerFormat, java.text.MessageFormat footerFormat) {
129        try {
130            tabbedTableArray.get(dataTabs.getSelectedIndex()).getDataTable().print(mode, headerFormat, footerFormat);
131        } catch (java.awt.print.PrinterException e1) {
132            log.warn("error printing: {}", e1);
133        } catch (NullPointerException ex) {
134            log.error("Trying to print returned a NPE error");
135        }
136    }
137
138    @Override
139    public void dispose() {
140        for (int x = 0; x < tabbedTableArray.size(); x++) {
141            tabbedTableArray.get(x).dispose();
142        }
143        super.dispose();
144    }
145
146    static protected class TabbedTableItem<E extends NamedBean> implements TableColumnModelListener {  // E comes from the parent
147
148        final AbstractTableAction<E> tableAction;
149        final String itemText;
150        private BeanTableDataModel<E> dataModel;
151        private JTable dataTable;
152        private JScrollPane dataScroll;
153        final Box bottomBox;
154        private boolean addToFrameRan = false;
155        final Manager<E> manager;
156
157        private int bottomBoxIndex; // index to insert extra stuff
158        static final int BOTTOM_STRUT_WIDTH = 20;
159
160        private boolean standardModel = true;
161
162        final JPanel dataPanel = new JPanel();
163
164        public TabbedTableItem(String choice, boolean stdModel, Manager<E> manager, AbstractTableAction<E> tableAction) {
165
166            this.tableAction = tableAction;
167            itemText = choice;
168            standardModel = stdModel;
169            this.manager = manager;
170
171            //If a panel model is used, it should really add to the bottom box
172            //but it can be done this way if required.
173            bottomBox = Box.createHorizontalBox();
174            bottomBox.add(Box.createHorizontalGlue());
175            bottomBoxIndex = 0;
176            dataPanel.setLayout(new BorderLayout());
177            if (stdModel) {
178                createDataModel();
179            } else {
180                addPanelModel();
181            }
182        }
183
184        final void createDataModel() {
185            if (manager != null) {
186                tableAction.setManager(manager);
187            }
188            dataModel = tableAction.getTableDataModel();
189            TableRowSorter<BeanTableDataModel<E>> sorter = new TableRowSorter<>(dataModel);
190            dataTable = dataModel.makeJTable(dataModel.getMasterClassName() + ":" + getItemString(), dataModel, sorter);
191            dataScroll = new JScrollPane(dataTable);
192
193            RowSorterUtil.setSortOrder(sorter, BeanTableDataModel.SYSNAMECOL, SortOrder.ASCENDING);
194
195            sorter.setComparator(BeanTableDataModel.USERNAMECOL, new AlphanumComparator());
196            RowSorterUtil.setSortOrder(sorter, BeanTableDataModel.USERNAMECOL, SortOrder.ASCENDING);
197
198            dataModel.configureTable(dataTable);
199            tableAction.configureTable(dataTable);
200
201            java.awt.Dimension dataTableSize = dataTable.getPreferredSize();
202            // width is right, but if table is empty, it's not high
203            // enough to reserve much space.
204            dataTableSize.height = Math.max(dataTableSize.height, 400);
205            dataScroll.getViewport().setPreferredSize(dataTableSize);
206
207            // set preferred scrolling options
208            dataScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
209            dataScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
210
211            //dataPanel.setLayout(new BoxLayout(dataPanel, BoxLayout.Y_AXIS));
212            dataPanel.add(dataScroll, BorderLayout.CENTER);
213
214            dataPanel.add(bottomBox, BorderLayout.SOUTH);
215            if (tableAction.includeAddButton()) {
216                JButton addButton = new JButton(Bundle.getMessage("ButtonAdd"));
217                addToBottomBox(addButton);
218                addButton.addActionListener((ActionEvent e) -> {
219                    tableAction.addPressed(e);
220                });
221            }
222            if (dataModel.getPropertyColumnCount() > 0) {
223                propertyVisible.setToolTipText(Bundle.getMessage
224                        ("ShowSystemSpecificPropertiesToolTip"));
225                addToBottomBox(propertyVisible);
226                propertyVisible.addActionListener((ActionEvent e) -> {
227                    dataModel.setPropertyColumnsVisible(dataTable, propertyVisible.isSelected());
228                });
229            }
230            
231            fireColumnsUpdated(); // init bottom buttons
232            dataTable.getColumnModel().addColumnModelListener(this);
233            
234        }
235        
236        private final TriStateJCheckBox propertyVisible = new TriStateJCheckBox(Bundle.getMessage("ShowSystemSpecificProperties"));
237        
238        /**
239         * Notify the subclasses that column visibility has been updated,
240         * or the table has finished loading.
241         * 
242         * Sends notification to the tableAction with boolean array of column visibility.
243         * 
244         */
245        private void fireColumnsUpdated(){
246            TableColumnModel model = dataTable.getColumnModel();
247            if (model instanceof XTableColumnModel) {
248                Enumeration<TableColumn> e = ((XTableColumnModel) model).getColumns(false);
249                int numCols = ((XTableColumnModel) model).getColumnCount(false);
250                // XTableColumnModel has been spotted to return a fleeting different
251                // column count to actual model, generally if manager is changed at startup
252                // so we do a sanity check to make sure the models are in synch.
253                if (numCols != dataModel.getColumnCount()){
254                    log.debug("Difference with Xtable cols: {} Model cols: {}",numCols,dataModel.getColumnCount());
255                    return;
256                }
257                boolean[] colsVisible = new boolean[numCols];
258                while (e.hasMoreElements()) {
259                    TableColumn column = e.nextElement();
260                    boolean visible = ((XTableColumnModel) model).isColumnVisible(column);
261                    colsVisible[column.getModelIndex()] = visible;
262                }
263                tableAction.columnsVisibleUpdated(colsVisible);
264                setPropertyVisibleCheckbox(colsVisible);
265            }
266        }
267        
268        /**
269         * Updates the custom bean property columns checkbox.
270         * @param colsVisible array of column visibility
271         */
272        private void setPropertyVisibleCheckbox(boolean[] colsVisible){
273            int numberofCustomCols = dataModel.getPropertyColumnCount();
274            if (numberofCustomCols>0){
275                boolean[] customColVisibility = new boolean[numberofCustomCols];
276                for ( int i=0; i<numberofCustomCols; i++){
277                    customColVisibility[i]=colsVisible[colsVisible.length-i-1];
278                }
279                propertyVisible.setState(customColVisibility);
280            }
281        }
282        
283        /**
284         * {@inheritDoc}
285         * A column is now visible.  fireColumnsUpdated()
286         */
287        @Override
288        public void columnAdded(TableColumnModelEvent e) {
289            fireColumnsUpdated();
290        }
291        
292        /**
293         * {@inheritDoc}
294         * A column is now hidden.  fireColumnsUpdated()
295         */
296        @Override
297        public void columnRemoved(TableColumnModelEvent e) {
298            fireColumnsUpdated();
299        }
300        
301        /**
302         * {@inheritDoc}
303         * Unused.
304         */
305        @Override
306        public void columnMoved(TableColumnModelEvent e) {}
307        
308        /**
309         * {@inheritDoc}
310         * Unused.
311         */
312        @Override
313        public void columnSelectionChanged(ListSelectionEvent e) {}
314        
315        /**
316         * {@inheritDoc}
317         * Unused.
318         */
319        @Override
320        public void columnMarginChanged(ChangeEvent e) {}
321
322        final void addPanelModel() {
323            dataPanel.add(tableAction.getPanel(), BorderLayout.CENTER);
324            dataPanel.add(bottomBox, BorderLayout.SOUTH);
325        }
326
327        public boolean getStandardTableModel() {
328            return standardModel;
329        }
330
331        public String getItemString() {
332            return itemText;
333        }
334
335        public AbstractTableAction<E> getAAClass() {
336            return tableAction;
337        }
338
339        public JPanel getPanel() {
340            return dataPanel;
341        }
342
343        public boolean getAdditionsToFrameDone() {
344            return addToFrameRan;
345        }
346
347        public void setAddToFrameRan() {
348            addToFrameRan = true;
349        }
350
351        public JTable getDataTable() {
352            return dataTable;
353        }
354
355        protected void addToBottomBox(JComponent comp) {
356            try {
357                bottomBox.add(Box.createHorizontalStrut(BOTTOM_STRUT_WIDTH), bottomBoxIndex);
358                ++bottomBoxIndex;
359                bottomBox.add(comp, bottomBoxIndex);
360                ++bottomBoxIndex;
361            } catch (java.lang.IllegalArgumentException ex) {
362                log.error(ex.getLocalizedMessage(), ex);
363            }
364        }
365
366        protected void dispose() {
367            if (dataTable !=null ) {
368                dataTable.getColumnModel().removeColumnModelListener(this);
369            }
370            if (dataModel != null) {
371                dataModel.stopPersistingTable(dataTable);
372                dataModel.dispose();
373            }
374            dataModel = null;
375            dataTable = null;
376            dataScroll = null;
377        }
378    }
379
380    private final static Logger log = LoggerFactory.getLogger(AbstractTableTabAction.class);
381
382}