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