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}