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