001package jmri.jmrit.symbolicprog.tabbedframe; 002 003import java.awt.*; 004import java.awt.event.ActionEvent; 005import java.awt.event.ItemEvent; 006import java.awt.event.ItemListener; 007import java.util.ArrayList; 008import java.util.List; 009import javax.annotation.Nonnull; 010import javax.swing.*; 011 012import jmri.AddressedProgrammerManager; 013import jmri.GlobalProgrammerManager; 014import jmri.InstanceManager; 015import jmri.Programmer; 016import jmri.ProgrammingMode; 017import jmri.ShutDownTask; 018import jmri.UserPreferencesManager; 019import jmri.implementation.swing.SwingShutDownTask; 020import jmri.jmrit.XmlFile; 021import jmri.jmrit.decoderdefn.DecoderFile; 022import jmri.jmrit.decoderdefn.DecoderIndexFile; 023import jmri.jmrit.roster.*; 024import jmri.jmrit.symbolicprog.*; 025import jmri.util.BusyGlassPane; 026import jmri.util.FileUtil; 027import jmri.util.JmriJFrame; 028import jmri.util.swing.JmriJOptionPane; 029 030import org.jdom2.Attribute; 031import org.jdom2.Element; 032 033/** 034 * Frame providing a command station programmer from decoder definition files. 035 * 036 * @author Bob Jacobsen Copyright (C) 2001, 2004, 2005, 2008, 2014, 2018, 2019 037 * @author D Miller Copyright 2003, 2005 038 * @author Howard G. Penny Copyright (C) 2005 039 */ 040abstract public class PaneProgFrame extends JmriJFrame 041 implements java.beans.PropertyChangeListener, PaneContainer { 042 043 // members to contain working variable, CV values 044 JLabel progStatus = new JLabel(Bundle.getMessage("StateIdle")); 045 CvTableModel cvModel; 046 VariableTableModel variableModel; 047 048 ResetTableModel resetModel; 049 JMenu resetMenu = null; 050 051 ArrayList<ExtraMenuTableModel> extraMenuModelList; 052 ArrayList<JMenu> extraMenuList = new ArrayList<>(); 053 054 Programmer mProgrammer; 055 boolean noDecoder = false; 056 057 JMenuBar menuBar = new JMenuBar(); 058 059 JPanel tempPane; // passed around during construction 060 061 boolean _opsMode; 062 063 boolean maxFnNumDirty = false; 064 String maxFnNumOld = ""; 065 String maxFnNumNew = ""; 066 067 RosterEntry _rosterEntry; 068 RosterEntryPane _rPane = null; 069 FunctionLabelPane _flPane = null; 070 RosterMediaPane _rMPane = null; 071 String _frameEntryId; 072 073 List<JPanel> paneList = new ArrayList<>(); 074 int paneListIndex; 075 076 List<Element> decoderPaneList; 077 078 BusyGlassPane glassPane; 079 List<JComponent> activeComponents = new ArrayList<>(); 080 081 String filename; 082 String programmerShowEmptyPanes = ""; 083 String decoderShowEmptyPanes = ""; 084 String decoderAllowResetDefaults = ""; 085 String suppressFunctionLabels = ""; 086 String suppressRosterMedia = ""; 087 088 // GUI member declarations 089 JTabbedPane tabPane; 090 JToggleButton readChangesButton = new JToggleButton(Bundle.getMessage("ButtonReadChangesAllSheets")); 091 JToggleButton writeChangesButton = new JToggleButton(Bundle.getMessage("ButtonWriteChangesAllSheets")); 092 JToggleButton readAllButton = new JToggleButton(Bundle.getMessage("ButtonReadAllSheets")); 093 JToggleButton writeAllButton = new JToggleButton(Bundle.getMessage("ButtonWriteAllSheets")); 094 095 ItemListener l1; 096 ItemListener l2; 097 ItemListener l3; 098 ItemListener l4; 099 100 ShutDownTask decoderDirtyTask; 101 ShutDownTask fileDirtyTask; 102 103 public RosterEntryPane getRosterPane() { return _rPane;} 104 public FunctionLabelPane getFnLabelPane() { return _flPane;} 105 106 /** 107 * Abstract method to provide a JPanel setting the programming mode, if 108 * appropriate. 109 * <p> 110 * A null value is ignored (?) 111 * @return new mode panel for inclusion in the GUI 112 */ 113 abstract protected JPanel getModePane(); 114 115 protected void installComponents() { 116 117 tabPane = new jmri.util.org.mitre.jawb.swing.DetachableTabbedPane(" : "+_frameEntryId); 118 119 // create ShutDownTasks 120 if (decoderDirtyTask == null) { 121 decoderDirtyTask = new SwingShutDownTask("DecoderPro Decoder Window Check", 122 Bundle.getMessage("PromptQuitWindowNotWrittenDecoder"), null, this) { 123 @Override 124 public boolean checkPromptNeeded() { 125 return !checkDirtyDecoder(); 126 } 127 }; 128 } 129 jmri.InstanceManager.getDefault(jmri.ShutDownManager.class).register(decoderDirtyTask); 130 if (fileDirtyTask == null) { 131 fileDirtyTask = new SwingShutDownTask("DecoderPro Decoder Window Check", 132 Bundle.getMessage("PromptQuitWindowNotWrittenConfig"), 133 Bundle.getMessage("PromptSaveQuit"), this) { 134 @Override 135 public boolean checkPromptNeeded() { 136 return !checkDirtyFile(); 137 } 138 139 @Override 140 public boolean doPrompt() { 141 // storeFile returns false if failed, so abort shutdown 142 return storeFile(); 143 } 144 }; 145 } 146 jmri.InstanceManager.getDefault(jmri.ShutDownManager.class).register(fileDirtyTask); 147 148 // Create a menu bar 149 setJMenuBar(menuBar); 150 151 // add a "File" menu 152 JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile")); 153 menuBar.add(fileMenu); 154 155 // add a "Factory Reset" menu 156 resetMenu = new JMenu(Bundle.getMessage("MenuReset")); 157 menuBar.add(resetMenu); 158 resetMenu.add(new FactoryResetAction(Bundle.getMessage("MenuFactoryReset"), resetModel, this)); 159 resetMenu.setEnabled(false); 160 161 // Add a save item 162 JMenuItem menuItem = new JMenuItem(Bundle.getMessage("MenuSaveNoDots")); 163 menuItem.addActionListener(e -> storeFile() 164 165 ); 166 menuItem.setAccelerator(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.KeyEvent.META_DOWN_MASK)); 167 fileMenu.add(menuItem); 168 169 JMenu printSubMenu = new JMenu(Bundle.getMessage("MenuPrint")); 170 printSubMenu.add(new PrintAction(Bundle.getMessage("MenuPrintAll"), this, false)); 171 printSubMenu.add(new PrintCvAction(Bundle.getMessage("MenuPrintCVs"), cvModel, this, false, _rosterEntry)); 172 fileMenu.add(printSubMenu); 173 174 JMenu printPreviewSubMenu = new JMenu(Bundle.getMessage("MenuPrintPreview")); 175 printPreviewSubMenu.add(new PrintAction(Bundle.getMessage("MenuPrintPreviewAll"), this, true)); 176 printPreviewSubMenu.add(new PrintCvAction(Bundle.getMessage("MenuPrintPreviewCVs"), cvModel, this, true, _rosterEntry)); 177 fileMenu.add(printPreviewSubMenu); 178 179 // add "Import" submenu; this is hierarchical because 180 // some of the names are so long, and we expect more formats 181 JMenu importSubMenu = new JMenu(Bundle.getMessage("MenuImport")); 182 fileMenu.add(importSubMenu); 183 importSubMenu.add(new CsvImportAction(Bundle.getMessage("MenuImportCSV"), cvModel, this, progStatus)); 184 importSubMenu.add(new Pr1ImportAction(Bundle.getMessage("MenuImportPr1"), cvModel, this, progStatus)); 185 importSubMenu.add(new LokProgImportAction(Bundle.getMessage("MenuImportLokProg"), cvModel, this, progStatus)); 186 importSubMenu.add(new QuantumCvMgrImportAction(Bundle.getMessage("MenuImportQuantumCvMgr"), cvModel, this, progStatus)); 187 importSubMenu.add(new TcsImportAction(Bundle.getMessage("MenuImportTcsFile"), cvModel, variableModel, this, progStatus, _rosterEntry)); 188 if (TcsDownloadAction.willBeEnabled()) { 189 importSubMenu.add(new TcsDownloadAction(Bundle.getMessage("MenuImportTcsCS"), cvModel, variableModel, this, progStatus, _rosterEntry)); 190 } 191 192 // add "Export" submenu; this is hierarchical because 193 // some of the names are so long, and we expect more formats 194 JMenu exportSubMenu = new JMenu(Bundle.getMessage("MenuExport")); 195 fileMenu.add(exportSubMenu); 196 exportSubMenu.add(new CsvExportAction(Bundle.getMessage("MenuExportCSV"), cvModel, this)); 197 exportSubMenu.add(new CsvExportModifiedAction(Bundle.getMessage("MenuExportCSVModified"), cvModel, this)); 198 exportSubMenu.add(new Pr1ExportAction(Bundle.getMessage("MenuExportPr1DOS"), cvModel, this)); 199 exportSubMenu.add(new Pr1WinExportAction(Bundle.getMessage("MenuExportPr1WIN"), cvModel, this)); 200 exportSubMenu.add(new CsvExportVariablesAction(Bundle.getMessage("MenuExportVariables"), variableModel, this)); 201 exportSubMenu.add(new TcsExportAction(Bundle.getMessage("MenuExportTcsFile"), cvModel, variableModel, _rosterEntry, this)); 202 if (TcsDownloadAction.willBeEnabled()) { 203 exportSubMenu.add(new TcsUploadAction(Bundle.getMessage("MenuExportTcsCS"), cvModel, variableModel, _rosterEntry, this)); 204 } 205 206 // add "Import" submenu; this is hierarchical because 207 // some of the names are so long, and we expect more formats 208 JMenu speedTableSubMenu = new JMenu(Bundle.getMessage("MenuSpeedTable")); 209 fileMenu.add(speedTableSubMenu); 210 ButtonGroup SpeedTableNumbersGroup = new ButtonGroup(); 211 UserPreferencesManager upm = InstanceManager.getDefault(UserPreferencesManager.class); 212 Object speedTableNumbersSelectionObj = upm.getProperty(SpeedTableNumbers.class.getName(), "selection"); 213 214 SpeedTableNumbers speedTableNumbersSelection = 215 speedTableNumbersSelectionObj != null 216 ? SpeedTableNumbers.valueOf(speedTableNumbersSelectionObj.toString()) 217 : null; 218 219 for (SpeedTableNumbers speedTableNumbers : SpeedTableNumbers.values()) { 220 JRadioButtonMenuItem rbMenuItem = new JRadioButtonMenuItem(speedTableNumbers.toString()); 221 rbMenuItem.addActionListener((ActionEvent event) -> { 222 rbMenuItem.setSelected(true); 223 upm.setProperty(SpeedTableNumbers.class.getName(), "selection", speedTableNumbers.name()); 224 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("MenuSpeedTable_CloseReopenWindow")); 225 }); 226 rbMenuItem.setSelected(speedTableNumbers == speedTableNumbersSelection); 227 speedTableSubMenu.add(rbMenuItem); 228 SpeedTableNumbersGroup.add(rbMenuItem); 229 } 230 231 // to control size, we need to insert a single 232 // JPanel, then have it laid out with BoxLayout 233 JPanel pane = new JPanel(); 234 tempPane = pane; 235 236 // general GUI config 237 pane.setLayout(new BorderLayout()); 238 239 // configure GUI elements 240 // set read buttons enabled state, tooltips 241 enableReadButtons(); 242 243 readChangesButton.addItemListener(l1 = e -> { 244 if (e.getStateChange() == ItemEvent.SELECTED) { 245 prepGlassPane(readChangesButton); 246 readChangesButton.setText(Bundle.getMessage("ButtonStopReadChangesAll")); 247 readChanges(); 248 } else { 249 if (_programmingPane != null) { 250 _programmingPane.stopProgramming(); 251 } 252 paneListIndex = paneList.size(); 253 readChangesButton.setText(Bundle.getMessage("ButtonReadChangesAllSheets")); 254 } 255 }); 256 257 readAllButton.addItemListener(l3 = e -> { 258 if (e.getStateChange() == ItemEvent.SELECTED) { 259 prepGlassPane(readAllButton); 260 readAllButton.setText(Bundle.getMessage("ButtonStopReadAll")); 261 readAll(); 262 } else { 263 if (_programmingPane != null) { 264 _programmingPane.stopProgramming(); 265 } 266 paneListIndex = paneList.size(); 267 readAllButton.setText(Bundle.getMessage("ButtonReadAllSheets")); 268 } 269 }); 270 271 writeChangesButton.setToolTipText(Bundle.getMessage("TipWriteHighlightedValues")); 272 writeChangesButton.addItemListener(l2 = e -> { 273 if (e.getStateChange() == ItemEvent.SELECTED) { 274 prepGlassPane(writeChangesButton); 275 writeChangesButton.setText(Bundle.getMessage("ButtonStopWriteChangesAll")); 276 writeChanges(); 277 } else { 278 if (_programmingPane != null) { 279 _programmingPane.stopProgramming(); 280 } 281 paneListIndex = paneList.size(); 282 writeChangesButton.setText(Bundle.getMessage("ButtonWriteChangesAllSheets")); 283 } 284 }); 285 286 writeAllButton.setToolTipText(Bundle.getMessage("TipWriteAllValues")); 287 writeAllButton.addItemListener(l4 = e -> { 288 if (e.getStateChange() == ItemEvent.SELECTED) { 289 prepGlassPane(writeAllButton); 290 writeAllButton.setText(Bundle.getMessage("ButtonStopWriteAll")); 291 writeAll(); 292 } else { 293 if (_programmingPane != null) { 294 _programmingPane.stopProgramming(); 295 } 296 paneListIndex = paneList.size(); 297 writeAllButton.setText(Bundle.getMessage("ButtonWriteAllSheets")); 298 } 299 }); 300 301 // most of the GUI is done from XML in readConfig() function 302 // which configures the tabPane 303 pane.add(tabPane, BorderLayout.CENTER); 304 305 // and put that pane into the JFrame 306 getContentPane().add(pane); 307 308 } 309 310 void setProgrammingGui(JPanel bottom) { 311 // see if programming mode is available 312 JPanel tempModePane = null; 313 if (!noDecoder) { 314 tempModePane = getModePane(); 315 } 316 if (tempModePane != null) { 317 // if so, configure programming part of GUI 318 // add buttons 319 JPanel bottomButtons = new JPanel(); 320 bottomButtons.setLayout(new BoxLayout(bottomButtons, BoxLayout.X_AXIS)); 321 322 bottomButtons.add(readChangesButton); 323 bottomButtons.add(writeChangesButton); 324 bottomButtons.add(readAllButton); 325 bottomButtons.add(writeAllButton); 326 bottom.add(bottomButtons); 327 328 // add programming mode 329 bottom.add(new JSeparator(javax.swing.SwingConstants.HORIZONTAL)); 330 JPanel temp = new JPanel(); 331 bottom.add(temp); 332 temp.add(tempModePane); 333 } else { 334 // set title to Editing 335 super.setTitle(Bundle.getMessage("TitleEditPane", _frameEntryId)); 336 } 337 338 // add space for (programming) status message 339 bottom.add(new JSeparator(javax.swing.SwingConstants.HORIZONTAL)); 340 progStatus.setAlignmentX(JLabel.CENTER_ALIGNMENT); 341 bottom.add(progStatus); 342 } 343 344 // ================== Search section ================== 345 346 // create and add the Search GUI 347 void setSearchGui(JPanel bottom) { 348 // search field 349 searchBar = new jmri.util.swing.SearchBar(searchForwardTask, searchBackwardTask, searchDoneTask); 350 searchBar.setVisible(false); // start not visible 351 searchBar.configureKeyModifiers(this); 352 bottom.add(searchBar); 353 } 354 355 jmri.util.swing.SearchBar searchBar; 356 static class SearchPair { 357 WatchingLabel label; 358 JPanel tab; 359 SearchPair(WatchingLabel label, @Nonnull JPanel tab) { 360 this.label = label; 361 this.tab = tab; 362 } 363 } 364 365 ArrayList<SearchPair> searchTargetList; 366 int nextSearchTarget = 0; 367 368 // Load the array of search targets 369 protected void loadSearchTargets() { 370 if (searchTargetList != null) return; 371 372 searchTargetList = new ArrayList<>(); 373 374 for (JPanel p : getPaneList()) { 375 for (Component c : p.getComponents()) { 376 loadJPanel(c, p); 377 } 378 } 379 380 // add the panes themselves 381 for (JPanel tab : getPaneList()) { 382 searchTargetList.add( new SearchPair( null, tab )); 383 } 384 } 385 386 // Recursive load of possible search targets 387 protected void loadJPanel(Component c, JPanel tab) { 388 if (c instanceof JPanel) { 389 for (Component d : ((JPanel)c).getComponents()) { 390 loadJPanel(d, tab); 391 } 392 } else if (c instanceof JScrollPane) { 393 loadJPanel( ((JScrollPane)c).getViewport().getView(), tab); 394 } else if (c instanceof WatchingLabel) { 395 searchTargetList.add( new SearchPair( (WatchingLabel)c, tab)); 396 } 397 } 398 399 // Search didn't find anything at all 400 protected void searchDidNotFind() { 401 java.awt.Toolkit.getDefaultToolkit().beep(); 402 } 403 404 // Search succeeded, go to the result 405 protected void searchGoesTo(SearchPair result) { 406 tabPane.setSelectedComponent(result.tab); 407 if (result.label != null) { 408 SwingUtilities.invokeLater(() -> result.label.getWatched().requestFocus()); 409 } else { 410 log.trace("search result set to tab {}", result.tab); 411 } 412 } 413 414 // Check a single case to see if its search match 415 // @return true for matched 416 private boolean checkSearchTarget(int index, String target) { 417 boolean result = false; 418 if (searchTargetList.get(index).label != null ) { 419 // match label text 420 if ( ! searchTargetList.get(index).label.getText().toUpperCase().contains(target.toUpperCase() ) ) { 421 return false; 422 } 423 // only match if showing 424 return searchTargetList.get(index).label.isShowing(); 425 } else { 426 // Match pane label. 427 // Finding the tab requires a search here. 428 // Could have passed a clue along in SwingUtilities 429 for (int i = 0; i < tabPane.getTabCount(); i++) { 430 if (tabPane.getComponentAt(i) == searchTargetList.get(index).tab) { 431 result = tabPane.getTitleAt(i).toUpperCase().contains(target.toUpperCase()); 432 } 433 } 434 } 435 return result; 436 } 437 438 // Invoked by forward search operation 439 private final Runnable searchForwardTask = new Runnable() { 440 @Override 441 public void run() { 442 log.trace("start forward"); 443 loadSearchTargets(); 444 String target = searchBar.getSearchString(); 445 446 nextSearchTarget++; 447 if (nextSearchTarget < 0 ) nextSearchTarget = 0; 448 if (nextSearchTarget >= searchTargetList.size() ) nextSearchTarget = 0; 449 450 int startingSearchTarget = nextSearchTarget; 451 452 while (nextSearchTarget < searchTargetList.size()) { 453 if ( checkSearchTarget(nextSearchTarget, target)) { 454 // hit! 455 searchGoesTo(searchTargetList.get(nextSearchTarget)); 456 return; 457 } 458 nextSearchTarget++; 459 } 460 461 // end reached, wrap 462 nextSearchTarget = 0; 463 while (nextSearchTarget < startingSearchTarget) { 464 if ( checkSearchTarget(nextSearchTarget, target)) { 465 // hit! 466 searchGoesTo(searchTargetList.get(nextSearchTarget)); 467 return; 468 } 469 nextSearchTarget++; 470 } 471 // not found 472 searchDidNotFind(); 473 } 474 }; 475 476 // Invoked by backward search operation 477 private final Runnable searchBackwardTask = new Runnable() { 478 @Override 479 public void run() { 480 log.trace("start backward"); 481 loadSearchTargets(); 482 String target = searchBar.getSearchString(); 483 484 nextSearchTarget--; 485 if (nextSearchTarget < 0 ) nextSearchTarget = searchTargetList.size()-1; 486 if (nextSearchTarget >= searchTargetList.size() ) nextSearchTarget = searchTargetList.size()-1; 487 488 int startingSearchTarget = nextSearchTarget; 489 490 while (nextSearchTarget > 0) { 491 if ( checkSearchTarget(nextSearchTarget, target)) { 492 // hit! 493 searchGoesTo(searchTargetList.get(nextSearchTarget)); 494 return; 495 } 496 nextSearchTarget--; 497 } 498 499 // start reached, wrap 500 nextSearchTarget = searchTargetList.size() - 1; 501 while (nextSearchTarget > startingSearchTarget) { 502 if ( checkSearchTarget(nextSearchTarget, target)) { 503 // hit! 504 searchGoesTo(searchTargetList.get(nextSearchTarget)); 505 return; 506 } 507 nextSearchTarget--; 508 } 509 // not found 510 searchDidNotFind(); 511 } 512 }; 513 514 // Invoked when search bar Done is pressed 515 private final Runnable searchDoneTask = new Runnable() { 516 @Override 517 public void run() { 518 log.debug("done with search bar"); 519 searchBar.setVisible(false); 520 } 521 }; 522 523 // =================== End of search section ================== 524 525 public List<JPanel> getPaneList() { 526 return paneList; 527 } 528 529 void addHelp() { 530 addHelpMenu("package.jmri.jmrit.symbolicprog.tabbedframe.PaneProgFrame", true); 531 } 532 533 @Override 534 public Dimension getPreferredSize() { 535 Dimension screen = getMaximumSize(); 536 int width = Math.min(super.getPreferredSize().width, screen.width); 537 int height = Math.min(super.getPreferredSize().height, screen.height); 538 return new Dimension(width, height); 539 } 540 541 @Override 542 public Dimension getMaximumSize() { 543 Dimension screen = getToolkit().getScreenSize(); 544 return new Dimension(screen.width, screen.height - 35); 545 } 546 547 /** 548 * Enable the [Read all] and [Read changes] buttons if possible. This checks 549 * to make sure this is appropriate, given the attached programmer's 550 * capability. 551 */ 552 void enableReadButtons() { 553 readChangesButton.setToolTipText(Bundle.getMessage("TipReadChanges")); 554 readAllButton.setToolTipText(Bundle.getMessage("TipReadAll")); 555 // check with CVTable programmer to see if read is possible 556 if (cvModel != null && cvModel.getProgrammer() != null 557 && !cvModel.getProgrammer().getCanRead() 558 || noDecoder) { 559 // can't read, disable the button 560 readChangesButton.setEnabled(false); 561 readAllButton.setEnabled(false); 562 readChangesButton.setToolTipText(Bundle.getMessage("TipNoRead")); 563 readAllButton.setToolTipText(Bundle.getMessage("TipNoRead")); 564 } else { 565 readChangesButton.setEnabled(true); 566 readAllButton.setEnabled(true); 567 } 568 } 569 570 /** 571 * Initialization sequence: 572 * <ul> 573 * <li> Ask the RosterEntry to read its contents 574 * <li> If the decoder file is specified, open and load it, otherwise get 575 * the decoder filename from the RosterEntry and load that. Note that we're 576 * assuming the roster entry has the right decoder, at least w.r.t. the loco 577 * file. 578 * <li> Fill CV values from the roster entry 579 * <li> Create the programmer panes 580 * </ul> 581 * 582 * @param pDecoderFile XML file defining the decoder contents; if null, 583 * the decoder definition is found from the 584 * RosterEntry 585 * @param pRosterEntry RosterEntry for information on this locomotive 586 * @param pFrameEntryId Roster ID (entry) loaded into the frame 587 * @param pProgrammerFile Name of the programmer file to use 588 * @param pProg Programmer object to be used to access CVs 589 * @param opsMode true for opsMode, else false. 590 */ 591 public PaneProgFrame(DecoderFile pDecoderFile, @Nonnull RosterEntry pRosterEntry, 592 String pFrameEntryId, String pProgrammerFile, Programmer pProg, boolean opsMode) { 593 super(Bundle.getMessage("TitleProgPane", pFrameEntryId)); 594 595 _rosterEntry = pRosterEntry; 596 _opsMode = opsMode; 597 filename = pProgrammerFile; 598 mProgrammer = pProg; 599 _frameEntryId = pFrameEntryId; 600 601 // create the tables 602 cvModel = new CvTableModel(progStatus, mProgrammer); 603 604 variableModel = new VariableTableModel(progStatus, new String[] {"Name", "Value"}, 605 cvModel); 606 607 resetModel = new ResetTableModel(progStatus, mProgrammer); 608 extraMenuModelList = new ArrayList<>(); 609 610 // handle the roster entry 611 _rosterEntry.setOpen(true); 612 613 installComponents(); 614 615 if (_rosterEntry.getFileName() != null) { 616 // set the loco file name in the roster entry 617 _rosterEntry.readFile(); // read, but don't yet process 618 } 619 620 if (pDecoderFile != null) { 621 loadDecoderFile(pDecoderFile, _rosterEntry); 622 } else { 623 loadDecoderFromLoco(pRosterEntry); 624 } 625 626 // save default values 627 saveDefaults(); 628 629 // finally fill the Variable and CV values from the specific loco file 630 if (_rosterEntry.getFileName() != null) { 631 _rosterEntry.loadCvModel(variableModel, cvModel); 632 } 633 634 // mark file state as consistent 635 variableModel.setFileDirty(false); 636 637 // if the Reset Table was used lets enable the menu item 638 if (!_opsMode || resetModel.hasOpsModeReset()) { 639 if (resetModel.getRowCount() > 0) { 640 resetMenu.setEnabled(true); 641 } 642 } 643 644 // if there are extra menus defined, enable them 645 log.trace("enabling {} {}", extraMenuModelList.size(), extraMenuModelList); 646 for (int i = 0; i<extraMenuModelList.size(); i++) { 647 log.trace("enabling {} {}", _opsMode, extraMenuModelList.get(i).hasOpsModeReset()); 648 if ( !_opsMode || extraMenuModelList.get(i).hasOpsModeReset()) { 649 if (extraMenuModelList.get(i).getRowCount() > 0) { 650 extraMenuList.get(i).setEnabled(true); 651 } 652 } 653 } 654 655 // set the programming mode 656 if (pProg != null) { 657 if (InstanceManager.getOptionalDefault(AddressedProgrammerManager.class).isPresent() 658 || InstanceManager.getOptionalDefault(GlobalProgrammerManager.class).isPresent()) { 659 // go through in preference order, trying to find a mode 660 // that exists in both the programmer and decoder. 661 // First, get attributes. If not present, assume that 662 // all modes are usable 663 Element programming = null; 664 if (decoderRoot != null 665 && (programming = decoderRoot.getChild("decoder").getChild("programming")) != null) { 666 667 // add a verify-write facade if configured 668 Programmer pf = mProgrammer; 669 if (getDoConfirmRead()) { 670 pf = new jmri.implementation.VerifyWriteProgrammerFacade(pf); 671 log.debug("adding VerifyWriteProgrammerFacade, new programmer is {}", pf); 672 } 673 // add any facades defined in the decoder file 674 pf = jmri.implementation.ProgrammerFacadeSelector 675 .loadFacadeElements(programming, pf, getCanCacheDefault(), pProg); 676 log.debug("added any other FacadeElements, new programmer is {}", pf); 677 mProgrammer = pf; 678 cvModel.setProgrammer(pf); 679 resetModel.setProgrammer(pf); 680 for (var model : extraMenuModelList) { 681 model.setProgrammer(pf); 682 } 683 log.debug("Found programmer: {}", cvModel.getProgrammer()); 684 } 685 686 // done after setting facades in case new possibilities appear 687 if (programming != null) { 688 pickProgrammerMode(programming); 689 // reset the read buttons if the mode changes 690 enableReadButtons(); 691 if (noDecoder) { 692 writeChangesButton.setEnabled(false); 693 writeAllButton.setEnabled(false); 694 } 695 } else { 696 log.debug("Skipping programmer setup because found no programmer element"); 697 } 698 699 } else { 700 log.error("Can't set programming mode, no programmer instance"); 701 } 702 } 703 704 // and build the GUI (after programmer mode because it depends on what's available) 705 loadProgrammerFile(pRosterEntry); 706 707 // optionally, add extra panes from the decoder file 708 Attribute a; 709 if ((a = programmerRoot.getChild("programmer").getAttribute("decoderFilePanes")) != null 710 && a.getValue().equals("yes")) { 711 if (decoderRoot != null) { 712 if (log.isDebugEnabled()) { 713 log.debug("will process {} pane definitions from decoder file", decoderPaneList.size()); 714 } 715 for (Element element : decoderPaneList) { 716 // load each pane 717 String pname = jmri.util.jdom.LocaleSelector.getAttribute(element, "name"); 718 719 // handle include/exclude 720 if (isIncludedFE(element, modelElem, _rosterEntry, "", "")) { 721 newPane(pname, element, modelElem, true, false); // show even if empty not a programmer pane 722 log.debug("PaneProgFrame init - pane {} added", pname); // these are MISSING in RosterPrint 723 } 724 } 725 } 726 } 727 728 JPanel bottom = new JPanel(); 729 bottom.setLayout(new BoxLayout(bottom, BoxLayout.Y_AXIS)); 730 tempPane.add(bottom, BorderLayout.SOUTH); 731 732 // now that programmer is configured, set the programming GUI 733 setProgrammingGui(bottom); 734 735 // add the search GUI 736 setSearchGui(bottom); 737 738 pack(); 739 740 if (log.isDebugEnabled()) { // because size elements take time 741 log.debug("PaneProgFrame \"{}\" constructed for file {}, unconstrained size is {}, constrained to {}", 742 pFrameEntryId, _rosterEntry.getFileName(), super.getPreferredSize(), getPreferredSize()); 743 } 744 } 745 746 /** 747 * Front end to DecoderFile.isIncluded() 748 * <ul> 749 * <li>Retrieves "productID" and "model attributes from the "model" element 750 * and "family" attribute from the roster entry. </li> 751 * <li>Then invokes DecoderFile.isIncluded() with the retrieved values.</li> 752 * <li>Deals gracefully with null or missing elements and 753 * attributes.</li> 754 * </ul> 755 * 756 * @param e XML element with possible "include" and "exclude" 757 * attributes to be checked 758 * @param aModelElement "model" element from the Decoder Index, used to get 759 * "model" and "productID". 760 * @param aRosterEntry The current roster entry, used to get "family". 761 * @param extraIncludes additional "include" terms 762 * @param extraExcludes additional "exclude" terms. 763 * @return true if front ended included, else false. 764 */ 765 public static boolean isIncludedFE(Element e, Element aModelElement, RosterEntry aRosterEntry, String extraIncludes, String extraExcludes) { 766 767 String pID; 768 try { 769 pID = aModelElement.getAttribute("productID").getValue(); 770 } catch (Exception ex) { 771 pID = null; 772 } 773 774 String modelName; 775 try { 776 modelName = aModelElement.getAttribute("model").getValue(); 777 } catch (Exception ex) { 778 modelName = null; 779 } 780 781 String familyName; 782 try { 783 familyName = aRosterEntry.getDecoderFamily(); 784 } catch (Exception ex) { 785 familyName = null; 786 } 787 return DecoderFile.isIncluded(e, pID, modelName, familyName, extraIncludes, extraExcludes); 788 } 789 790 protected void pickProgrammerMode(@Nonnull Element programming) { 791 log.debug("pickProgrammerMode starts"); 792 boolean paged = true; 793 boolean directbit = true; 794 boolean directbyte = true; 795 boolean register = true; 796 797 Attribute a; 798 799 // set the programming attributes for DCC 800 if ((a = programming.getAttribute("nodecoder")) != null) { 801 if (a.getValue().equals("yes")) { 802 noDecoder = true; // No decoder in the loco 803 } 804 } 805 if ((a = programming.getAttribute("paged")) != null) { 806 if (a.getValue().equals("no")) { 807 paged = false; 808 } 809 } 810 if ((a = programming.getAttribute("direct")) != null) { 811 if (a.getValue().equals("no")) { 812 directbit = false; 813 directbyte = false; 814 } else if (a.getValue().equals("bitOnly")) { 815 //directbit = true; 816 directbyte = false; 817 } else if (a.getValue().equals("byteOnly")) { 818 directbit = false; 819 //directbyte = true; 820 //} else { // items already have these values 821 //directbit = true; 822 //directbyte = true; 823 } 824 } 825 if ((a = programming.getAttribute("register")) != null) { 826 if (a.getValue().equals("no")) { 827 register = false; 828 } 829 } 830 831 // find an accepted mode to set it to 832 List<ProgrammingMode> modes = mProgrammer.getSupportedModes(); 833 834 if (log.isDebugEnabled()) { 835 log.debug("XML specifies modes: P {} DBi {} Dby {} R {} now {}", paged, directbit, directbyte, register, mProgrammer.getMode()); 836 log.debug("Programmer supports:"); 837 for (ProgrammingMode m : modes) { 838 log.debug(" mode: {} {}", m.getStandardName(), m); 839 } 840 } 841 842 StringBuilder desiredModes = new StringBuilder(); 843 // first try specified modes 844 for (Element el1 : programming.getChildren("mode")) { 845 String name = el1.getText(); 846 if (desiredModes.length() > 0) desiredModes.append(", "); 847 desiredModes.append(name); 848 log.debug(" mode {} was specified", name); 849 for (ProgrammingMode m : modes) { 850 if (name.equals(m.getStandardName())) { 851 log.info("Programming mode selected: {} ({})", m, m.getStandardName()); 852 mProgrammer.setMode(m); 853 return; 854 } 855 } 856 } 857 858 // go through historical modes 859 if (modes.contains(ProgrammingMode.DIRECTMODE) && directbit && directbyte) { 860 mProgrammer.setMode(ProgrammingMode.DIRECTMODE); 861 log.debug("Set to DIRECTMODE"); 862 } else if (modes.contains(ProgrammingMode.DIRECTBITMODE) && directbit) { 863 mProgrammer.setMode(ProgrammingMode.DIRECTBITMODE); 864 log.debug("Set to DIRECTBITMODE"); 865 } else if (modes.contains(ProgrammingMode.DIRECTBYTEMODE) && directbyte) { 866 mProgrammer.setMode(ProgrammingMode.DIRECTBYTEMODE); 867 log.debug("Set to DIRECTBYTEMODE"); 868 } else if (modes.contains(ProgrammingMode.PAGEMODE) && paged) { 869 mProgrammer.setMode(ProgrammingMode.PAGEMODE); 870 log.debug("Set to PAGEMODE"); 871 } else if (modes.contains(ProgrammingMode.REGISTERMODE) && register) { 872 mProgrammer.setMode(ProgrammingMode.REGISTERMODE); 873 log.debug("Set to REGISTERMODE"); 874 } else if (noDecoder) { 875 log.debug("No decoder"); 876 } else { 877 JmriJOptionPane.showMessageDialog( 878 this, 879 Bundle.getMessage("ErrorCannotSetMode", desiredModes.toString()), 880 Bundle.getMessage("ErrorCannotSetModeTitle"), 881 JmriJOptionPane.ERROR_MESSAGE); 882 log.warn("No acceptable mode found, leave as found"); 883 } 884 } 885 886 /** 887 * Data element holding the 'model' element representing the decoder type. 888 */ 889 Element modelElem = null; 890 891 Element decoderRoot = null; 892 893 protected void loadDecoderFromLoco(RosterEntry r) { 894 // get a DecoderFile from the locomotive xml 895 String decoderModel = r.getDecoderModel(); 896 String decoderFamily = r.getDecoderFamily(); 897 log.debug("selected loco uses decoder {} {}", decoderFamily, decoderModel); 898 899 // locate a decoder like that. 900 List<DecoderFile> l = InstanceManager.getDefault(DecoderIndexFile.class).matchingDecoderList(null, decoderFamily, null, null, null, decoderModel); 901 log.debug("found {} matches", l.size()); 902 if (l.size() == 0) { 903 log.debug("Loco uses {} {} decoder, but no such decoder defined", decoderFamily, decoderModel); 904 // fall back to use just the decoder name, not family 905 l = InstanceManager.getDefault(DecoderIndexFile.class).matchingDecoderList(null, null, null, null, null, decoderModel); 906 if (log.isDebugEnabled()) { 907 log.debug("found {} matches without family key", l.size()); 908 } 909 } 910 if (l.size() > 0) { 911 DecoderFile d = l.get(0); 912 loadDecoderFile(d, r); 913 } else { 914 if (decoderModel.equals("")) { 915 log.debug("blank decoderModel requested, so nothing loaded"); 916 } else { 917 log.warn("no matching \"{}\" decoder found for loco, no decoder info loaded", decoderModel); 918 } 919 } 920 } 921 922 protected void loadDecoderFile(@Nonnull DecoderFile df, @Nonnull RosterEntry re) { 923 if (log.isDebugEnabled()) { 924 log.debug("loadDecoderFile from {} {}", DecoderFile.fileLocation, df.getFileName()); 925 } 926 927 try { 928 decoderRoot = df.rootFromName(DecoderFile.fileLocation + df.getFileName()); 929 } catch (org.jdom2.JDOMException e) { 930 log.error("Exception while parsing decoder XML file: {}", df.getFileName(), e); 931 return; 932 } catch (java.io.IOException e) { 933 log.error("Exception while reading decoder XML file: {}", df.getFileName(), e); 934 return; 935 } 936 // load variables from decoder tree 937 df.getProductID(); 938 df.loadVariableModel(decoderRoot.getChild("decoder"), variableModel); 939 940 // load reset from decoder tree 941 df.loadResetModel(decoderRoot.getChild("decoder"), resetModel); 942 943 // load extra menus from decoder tree 944 df.loadExtraMenuModel(decoderRoot.getChild("decoder"), extraMenuModelList, progStatus, mProgrammer); 945 946 // add extra menus 947 log.trace("add menus {} {}", extraMenuModelList.size(), extraMenuList); 948 for (int i=0; i < extraMenuModelList.size(); i++ ) { 949 String name = extraMenuModelList.get(i).getName(); 950 JMenu menu = new JMenu(name); 951 extraMenuList.add(i, menu); 952 menuBar.add(menu); 953 menu.add(new ExtraMenuAction(name, extraMenuModelList.get(i), this)); 954 menu.setEnabled(false); 955 } 956 957 // add Window and Help menu items (_after_ the extra menus) 958 addHelp(); 959 960 // load function names from family 961 re.loadFunctions(decoderRoot.getChild("decoder").getChild("family").getChild("functionlabels"), "family"); 962 963 // load sound names from family 964 re.loadSounds(decoderRoot.getChild("decoder").getChild("family").getChild("soundlabels"), "family"); 965 966 // get the showEmptyPanes attribute, if yes/no update our state 967 if (decoderRoot.getAttribute("showEmptyPanes") != null) { 968 log.debug("Found in decoder showEmptyPanes={}", decoderRoot.getAttribute("showEmptyPanes").getValue()); 969 decoderShowEmptyPanes = decoderRoot.getAttribute("showEmptyPanes").getValue(); 970 } else { 971 decoderShowEmptyPanes = ""; 972 } 973 log.debug("decoderShowEmptyPanes={}", decoderShowEmptyPanes); 974 975 // get the suppressFunctionLabels attribute, if yes/no update our state 976 if (decoderRoot.getAttribute("suppressFunctionLabels") != null) { 977 log.debug("Found in decoder suppressFunctionLabels={}", decoderRoot.getAttribute("suppressFunctionLabels").getValue()); 978 suppressFunctionLabels = decoderRoot.getAttribute("suppressFunctionLabels").getValue(); 979 } else { 980 suppressFunctionLabels = ""; 981 } 982 log.debug("suppressFunctionLabels={}", suppressFunctionLabels); 983 984 // get the suppressRosterMedia attribute, if yes/no update our state 985 if (decoderRoot.getAttribute("suppressRosterMedia") != null) { 986 log.debug("Found in decoder suppressRosterMedia={}", decoderRoot.getAttribute("suppressRosterMedia").getValue()); 987 suppressRosterMedia = decoderRoot.getAttribute("suppressRosterMedia").getValue(); 988 } else { 989 suppressRosterMedia = ""; 990 } 991 log.debug("suppressRosterMedia={}", suppressRosterMedia); 992 993 // get the allowResetDefaults attribute, if yes/no update our state 994 if (decoderRoot.getAttribute("allowResetDefaults") != null) { 995 log.debug("Found in decoder allowResetDefaults={}", decoderRoot.getAttribute("allowResetDefaults").getValue()); 996 decoderAllowResetDefaults = decoderRoot.getAttribute("allowResetDefaults").getValue(); 997 } else { 998 decoderAllowResetDefaults = "yes"; 999 } 1000 log.debug("decoderAllowResetDefaults={}", decoderAllowResetDefaults); 1001 1002 // save the pointer to the model element 1003 modelElem = df.getModelElement(); 1004 1005 // load function names from model 1006 re.loadFunctions(modelElem.getChild("functionlabels"), "model"); 1007 1008 // load sound names from model 1009 re.loadSounds(modelElem.getChild("soundlabels"), "model"); 1010 1011 // load maxFnNum from model 1012 Attribute a; 1013 if ((a = modelElem.getAttribute("maxFnNum")) != null) { 1014 maxFnNumOld = re.getMaxFnNum(); 1015 maxFnNumNew = a.getValue(); 1016 if (!maxFnNumOld.equals(maxFnNumNew)) { 1017 if (!re.getId().equals(Bundle.getMessage("LabelNewDecoder"))) { 1018 maxFnNumDirty = true; 1019 log.info("maxFnNum for \"{}\" changed from {} to {}", re.getId(), maxFnNumOld, maxFnNumNew); 1020 String message = java.text.MessageFormat.format( 1021 SymbolicProgBundle.getMessage("StatusMaxFnNumUpdated"), 1022 re.getDecoderFamily(), re.getDecoderModel(), maxFnNumNew); 1023 progStatus.setText(message); 1024 } 1025 re.setMaxFnNum(maxFnNumNew); 1026 } 1027 } 1028 } 1029 1030 protected void loadProgrammerFile(RosterEntry r) { 1031 // Open and parse programmer file 1032 XmlFile pf = new XmlFile() { 1033 }; // XmlFile is abstract 1034 try { 1035 programmerRoot = pf.rootFromName(filename); 1036 1037 // get the showEmptyPanes attribute, if yes/no update our state 1038 if (programmerRoot.getChild("programmer").getAttribute("showEmptyPanes") != null) { 1039 programmerShowEmptyPanes = programmerRoot.getChild("programmer").getAttribute("showEmptyPanes").getValue(); 1040 log.debug("Found in programmer {}", programmerShowEmptyPanes); 1041 } else { 1042 programmerShowEmptyPanes = ""; 1043 } 1044 1045 // get extra any panes from the programmer file 1046 Attribute a; 1047 if ((a = programmerRoot.getChild("programmer").getAttribute("decoderFilePanes")) != null 1048 && a.getValue().equals("yes")) { 1049 if (decoderRoot != null) { 1050 decoderPaneList = decoderRoot.getChildren("pane"); 1051 } 1052 } 1053 1054 // load programmer config from programmer tree 1055 readConfig(programmerRoot, r); 1056 1057 } catch (org.jdom2.JDOMException e) { 1058 log.error("exception parsing programmer file: {}", filename, e); 1059 } catch (java.io.IOException e) { 1060 log.error("exception reading programmer file: {}", filename, e); 1061 } 1062 } 1063 1064 Element programmerRoot = null; 1065 1066 /** 1067 * @return true if decoder needs to be written 1068 */ 1069 protected boolean checkDirtyDecoder() { 1070 if (log.isDebugEnabled()) { 1071 log.debug("Checking decoder dirty status. CV: {} variables:{}", cvModel.decoderDirty(), variableModel.decoderDirty()); 1072 } 1073 return (getModePane() != null && (cvModel.decoderDirty() || variableModel.decoderDirty())); 1074 } 1075 1076 /** 1077 * @return true if file needs to be written 1078 */ 1079 protected boolean checkDirtyFile() { 1080 return (variableModel.fileDirty() || _rPane.guiChanged(_rosterEntry) || _flPane.guiChanged(_rosterEntry) || _rMPane.guiChanged(_rosterEntry) || maxFnNumDirty); 1081 } 1082 1083 protected void handleDirtyFile() { 1084 } 1085 1086 /** 1087 * Close box has been clicked; handle check for dirty with respect to 1088 * decoder or file, then close. 1089 * 1090 * @param e Not used 1091 */ 1092 @Override 1093 public void windowClosing(java.awt.event.WindowEvent e) { 1094 1095 // Don't want to actually close if we return early 1096 setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 1097 1098 // check for various types of dirty - first table data not written back 1099 if (log.isDebugEnabled()) { 1100 log.debug("Checking decoder dirty status. CV: {} variables:{}", cvModel.decoderDirty(), variableModel.decoderDirty()); 1101 } 1102 if (!noDecoder && checkDirtyDecoder()) { 1103 if (JmriJOptionPane.showConfirmDialog(this, 1104 Bundle.getMessage("PromptCloseWindowNotWrittenDecoder"), 1105 Bundle.getMessage("PromptChooseOne"), 1106 JmriJOptionPane.OK_CANCEL_OPTION) != JmriJOptionPane.OK_OPTION) { 1107 return; 1108 } 1109 } 1110 if (checkDirtyFile()) { 1111 int option = JmriJOptionPane.showOptionDialog(this, Bundle.getMessage("PromptCloseWindowNotWrittenConfig"), 1112 Bundle.getMessage("PromptChooseOne"), 1113 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.WARNING_MESSAGE, null, 1114 new String[]{Bundle.getMessage("PromptSaveAndClose"), Bundle.getMessage("PromptClose"), Bundle.getMessage("ButtonCancel")}, 1115 Bundle.getMessage("PromptSaveAndClose")); 1116 if (option == 0) { // array position 0 PromptSaveAndClose 1117 // save requested 1118 if (!storeFile()) { 1119 return; // don't close if failed 1120 } 1121 } else if (option == 2 || option == JmriJOptionPane.CLOSED_OPTION ) { 1122 // cancel requested or Dialog closed 1123 return; // without doing anything 1124 } 1125 } 1126 if(maxFnNumDirty && !maxFnNumOld.equals("")){ 1127 _rosterEntry.setMaxFnNum(maxFnNumOld); 1128 } 1129 // Check for a "<new loco>" roster entry; if found, remove it 1130 List<RosterEntry> l = Roster.getDefault().matchingList(null, null, null, null, null, null, Bundle.getMessage("LabelNewDecoder")); 1131 if (l.size() > 0 && log.isDebugEnabled()) { 1132 log.debug("Removing {} <new loco> entries", l.size()); 1133 } 1134 int x = l.size() + 1; 1135 while (l.size() > 0) { 1136 Roster.getDefault().removeEntry(l.get(0)); 1137 l = Roster.getDefault().matchingList(null, null, null, null, null, null, Bundle.getMessage("LabelNewDecoder")); 1138 x--; 1139 if (x == 0) { 1140 log.error("We have tried to remove all the entries, however an error has occurred which has resulted in the entries not being deleted correctly"); 1141 l = new ArrayList<>(); 1142 } 1143 } 1144 1145 // OK, continue close 1146 setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); 1147 1148 // deregister shutdown hooks 1149 jmri.InstanceManager.getDefault(jmri.ShutDownManager.class).deregister(decoderDirtyTask); 1150 decoderDirtyTask = null; 1151 jmri.InstanceManager.getDefault(jmri.ShutDownManager.class).deregister(fileDirtyTask); 1152 fileDirtyTask = null; 1153 1154 // do the close itself 1155 super.windowClosing(e); 1156 } 1157 1158 void readConfig(Element root, RosterEntry r) { 1159 // check for "programmer" element at start 1160 Element base; 1161 if ((base = root.getChild("programmer")) == null) { 1162 log.error("xml file top element is not programmer"); 1163 return; 1164 } 1165 1166 // add the Info tab 1167 if (root.getChild("programmer").getAttribute("showRosterPane") != null) { 1168 if (root.getChild("programmer").getAttribute("showRosterPane").getValue().equals("no")) { 1169 makeInfoPane(r); 1170 } else { 1171 tabPane.addTab(Bundle.getMessage("ROSTER ENTRY"), makeInfoPane(r)); 1172 } 1173 } else { 1174 tabPane.addTab(Bundle.getMessage("ROSTER ENTRY"), makeInfoPane(r)); 1175 } 1176 1177 // add the Function Label tab 1178 if (root.getChild("programmer").getAttribute("showFnLanelPane").getValue().equals("yes") 1179 && !suppressFunctionLabels.equals("yes") 1180 ) { 1181 tabPane.addTab(Bundle.getMessage("FUNCTION LABELS"), makeFunctionLabelPane(r)); 1182 } else { 1183 // make it, just don't make it visible 1184 makeFunctionLabelPane(r); 1185 } 1186 1187 // add the Media tab 1188 if (root.getChild("programmer").getAttribute("showRosterMediaPane").getValue().equals("yes") 1189 && !suppressRosterMedia.equals("yes") 1190 ) { 1191 tabPane.addTab(Bundle.getMessage("ROSTER MEDIA"), makeMediaPane(r)); 1192 } else { 1193 // create it, just don't make it visible 1194 makeMediaPane(r); 1195 } 1196 1197 // add the comment tab 1198 JPanel commentTab = new JPanel(); 1199 var comment = new JTextArea(_rPane.getCommentDocument()); 1200 JScrollPane commentScroller = new JScrollPane(comment, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 1201 commentTab.add(commentScroller); 1202 commentTab.setLayout(new BoxLayout(commentTab, BoxLayout.Y_AXIS)); 1203 tabPane.addTab(Bundle.getMessage("COMMENT PANE"), commentTab); 1204 1205 // for all "pane" elements in the programmer 1206 List<Element> progPaneList = base.getChildren("pane"); 1207 if (log.isDebugEnabled()) { 1208 log.debug("will process {} pane definitions", progPaneList.size()); 1209 } 1210 for (Element temp : progPaneList) { 1211 // load each programmer pane 1212 List<Element> pnames = temp.getChildren("name"); 1213 boolean isProgPane = true; 1214 if ((pnames.size() > 0) && (decoderPaneList != null) && (decoderPaneList.size() > 0)) { 1215 String namePrimary = (pnames.get(0)).getValue(); // get non-localised name 1216 1217 // check if there is a same-name pane in decoder file 1218 // start at end to prevent concurrentmodification exception on remove 1219 for (int j = decoderPaneList.size() - 1; j >= 0; j--) { 1220 List<Element> dnames = decoderPaneList.get(j).getChildren("name"); 1221 if (dnames.size() > 0) { 1222 String namePrimaryDecoder = (dnames.get(0)).getValue(); // get non-localised name 1223 if (namePrimary.equals(namePrimaryDecoder)) { 1224 // replace programmer pane with same-name decoder pane 1225 temp = decoderPaneList.get(j); 1226 decoderPaneList.remove(j); // safe, not suspicious as we work end - front 1227 isProgPane = false; 1228 } 1229 } 1230 } 1231 } 1232 String name = jmri.util.jdom.LocaleSelector.getAttribute(temp, "name"); 1233 1234 // handle include/exclude 1235 if (isIncludedFE(temp, modelElem, _rosterEntry, "", "")) { 1236 newPane(name, temp, modelElem, false, isProgPane); // don't force showing if empty 1237 log.debug("readConfig - pane {} added", name); // these are also in RosterPrint 1238 } 1239 } 1240 } 1241 1242 /** 1243 * Reset all CV values to defaults stored earlier. 1244 * <p> 1245 * This will in turn update the variables. 1246 */ 1247 protected void resetToDefaults() { 1248 int n = defaultCvValues.length; 1249 for (int i = 0; i < n; i++) { 1250 CvValue cv = cvModel.getCvByNumber(defaultCvNumbers[i]); 1251 if (cv == null) { 1252 log.warn("Trying to set default in CV {} but didn't find the CV object", defaultCvNumbers[i]); 1253 } else { 1254 cv.setValue(defaultCvValues[i]); 1255 } 1256 } 1257 } 1258 1259 int[] defaultCvValues = null; 1260 String[] defaultCvNumbers = null; 1261 1262 /** 1263 * Save all CV values. 1264 * <p> 1265 * These stored values are used by {link #resetToDefaults()} 1266 */ 1267 protected void saveDefaults() { 1268 int n = cvModel.getRowCount(); 1269 defaultCvValues = new int[n]; 1270 defaultCvNumbers = new String[n]; 1271 1272 for (int i = 0; i < n; i++) { 1273 CvValue cv = cvModel.getCvByRow(i); 1274 defaultCvValues[i] = cv.getValue(); 1275 defaultCvNumbers[i] = cv.number(); 1276 } 1277 } 1278 1279 protected JPanel makeInfoPane(RosterEntry r) { 1280 // create the identification pane (not configured by programmer file now; maybe later?) 1281 1282 JPanel outer = new JPanel(); 1283 outer.setLayout(new BoxLayout(outer, BoxLayout.Y_AXIS)); 1284 JPanel body = new JPanel(); 1285 body.setLayout(new BoxLayout(body, BoxLayout.Y_AXIS)); 1286 JScrollPane scrollPane = new JScrollPane(body); 1287 1288 // add roster info 1289 _rPane = new RosterEntryPane(r); 1290 _rPane.setMaximumSize(_rPane.getPreferredSize()); 1291 body.add(_rPane); 1292 1293 // add the store button 1294 JButton store = new JButton(Bundle.getMessage("ButtonSave")); 1295 store.setAlignmentX(JLabel.CENTER_ALIGNMENT); 1296 store.addActionListener(e -> storeFile()); 1297 1298 // add the reset button 1299 JButton reset = new JButton(Bundle.getMessage("ButtonResetDefaults")); 1300 reset.setAlignmentX(JLabel.CENTER_ALIGNMENT); 1301 if (decoderAllowResetDefaults.equals("no")) { 1302 reset.setEnabled(false); 1303 reset.setToolTipText(Bundle.getMessage("TipButtonResetDefaultsDisabled")); 1304 } else { 1305 reset.setToolTipText(Bundle.getMessage("TipButtonResetDefaults")); 1306 reset.addActionListener(e -> resetToDefaults()); 1307 } 1308 1309 int sizeX = Math.max(reset.getPreferredSize().width, store.getPreferredSize().width); 1310 int sizeY = Math.max(reset.getPreferredSize().height, store.getPreferredSize().height); 1311 store.setPreferredSize(new Dimension(sizeX, sizeY)); 1312 reset.setPreferredSize(new Dimension(sizeX, sizeY)); 1313 1314 store.setToolTipText(_rosterEntry.getFileName()); 1315 1316 JPanel buttons = new JPanel(); 1317 buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS)); 1318 1319 buttons.add(store); 1320 buttons.add(reset); 1321 1322 body.add(buttons); 1323 outer.add(scrollPane); 1324 1325 // arrange for the dcc address to be updated 1326 java.beans.PropertyChangeListener dccNews = e -> updateDccAddress(); 1327 primaryAddr = variableModel.findVar("Short Address"); 1328 if (primaryAddr == null) { 1329 log.debug("DCC Address monitor didn't find a Short Address variable"); 1330 } else { 1331 primaryAddr.addPropertyChangeListener(dccNews); 1332 } 1333 extendAddr = variableModel.findVar("Long Address"); 1334 if (extendAddr == null) { 1335 log.debug("DCC Address monitor didn't find an Long Address variable"); 1336 } else { 1337 extendAddr.addPropertyChangeListener(dccNews); 1338 } 1339 addMode = (EnumVariableValue) variableModel.findVar("Address Format"); 1340 if (addMode == null) { 1341 log.debug("DCC Address monitor didn't find an Address Format variable"); 1342 } else { 1343 addMode.addPropertyChangeListener(dccNews); 1344 } 1345 1346 // get right address to start 1347 updateDccAddress(); 1348 1349 return outer; 1350 } 1351 1352 protected JPanel makeFunctionLabelPane(RosterEntry r) { 1353 // create the identification pane (not configured by programmer file now; maybe later?) 1354 1355 JPanel outer = new JPanel(); 1356 outer.setLayout(new BoxLayout(outer, BoxLayout.Y_AXIS)); 1357 JPanel body = new JPanel(); 1358 body.setLayout(new BoxLayout(body, BoxLayout.Y_AXIS)); 1359 JScrollPane scrollPane = new JScrollPane(body); 1360 1361 // add tab description 1362 JLabel title = new JLabel(Bundle.getMessage("UseThisTabCustomize")); 1363 title.setAlignmentX(JLabel.CENTER_ALIGNMENT); 1364 body.add(title); 1365 body.add(new JLabel(" ")); // some padding 1366 1367 // add roster info 1368 _flPane = new FunctionLabelPane(r); 1369 //_flPane.setMaximumSize(_flPane.getPreferredSize()); 1370 body.add(_flPane); 1371 1372 // add the store button 1373 JButton store = new JButton(Bundle.getMessage("ButtonSave")); 1374 store.setAlignmentX(JLabel.CENTER_ALIGNMENT); 1375 store.addActionListener(e -> storeFile()); 1376 1377 store.setToolTipText(_rosterEntry.getFileName()); 1378 1379 JPanel buttons = new JPanel(); 1380 buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS)); 1381 1382 buttons.add(store); 1383 1384 body.add(buttons); 1385 outer.add(scrollPane); 1386 return outer; 1387 } 1388 1389 protected JPanel makeMediaPane(RosterEntry r) { 1390 // create the identification pane (not configured by programmer file now; maybe later?) 1391 JPanel outer = new JPanel(); 1392 outer.setLayout(new BoxLayout(outer, BoxLayout.Y_AXIS)); 1393 JPanel body = new JPanel(); 1394 body.setLayout(new BoxLayout(body, BoxLayout.Y_AXIS)); 1395 JScrollPane scrollPane = new JScrollPane(body); 1396 1397 // add tab description 1398 JLabel title = new JLabel(Bundle.getMessage("UseThisTabMedia")); 1399 title.setAlignmentX(JLabel.CENTER_ALIGNMENT); 1400 body.add(title); 1401 body.add(new JLabel(" ")); // some padding 1402 1403 // add roster info 1404 _rMPane = new RosterMediaPane(r); 1405 _rMPane.setMaximumSize(_rMPane.getPreferredSize()); 1406 body.add(_rMPane); 1407 1408 // add the store button 1409 JButton store = new JButton(Bundle.getMessage("ButtonSave")); 1410 store.setAlignmentX(JLabel.CENTER_ALIGNMENT); 1411 store.addActionListener(e -> storeFile()); 1412 1413 JPanel buttons = new JPanel(); 1414 buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS)); 1415 1416 buttons.add(store); 1417 1418 body.add(buttons); 1419 outer.add(scrollPane); 1420 return outer; 1421 } 1422 1423 // hold refs to variables to check dccAddress 1424 VariableValue primaryAddr = null; 1425 VariableValue extendAddr = null; 1426 EnumVariableValue addMode = null; 1427 1428 boolean longMode = false; 1429 String newAddr = null; 1430 1431 void updateDccAddress() { 1432 1433 if (log.isDebugEnabled()) { 1434 log.debug("updateDccAddress: short {} long {} mode {}", primaryAddr == null ? "<null>" : primaryAddr.getValueString(), extendAddr == null ? "<null>" : extendAddr.getValueString(), addMode == null ? "<null>" : addMode.getValueString()); 1435 } 1436 1437 new DccAddressVarHandler(primaryAddr, extendAddr, addMode) { 1438 @Override 1439 protected void doPrimary() { 1440 // short address mode 1441 longMode = false; 1442 if (primaryAddr != null && !primaryAddr.getValueString().equals("")) { 1443 newAddr = primaryAddr.getValueString(); 1444 } 1445 } 1446 1447 @Override 1448 protected void doExtended() { 1449 // long address 1450 if (extendAddr != null && !extendAddr.getValueString().equals("")) { 1451 longMode = true; 1452 newAddr = extendAddr.getValueString(); 1453 } 1454 } 1455 }; 1456 // update if needed 1457 if (newAddr != null) { 1458 // store DCC address, type 1459 _rPane.setDccAddress(newAddr); 1460 _rPane.setDccAddressLong(longMode); 1461 } 1462 } 1463 1464 public void newPane(String name, Element pane, Element modelElem, boolean enableEmpty, boolean programmerPane) { 1465 if (log.isDebugEnabled()) { 1466 log.debug("newPane with enableEmpty {} showEmptyPanes {}", enableEmpty, isShowingEmptyPanes()); 1467 } 1468 // create a panel to hold columns 1469 PaneProgPane p = new PaneProgPane(this, name, pane, cvModel, variableModel, modelElem, _rosterEntry, programmerPane); 1470 p.setOpaque(true); 1471 if (noDecoder) { 1472 p.setNoDecoder(); 1473 cvModel.setNoDecoder(); 1474 } 1475 // how to handle the tab depends on whether it has contents and option setting 1476 int index; 1477 if (enableEmpty || !p.cvList.isEmpty() || !p.varList.isEmpty()) { 1478 tabPane.addTab(name, p); // always add if not empty 1479 index = tabPane.indexOfTab(name); 1480 tabPane.setToolTipTextAt(index, p.getToolTipText()); 1481 } else if (isShowingEmptyPanes()) { 1482 // here empty, but showing anyway as disabled 1483 tabPane.addTab(name, p); 1484 index = tabPane.indexOfTab(name); 1485 tabPane.setEnabledAt(index, true); // need to enable the pane so user can see message 1486 tabPane.setToolTipTextAt(index, 1487 Bundle.getMessage("TipTabEmptyNoCategory")); 1488 } else { 1489 // here not showing tab at all 1490 index = -1; 1491 } 1492 1493 // remember it for programming 1494 paneList.add(p); 1495 1496 // if visible, set qualifications 1497 if (index >= 0) { 1498 processModifierElements(pane, p, variableModel, tabPane, index); 1499 } 1500 } 1501 1502 /** 1503 * If there are any modifier elements, process them. 1504 * 1505 * @param e Process the contents of this element 1506 * @param pane Destination of any visible items 1507 * @param model Used to locate any needed variables 1508 * @param tabPane For overall GUI navigation 1509 * @param index Which pane in the overall window 1510 */ 1511 protected void processModifierElements(Element e, final PaneProgPane pane, VariableTableModel model, final JTabbedPane tabPane, final int index) { 1512 QualifierAdder qa = new QualifierAdder() { 1513 @Override 1514 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 1515 return new PaneQualifier(pane, var, Integer.parseInt(value), relation, tabPane, index); 1516 } 1517 1518 @Override 1519 protected void addListener(java.beans.PropertyChangeListener qc) { 1520 pane.addPropertyChangeListener(qc); 1521 } 1522 }; 1523 1524 qa.processModifierElements(e, model); 1525 } 1526 1527 @Override 1528 public BusyGlassPane getBusyGlassPane() { 1529 return glassPane; 1530 } 1531 1532 /** 1533 * Create a BusyGlassPane transparent layer over the panel blocking any 1534 * other interaction, excluding a supplied button. 1535 * 1536 * @param activeButton a button to put on top of the pane 1537 */ 1538 @Override 1539 public void prepGlassPane(AbstractButton activeButton) { 1540 List<Rectangle> rectangles = new ArrayList<>(); 1541 1542 if (glassPane != null) { 1543 glassPane.dispose(); 1544 } 1545 activeComponents.clear(); 1546 activeComponents.add(activeButton); 1547 if (activeButton == readChangesButton || activeButton == readAllButton 1548 || activeButton == writeChangesButton || activeButton == writeAllButton) { 1549 if (activeButton == readChangesButton) { 1550 for (JPanel jPanel : paneList) { 1551 assert jPanel instanceof PaneProgPane; 1552 activeComponents.add(((PaneProgPane) jPanel).readChangesButton); 1553 } 1554 } else if (activeButton == readAllButton) { 1555 for (JPanel jPanel : paneList) { 1556 assert jPanel instanceof PaneProgPane; 1557 activeComponents.add(((PaneProgPane) jPanel).readAllButton); 1558 } 1559 } else if (activeButton == writeChangesButton) { 1560 for (JPanel jPanel : paneList) { 1561 assert jPanel instanceof PaneProgPane; 1562 activeComponents.add(((PaneProgPane) jPanel).writeChangesButton); 1563 } 1564 } else { // (activeButton == writeAllButton) { 1565 for (JPanel jPanel : paneList) { 1566 assert jPanel instanceof PaneProgPane; 1567 activeComponents.add(((PaneProgPane) jPanel).writeAllButton); 1568 } 1569 } 1570 1571 for (int i = 0; i < tabPane.getTabCount(); i++) { 1572 rectangles.add(tabPane.getUI().getTabBounds(tabPane, i)); 1573 } 1574 } 1575 glassPane = new BusyGlassPane(activeComponents, rectangles, this.getContentPane(), this); 1576 this.setGlassPane(glassPane); 1577 } 1578 1579 @Override 1580 public void paneFinished() { 1581 log.debug("paneFinished with isBusy={}", isBusy()); 1582 if (!isBusy()) { 1583 if (glassPane != null) { 1584 glassPane.setVisible(false); 1585 glassPane.dispose(); 1586 glassPane = null; 1587 } 1588 setCursor(Cursor.getDefaultCursor()); 1589 enableButtons(true); 1590 } 1591 } 1592 1593 /** 1594 * Enable the read/write buttons. 1595 * <p> 1596 * In addition, if a programming mode pane is present, its "set" button is 1597 * enabled. 1598 * 1599 * @param stat Are reads possible? If false, so not enable the read buttons. 1600 */ 1601 @Override 1602 public void enableButtons(boolean stat) { 1603 log.debug("enableButtons({})", stat); 1604 if (noDecoder) { 1605 // If we don't have a decoder, no read or write is possible 1606 stat = false; 1607 } 1608 if (stat) { 1609 enableReadButtons(); 1610 } else { 1611 readChangesButton.setEnabled(false); 1612 readAllButton.setEnabled(false); 1613 } 1614 writeChangesButton.setEnabled(stat); 1615 writeAllButton.setEnabled(stat); 1616 1617 var tempModePane = getModePane(); 1618 if (tempModePane != null) { 1619 tempModePane.setEnabled(stat); 1620 } 1621 } 1622 1623 boolean justChanges; 1624 1625 @Override 1626 public boolean isBusy() { 1627 return _busy; 1628 } 1629 private boolean _busy = false; 1630 1631 private void setBusy(boolean stat) { 1632 log.debug("setBusy({})", stat); 1633 _busy = stat; 1634 1635 for (JPanel jPanel : paneList) { 1636 assert jPanel instanceof PaneProgPane; 1637 ((PaneProgPane) jPanel).enableButtons(!stat); 1638 } 1639 if (!stat) { 1640 paneFinished(); 1641 } 1642 } 1643 1644 /** 1645 * Invoked by "Read Changes" button, this sets in motion a continuing 1646 * sequence of "read changes" operations on the panes. 1647 * <p> 1648 * Each invocation of this method reads one pane; completion of that request 1649 * will cause it to happen again, reading the next pane, until there's 1650 * nothing left to read. 1651 * 1652 * @return true if a read has been started, false if the operation is 1653 * complete. 1654 */ 1655 public boolean readChanges() { 1656 log.debug("readChanges starts"); 1657 justChanges = true; 1658 for (JPanel jPanel : paneList) { 1659 assert jPanel instanceof PaneProgPane; 1660 ((PaneProgPane) jPanel).setToRead(justChanges, true); 1661 } 1662 setBusy(true); 1663 enableButtons(false); 1664 readChangesButton.setEnabled(true); 1665 glassPane.setVisible(true); 1666 paneListIndex = 0; 1667 // start operation 1668 return doRead(); 1669 } 1670 1671 /** 1672 * Invoked by the "Read All" button, this sets in motion a continuing 1673 * sequence of "read all" operations on the panes. 1674 * <p> 1675 * Each invocation of this method reads one pane; completion of that request 1676 * will cause it to happen again, reading the next pane, until there's 1677 * nothing left to read. 1678 * 1679 * @return true if a read has been started, false if the operation is 1680 * complete. 1681 */ 1682 public boolean readAll() { 1683 log.debug("readAll starts"); 1684 justChanges = false; 1685 for (JPanel jPanel : paneList) { 1686 assert jPanel instanceof PaneProgPane; 1687 ((PaneProgPane) jPanel).setToRead(justChanges, true); 1688 } 1689 setBusy(true); 1690 enableButtons(false); 1691 readAllButton.setEnabled(true); 1692 glassPane.setVisible(true); 1693 paneListIndex = 0; 1694 // start operation 1695 return doRead(); 1696 } 1697 1698 boolean doRead() { 1699 _read = true; 1700 while (paneListIndex < paneList.size()) { 1701 log.debug("doRead on {}", paneListIndex); 1702 _programmingPane = (PaneProgPane) paneList.get(paneListIndex); 1703 // some programming operations are instant, so need to have listener registered at readPaneAll 1704 _programmingPane.addPropertyChangeListener(this); 1705 boolean running; 1706 if (justChanges) { 1707 running = _programmingPane.readPaneChanges(); 1708 } else { 1709 running = _programmingPane.readPaneAll(); 1710 } 1711 1712 paneListIndex++; 1713 1714 if (running) { 1715 // operation in progress, stop loop until called back 1716 log.debug("doRead expecting callback from readPane {}", paneListIndex); 1717 return true; 1718 } else { 1719 _programmingPane.removePropertyChangeListener(this); 1720 } 1721 } 1722 // nothing to program, end politely 1723 _programmingPane = null; 1724 enableButtons(true); 1725 setBusy(false); 1726 readChangesButton.setSelected(false); 1727 readAllButton.setSelected(false); 1728 log.debug("doRead found nothing to do"); 1729 return false; 1730 } 1731 1732 /** 1733 * Invoked by "Write All" button, this sets in motion a continuing sequence 1734 * of "write all" operations on each pane. Each invocation of this method 1735 * writes one pane; completion of that request will cause it to happen 1736 * again, writing the next pane, until there's nothing left to write. 1737 * 1738 * @return true if a write has been started, false if the operation is 1739 * complete. 1740 */ 1741 public boolean writeAll() { 1742 log.debug("writeAll starts"); 1743 justChanges = false; 1744 for (JPanel jPanel : paneList) { 1745 assert jPanel instanceof PaneProgPane; 1746 ((PaneProgPane) jPanel).setToWrite(justChanges, true); 1747 } 1748 setBusy(true); 1749 enableButtons(false); 1750 writeAllButton.setEnabled(true); 1751 glassPane.setVisible(true); 1752 paneListIndex = 0; 1753 return doWrite(); 1754 } 1755 1756 /** 1757 * Invoked by "Write Changes" button, this sets in motion a continuing 1758 * sequence of "write changes" operations on each pane. 1759 * <p> 1760 * Each invocation of this method writes one pane; completion of that 1761 * request will cause it to happen again, writing the next pane, until 1762 * there's nothing left to write. 1763 * 1764 * @return true if a write has been started, false if the operation is 1765 * complete 1766 */ 1767 public boolean writeChanges() { 1768 log.debug("writeChanges starts"); 1769 justChanges = true; 1770 for (JPanel jPanel : paneList) { 1771 assert jPanel instanceof PaneProgPane; 1772 ((PaneProgPane) jPanel).setToWrite(justChanges, true); 1773 } 1774 setBusy(true); 1775 enableButtons(false); 1776 writeChangesButton.setEnabled(true); 1777 glassPane.setVisible(true); 1778 paneListIndex = 0; 1779 return doWrite(); 1780 } 1781 1782 boolean doWrite() { 1783 _read = false; 1784 while (paneListIndex < paneList.size()) { 1785 log.debug("doWrite starts on {}", paneListIndex); 1786 _programmingPane = (PaneProgPane) paneList.get(paneListIndex); 1787 // some programming operations are instant, so need to have listener registered at readPane 1788 _programmingPane.addPropertyChangeListener(this); 1789 boolean running; 1790 if (justChanges) { 1791 running = _programmingPane.writePaneChanges(); 1792 } else { 1793 running = _programmingPane.writePaneAll(); 1794 } 1795 1796 paneListIndex++; 1797 1798 if (running) { 1799 // operation in progress, stop loop until called back 1800 log.debug("doWrite expecting callback from writePane {}", paneListIndex); 1801 return true; 1802 } else { 1803 _programmingPane.removePropertyChangeListener(this); 1804 } 1805 } 1806 // nothing to program, end politely 1807 _programmingPane = null; 1808 enableButtons(true); 1809 setBusy(false); 1810 writeChangesButton.setSelected(false); 1811 writeAllButton.setSelected(false); 1812 log.debug("doWrite found nothing to do"); 1813 return false; 1814 } 1815 1816 /** 1817 * Prepare a roster entry to be printed, and display a selection list. 1818 * 1819 * @see jmri.jmrit.roster.PrintRosterEntry#doPrintPanes(boolean) 1820 * @param preview true if output should go to a Preview pane on screen, 1821 * false to output to a printer (dialog) 1822 */ 1823 public void printPanes(final boolean preview) { 1824 PrintRosterEntry pre = new PrintRosterEntry(_rosterEntry, paneList, _flPane, _rMPane, this); 1825 pre.printPanes(preview); 1826 } 1827 1828 boolean _read = true; 1829 PaneProgPane _programmingPane = null; 1830 1831 /** 1832 * Get notification of a variable property change in the pane, specifically 1833 * "busy" going to false at the end of a programming operation. 1834 * 1835 * @param e Event, used to find source 1836 */ 1837 @Override 1838 public void propertyChange(java.beans.PropertyChangeEvent e) { 1839 // check for the right event 1840 if (_programmingPane == null) { 1841 log.warn("unexpected propertyChange: {}", e); 1842 return; 1843 } else if (log.isDebugEnabled()) { 1844 log.debug("property changed: {} new value: {}", e.getPropertyName(), e.getNewValue()); 1845 } 1846 log.debug("check valid: {} {} {}", e.getSource() == _programmingPane, !e.getPropertyName().equals("Busy"), e.getNewValue().equals(Boolean.FALSE)); 1847 if (e.getSource() == _programmingPane 1848 && e.getPropertyName().equals("Busy") 1849 && e.getNewValue().equals(Boolean.FALSE)) { 1850 1851 log.debug("end of a programming pane operation, remove"); 1852 // remove existing listener 1853 _programmingPane.removePropertyChangeListener(this); 1854 _programmingPane = null; 1855 // restart the operation 1856 if (_read && readChangesButton.isSelected()) { 1857 log.debug("restart readChanges"); 1858 doRead(); 1859 } else if (_read && readAllButton.isSelected()) { 1860 log.debug("restart readAll"); 1861 doRead(); 1862 } else if (writeChangesButton.isSelected()) { 1863 log.debug("restart writeChanges"); 1864 doWrite(); 1865 } else if (writeAllButton.isSelected()) { 1866 log.debug("restart writeAll"); 1867 doWrite(); 1868 } else { 1869 log.debug("read/write end because button is lifted"); 1870 setBusy(false); 1871 } 1872 } 1873 } 1874 1875 /** 1876 * Store the locomotives information in the roster (and a RosterEntry file). 1877 * 1878 * @return false if store failed 1879 */ 1880 public boolean storeFile() { 1881 log.debug("storeFile starts"); 1882 1883 if (_rPane.checkDuplicate()) { 1884 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ErrorDuplicateID")); 1885 return false; 1886 } 1887 1888 // reload the RosterEntry 1889 updateDccAddress(); 1890 _rPane.update(_rosterEntry); 1891 _flPane.update(_rosterEntry); 1892 _rMPane.update(_rosterEntry); 1893 1894 // id has to be set! 1895 if (_rosterEntry.getId().equals("") || _rosterEntry.getId().equals(Bundle.getMessage("LabelNewDecoder"))) { 1896 log.debug("storeFile without a filename; issued dialog"); 1897 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("PromptFillInID")); 1898 return false; 1899 } 1900 1901 // if there isn't a filename, store using the id 1902 _rosterEntry.ensureFilenameExists(); 1903 String filename = _rosterEntry.getFileName(); 1904 1905 // create the RosterEntry to its file 1906 _rosterEntry.writeFile(cvModel, variableModel); 1907 1908 // mark this as a success 1909 variableModel.setFileDirty(false); 1910 maxFnNumDirty = false; 1911 1912 // and store an updated roster file 1913 FileUtil.createDirectory(FileUtil.getUserFilesPath()); 1914 Roster.getDefault().writeRoster(); 1915 1916 // save date changed, update 1917 _rPane.updateGUI(_rosterEntry); 1918 1919 // show OK status 1920 progStatus.setText(java.text.MessageFormat.format( 1921 Bundle.getMessage("StateSaveOK"), filename)); 1922 return true; 1923 } 1924 1925 /** 1926 * Local dispose, which also invokes parent. Note that we remove the 1927 * components (removeAll) before taking those apart. 1928 */ 1929 @Override 1930 public void dispose() { 1931 log.debug("dispose local"); 1932 1933 // remove listeners (not much of a point, though) 1934 readChangesButton.removeItemListener(l1); 1935 writeChangesButton.removeItemListener(l2); 1936 readAllButton.removeItemListener(l3); 1937 writeAllButton.removeItemListener(l4); 1938 if (_programmingPane != null) { 1939 _programmingPane.removePropertyChangeListener(this); 1940 } 1941 1942 // dispose the list of panes 1943 //noinspection ForLoopReplaceableByForEach 1944 for (int i = 0; i < paneList.size(); i++) { 1945 PaneProgPane p = (PaneProgPane) paneList.get(i); 1946 tabPane.remove(p); 1947 p.dispose(); 1948 } 1949 paneList.clear(); 1950 1951 // dispose of things we owned, in order of dependence 1952 _rPane.dispose(); 1953 _flPane.dispose(); 1954 _rMPane.dispose(); 1955 variableModel.dispose(); 1956 cvModel.dispose(); 1957 if (_rosterEntry != null) { 1958 _rosterEntry.setOpen(false); 1959 } 1960 1961 // remove references to everything we remember 1962 progStatus = null; 1963 cvModel = null; 1964 variableModel = null; 1965 _rosterEntry = null; 1966 _rPane = null; 1967 _flPane = null; 1968 _rMPane = null; 1969 1970 paneList.clear(); 1971 paneList = null; 1972 _programmingPane = null; 1973 1974 tabPane = null; 1975 readChangesButton = null; 1976 writeChangesButton = null; 1977 readAllButton = null; 1978 writeAllButton = null; 1979 1980 log.debug("dispose superclass"); 1981 removeAll(); 1982 super.dispose(); 1983 } 1984 1985 /** 1986 * Set value of Preference option to show empty panes. 1987 * 1988 * @param yes true if empty panes should be shown 1989 */ 1990 public static void setShowEmptyPanes(boolean yes) { 1991 if (InstanceManager.getNullableDefault(ProgrammerConfigManager.class) != null) { 1992 InstanceManager.getDefault(ProgrammerConfigManager.class).setShowEmptyPanes(yes); 1993 } 1994 } 1995 1996 /** 1997 * Get value of Preference option to show empty panes. 1998 * 1999 * @return value from programmer config. manager, else true. 2000 */ 2001 public static boolean getShowEmptyPanes() { 2002 return InstanceManager.getNullableDefault(ProgrammerConfigManager.class) == null || 2003 InstanceManager.getDefault(ProgrammerConfigManager.class).isShowEmptyPanes(); 2004 } 2005 2006 /** 2007 * Get value of whether current item should show empty panes. 2008 */ 2009 private boolean isShowingEmptyPanes() { 2010 boolean temp = getShowEmptyPanes(); 2011 if (programmerShowEmptyPanes.equals("yes")) { 2012 temp = true; 2013 } else if (programmerShowEmptyPanes.equals("no")) { 2014 temp = false; 2015 } 2016 if (decoderShowEmptyPanes.equals("yes")) { 2017 temp = true; 2018 } else if (decoderShowEmptyPanes.equals("no")) { 2019 temp = false; 2020 } 2021 return temp; 2022 } 2023 2024 /** 2025 * Option to control appearance of CV numbers in tool tips. 2026 * 2027 * @param yes true is CV numbers should be shown 2028 */ 2029 public static void setShowCvNumbers(boolean yes) { 2030 if (InstanceManager.getNullableDefault(ProgrammerConfigManager.class) != null) { 2031 InstanceManager.getDefault(ProgrammerConfigManager.class).setShowCvNumbers(yes); 2032 } 2033 } 2034 2035 public static boolean getShowCvNumbers() { 2036 return InstanceManager.getNullableDefault(ProgrammerConfigManager.class) == null || 2037 InstanceManager.getDefault(ProgrammerConfigManager.class).isShowCvNumbers(); 2038 } 2039 2040 public static void setCanCacheDefault(boolean yes) { 2041 if (InstanceManager.getNullableDefault(ProgrammerConfigManager.class) != null) { 2042 InstanceManager.getDefault(ProgrammerConfigManager.class).setCanCacheDefault(yes); 2043 } 2044 } 2045 2046 public static boolean getCanCacheDefault() { 2047 return InstanceManager.getNullableDefault(ProgrammerConfigManager.class) == null || 2048 InstanceManager.getDefault(ProgrammerConfigManager.class).isCanCacheDefault(); 2049 } 2050 2051 public static void setDoConfirmRead(boolean yes) { 2052 if (InstanceManager.getNullableDefault(ProgrammerConfigManager.class) != null) { 2053 InstanceManager.getDefault(ProgrammerConfigManager.class).setDoConfirmRead(yes); 2054 } 2055 } 2056 2057 public static boolean getDoConfirmRead() { 2058 return InstanceManager.getNullableDefault(ProgrammerConfigManager.class) == null || 2059 InstanceManager.getDefault(ProgrammerConfigManager.class).isDoConfirmRead(); 2060 } 2061 2062 public RosterEntry getRosterEntry() { 2063 return _rosterEntry; 2064 } 2065 2066 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PaneProgFrame.class); 2067 2068}