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