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