001package jmri.jmrix.can.cbus.swing.nodeconfig;
002
003import java.awt.BorderLayout;
004import java.awt.Color;
005import java.awt.Component;
006import java.awt.event.ActionListener;
007import java.text.SimpleDateFormat;
008import java.util.Date;
009
010import javax.swing.*;
011import javax.swing.event.*;
012import javax.swing.table.TableCellRenderer;
013
014import jmri.jmrix.can.cbus.node.*;
015
016import jmri.jmrix.can.cbus.swing.CbusCommonSwing;
017
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020
021/**
022 * Pane for displaying CBUS Node Configuration Backups.
023 * @author Steve Young Copyright (C) 2019
024 */
025public class CbusNodeBackupsPane extends CbusNodeConfigTab implements TableModelListener {
026    public SimpleDateFormat readableDateStyle = new SimpleDateFormat ("HH:mm EEE d MMM"); // NOI18N
027    private JScrollPane eventScroll;
028    private JPanel backupInfoPane;
029    private JPanel newInfoPane;
030    private JSplitPane split;
031    private ActionListener newBackupListener;
032    private ActionListener deleteBackupListener;
033    private JButton newBackupButton;
034    private JLabel headerText;
035    private JPanel evMenuPane;
036    private JTable backupTable;
037    private JTabbedPane tabbedBackupPane;
038    private CbusNodeNVTableDataModel nodeNVModel;
039    private CbusNodeNVEditTablePane nodevarPane;
040    private CbusNodeEventTablePane nodeEventPane;
041    private CbusNodeInfoPane nodeInfoPane;
042    private CbusNodeBackupTableModel cbusNodeBackupTableModel;
043    
044    // table stuff
045    public static final Color VERY_LIGHT_RED = new Color(255,176,173);
046    public static final Color VERY_LIGHT_GREEN = new Color(165,255,164);
047    public static final Color WHITE_GREEN = new Color(0xf5,0xf5,0xf5);
048
049    /**
050     * Create a new instance of CbusNodeBackupsPane.
051     * @param main the master Node Manager Pane
052     */
053    protected CbusNodeBackupsPane( NodeConfigToolPane main ) {
054        super(main);
055        initPane();
056    }
057    
058    /**
059     * {@inheritDoc}
060     */
061    @Override
062    public String getTitle(){
063        return "Node Backups";
064    }
065
066    /**
067     * Create the Pane components with a null Node.
068     */
069    public final void initPane() {
070        
071        if (eventScroll != null ){ 
072            eventScroll.setVisible(false);
073            evMenuPane.setVisible(false);
074        }
075        eventScroll = null;
076        evMenuPane = null;
077        
078        newBackupButton = new JButton(("Create New Backup"));
079        evMenuPane = new JPanel();
080        evMenuPane.add(newBackupButton);
081        
082        cbusNodeBackupTableModel = new CbusNodeBackupTableModel(null);
083        cbusNodeBackupTableModel.addTableModelListener(this);
084        
085        backupTable = new JTable(cbusNodeBackupTableModel);
086        backupTable.setRowHeight(26);
087        backupTable.setDefaultRenderer(Date.class, getRenderer());
088        backupTable.setDefaultRenderer(String.class, getRenderer());
089        backupTable.setDefaultRenderer(Integer.class, getRenderer());
090        backupTable.setDefaultRenderer(CbusNodeConstants.BackupType.class, getRenderer());
091        backupTable.setAutoCreateRowSorter(true);
092        backupTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
093        for (int i = 0; i < backupTable.getColumnCount(); i++) {
094            backupTable.getColumnModel().getColumn(i).setPreferredWidth(CbusNodeBackupTableModel.getPreferredWidth(i));
095        }
096        
097        headerText = new JLabel("");
098        evMenuPane.add(headerText);
099        updateHeaderText();
100        
101        JScrollPane backupTableScrollPane = new JScrollPane(backupTable);
102        //    textFieldName.setMargin( new java.awt.Insets(10,10,10,10) );
103        
104        // setLayout(new BorderLayout() );
105        
106        nodeInfoPane = new CbusNodeInfoPane(null);
107        
108        nodeNVModel = new CbusNodeNVTableDataModel(null, 5,
109            CbusNodeNVTableDataModel.MAX_COLUMN); // controller, row, column
110        nodevarPane = new CbusNodeNVEditTablePane(nodeNVModel);
111        nodevarPane.setNonEditable();
112        
113        CbusNodeEventTableDataModel nodeEvModel = new CbusNodeEventTableDataModel( null, null, 10,
114            CbusNodeEventTableDataModel.MAX_COLUMN); // controller, row, column
115        nodeEventPane = new CbusNodeEventTablePane(nodeEvModel);
116        nodeEventPane.setHideEditButton();
117        
118        backupInfoPane = new JPanel();
119        backupInfoPane.setLayout(new BorderLayout() );
120        
121        tabbedBackupPane = new JTabbedPane();
122        tabbedBackupPane.addTab(("Backup Info"), backupInfoPane);
123        tabbedBackupPane.addTab(("Node Info"), nodeInfoPane);
124        tabbedBackupPane.addTab(("Node Variables"), nodevarPane);
125        tabbedBackupPane.addTab(("Node Events"), nodeEventPane);
126        
127        split = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
128            backupTableScrollPane, tabbedBackupPane);
129        
130        // there is potential for a decent amount of data crunching involved
131        // when changing the view
132        split.setContinuousLayout(false);
133        split.setDividerLocation(100); // px from top of backups table
134        
135        this.add(evMenuPane, BorderLayout.PAGE_START);
136        this.add(split, BorderLayout.CENTER);
137        
138        validate();
139        repaint();
140        
141        newBackupListener = ae -> {
142            saveBackup();
143        };
144        newBackupButton.addActionListener(newBackupListener);
145        
146        // add listener for bottom pane
147        backupTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> {
148            if ( !e.getValueIsAdjusting() ) {
149                userBackupViewChanged();
150            }
151        });
152        
153        tabbedBackupPane.addChangeListener((ChangeEvent e) -> {
154            userBackupViewChanged();
155        });
156    }
157    
158    /**
159     * {@inheritDoc}
160     */
161    @Override
162    protected void disposeOfNode(CbusNode node){
163        node.removePropertyChangeListener(cbusNodeBackupTableModel);
164        super.disposeOfNode(node);
165    }
166    
167    /**
168     * Set the node and display backup details.
169     * {@inheritDoc}
170     */
171    @Override
172    public void changedNode(CbusNode node){
173        cbusNodeBackupTableModel.setNode(nodeOfInterest);
174        nodeOfInterest.addPropertyChangeListener(cbusNodeBackupTableModel);
175        userBackupViewChanged(); // set no backup selected message on startup
176    }
177    
178    /**
179     * Triggered when either the row selected has changed or tab has changed.
180     */
181    private void userBackupViewChanged(){
182        
183        int sel = backupTable.getSelectedRow();
184        CbusNodeFromBackup backupNode;
185        if ( backupTable.getSelectedRow() > -1 ) {
186            backupNode = nodeOfInterest.getNodeBackupManager().getBackups().get(backupTable.convertRowIndexToModel(sel));
187            tabbedBackupPane.setEnabled(true);
188            
189            if (tabbedBackupPane.getSelectedIndex()==1){
190                nodeInfoPane.setNode(backupNode);
191            }
192            if (tabbedBackupPane.getSelectedIndex()==2){
193                nodevarPane.setNode( backupNode );
194            }
195            if (tabbedBackupPane.getSelectedIndex()==3){
196                nodeEventPane.setNode( backupNode );
197            }
198            
199            
200        } else {
201            backupNode = null;
202            tabbedBackupPane.setSelectedIndex(0);
203            tabbedBackupPane.setEnabled(false);
204        }
205        
206        log.debug("user view changed node {}, index {}",backupNode,tabbedBackupPane.getSelectedIndex());
207        
208        if (tabbedBackupPane.getSelectedIndex()==0) {
209        
210            if (newInfoPane != null ){ 
211                newInfoPane.setVisible(false);
212            }
213            newInfoPane = null;
214        
215            // build backup pane locally
216        
217            newInfoPane = new JPanel();
218            
219            if ( backupNode!=null ) {
220                JScrollPane scroll = new JScrollPane(getBackupPanel(backupNode));
221                newInfoPane.setLayout(new BorderLayout() );
222                newInfoPane.add(scroll);
223
224                backupInfoPane.add(newInfoPane);
225                backupInfoPane.revalidate();
226
227            } else {
228                JLabel nvstring = new JLabel("<html><h3>No Backup Selected</h3></html>");
229                newInfoPane.add(nvstring);
230                backupInfoPane.add(newInfoPane);
231                backupInfoPane.validate();
232                backupInfoPane.repaint();
233            }
234        
235        }
236        
237    }
238    
239    private JPanel getBackupPanel(CbusNodeFromBackup backupNode){
240    
241        StringBuilder text = new StringBuilder();
242            text.append( "<html><h3>" )
243            .append(readableDateStyle.format(backupNode.getBackupTimeStamp()))
244            .append("</h3>")
245            .append(" <h4>NV's : " )
246            .append( ( Math.max(0,backupNode.getNodeParamManager().getParameter(6) )) )
247            .append( "</h4>")
248            .append( "<h4>Events : " )
249            .append(  ( Math.max(0,backupNode.getNodeEventManager().getTotalNodeEvents()) ) )
250            .append( "</h4>")
251            .append( "<h4>Params : " )
252            .append(  ( Math.max(0,backupNode.getNodeParamManager().getParameter(0) )) )
253            .append( "</h4>")
254            .append("</html>");
255            
256            JLabel nvstring = new JLabel(text.toString());
257        
258            JPanel evPane = new JPanel();
259            evPane.setLayout(new BoxLayout(evPane, BoxLayout.X_AXIS));
260            evPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
261            
262            // nvstring.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
263            
264            //  evPane.setLayout(new BorderLayout() );
265            evPane.add(nvstring);
266            
267            JButton restoreBackupButton = new JButton("Restore Backup");
268            JButton deleteBackupButton = new JButton("Delete Backup");
269            
270            restoreBackupButton.setEnabled(false);
271            
272            if (backupNode.getBackupResult() == CbusNodeConstants.BackupType.COMPLETE ) {
273                if ( backupNode.getNodeNvManager().getTotalNVs() == nodeOfInterest.getNodeNvManager().getTotalNVs() ) {
274                    if ( backupNode.getNodeParamManager().getParameter(5) == nodeOfInterest.getNodeParamManager().getParameter(5) ) {
275                        restoreBackupButton.setToolTipText(null);
276                        restoreBackupButton.setEnabled(true);
277                    } else {
278                        restoreBackupButton.setToolTipText("Event Variable total does not match");
279                    }
280                } else {
281                    restoreBackupButton.setToolTipText("NV total does not match");
282                }
283            } else {
284                restoreBackupButton.setToolTipText("Backup Incomplete");
285            }
286            
287            deleteBackupListener = ae -> {
288                deleteBackup(backupTable.convertRowIndexToModel(backupTable.getSelectedRow()));
289            };
290            deleteBackupButton.addActionListener(deleteBackupListener);
291            
292            ActionListener restore = ae -> {
293                // pre-validation checks, ie same nv's and same ev vars should be by button enabled
294                getMainPane().showConfirmThenSave(backupNode,nodeOfInterest,
295                    true, true, true, null ); // from, to, nvs, clear events, events, null uses mainpane frame
296            };
297            restoreBackupButton.addActionListener(restore);
298            
299            evPane.add(restoreBackupButton);
300            evPane.add(deleteBackupButton);
301    
302        return evPane;
303    }
304    
305    /**
306     * Updates the header text.
307     */
308    private void updateHeaderText(){
309        if (nodeOfInterest != null ) {
310            StringBuilder text = new StringBuilder();
311            text.append("<html><h4>");
312            if (nodeOfInterest.getNodeBackupManager().getBackups().size() == 1 ){
313                text.append(nodeOfInterest.getNodeBackupManager().getBackups().size())
314                .append(" xml entry");
315            } else {
316                text.append(nodeOfInterest.getNodeBackupManager().getBackups().size())
317                .append(" xml entries");
318            }
319            text.append("</h4></html>");
320            headerText.setText(text.toString());
321            evMenuPane.revalidate();
322            evMenuPane.repaint();
323        }
324    }
325    
326    /**
327     * Save a new backup with rotation.
328     */
329    private void saveBackup() {
330        if (!nodeOfInterest.getNodeBackupManager().doStore(true, nodeOfInterest.getNodeStats().hasLoadErrors())){
331            log.error("Issue saving Backup File");
332        }
333        cbusNodeBackupTableModel.fireTableDataChanged();
334    }
335    
336    /**
337     * Delete a backup from the array and re-save the XML.
338     * @param bup The index in the backup array to delete, 0 is most recent.
339     */
340    private void deleteBackup(int bup){
341        int rowAfter = Math.max(0,backupTable.getSelectedRow()-1);
342        nodeOfInterest.getNodeBackupManager().getBackups().remove(bup);
343        if (!nodeOfInterest.getNodeBackupManager().doStore(false, nodeOfInterest.getNodeStats().hasLoadErrors())){
344            log.error("Issue saving Backup File following remove single entry");
345        }
346        cbusNodeBackupTableModel.fireTableDataChanged();
347        if (backupTable.getRowCount() > 0 ) {
348            backupTable.getSelectionModel().setSelectionInterval(rowAfter,rowAfter);
349        }
350    }
351    
352    /**
353     * Cell Renderer for string table columns, highlights any text in filter input
354     */    
355    private TableCellRenderer getRenderer() {
356        return new TableCellRenderer() {
357            JTextField f = new JTextField();
358            
359            @Override
360            public Component getTableCellRendererComponent(
361                JTable table, Object arg1, boolean isSelected, boolean hasFocus, 
362                int row, int col) {
363                f.setHorizontalAlignment(JTextField.CENTER);
364                f.setBorder( table.getBorder() );
365                
366                String string;
367                CbusCommonSwing.setCellBackground(isSelected, f, table, row);
368                
369                if(arg1 != null){
370                    string = arg1.toString();
371                    f.setText(string);
372                    CbusCommonSwing.setCellFromDate(arg1, f, readableDateStyle);
373                    CbusCommonSwing.setCellFromBackupEnum(arg1, f);
374                } else {
375                    f.setText("");
376                }
377                CbusCommonSwing.setCellFocus(hasFocus, f, table);
378                return f;
379            }
380        };
381    }
382    
383    /**
384     * Update the header text (backup total) when table changes.
385     * {@inheritDoc} 
386     */
387    @Override
388    public void tableChanged(TableModelEvent e) {
389        updateHeaderText();
390    }
391
392    /**
393     * {@inheritDoc}
394     */
395    @Override
396    public void dispose() {
397        // bupFile.getBackupModel().removeTableModelListener(this);
398        disposeOfNode(nodeOfInterest);
399        nodeOfInterest.removePropertyChangeListener(cbusNodeBackupTableModel);
400    }
401    
402    private final static Logger log = LoggerFactory.getLogger(CbusNodeBackupsPane.class);
403    
404}