001package jmri.jmrit.throttle; 002 003import java.awt.*; 004import java.beans.PropertyChangeEvent; 005import java.beans.PropertyChangeListener; 006import java.io.File; 007import java.util.ArrayList; 008import java.util.List; 009import java.util.Objects; 010 011import javax.annotation.CheckForNull; 012 013import javax.swing.JButton; 014import javax.swing.JComboBox; 015import javax.swing.JFrame; 016import javax.swing.JInternalFrame; 017import javax.swing.JPanel; 018import javax.swing.WindowConstants; 019 020import jmri.Consist; 021import jmri.ConsistManager; 022import jmri.DccLocoAddress; 023import jmri.DccThrottle; 024import jmri.InstanceManager; 025import jmri.LocoAddress; 026import jmri.Programmer; 027import jmri.ThrottleListener; 028import jmri.ThrottleManager; 029import jmri.jmrit.DccLocoAddressSelector; 030import jmri.jmrit.consisttool.ConsistComboBox; 031import jmri.jmrit.roster.Roster; 032import jmri.jmrit.roster.RosterEntry; 033import jmri.jmrit.roster.swing.RosterEntrySelectorPanel; 034import jmri.jmrit.symbolicprog.ProgDefault; 035import jmri.jmrit.symbolicprog.tabbedframe.PaneOpsProgFrame; 036import jmri.jmrix.nce.consist.NceConsistRoster; 037import jmri.jmrix.nce.consist.NceConsistRosterEntry; 038import jmri.util.swing.JmriJOptionPane; 039import jmri.util.swing.WrapLayout; 040 041import org.jdom2.Element; 042 043/** 044 * A JInternalFrame that provides a way for the user to enter a decoder address. 045 * This class also store AddressListeners and notifies them when the user enters 046 * a new address. 047 * 048 * @author glen Copyright (C) 2002 049 * @author Daniel Boudreau Copyright (C) 2008 (add consist feature) 050 * @author Lionel Jeanson 2009-2021 051 */ 052public class AddressPanel extends JInternalFrame implements ThrottleListener, PropertyChangeListener { 053 054 private final ThrottleManager throttleManager; 055 private final ConsistManager consistManager; 056 057 private DccThrottle throttle; 058 private DccThrottle consistThrottle; 059 060 private final DccLocoAddressSelector addrSelector = new DccLocoAddressSelector(); 061 private DccLocoAddress currentAddress; 062 private DccLocoAddress consistAddress; 063 private DccLocoAddress requestedAddress; 064 private ArrayList<AddressListener> listeners; 065 066 private JPanel mainPanel; 067 068 private JButton releaseButton; 069 private JButton dispatchButton; 070 private JButton progButton; 071 private JButton setButton; 072 private RosterEntrySelectorPanel rosterBox; 073 @SuppressWarnings("rawtypes") // TBD: once JMRI consists vs NCE consists resolved, can be removed 074 private JComboBox conRosterBox; 075 private boolean isUpdatingUI = false; 076 private boolean disableRosterBoxActions = false; 077 private RosterEntry rosterEntry; 078 079 /** 080 * Constructor 081 * @param throttleManager the throttle manager 082 */ 083 public AddressPanel(ThrottleManager throttleManager) { 084 this.throttleManager = throttleManager; 085 consistManager = InstanceManager.getNullableDefault(jmri.ConsistManager.class); 086 initGUI(); 087 applyPreferences(); 088 } 089 090 public void destroy() { // Handle disposing of the throttle 091 if (conRosterBox != null && conRosterBox instanceof ConsistComboBox) { 092 ((ConsistComboBox)conRosterBox).dispose(); 093 } 094 if ( requestedAddress != null ) { 095 throttleManager.cancelThrottleRequest(requestedAddress, this); 096 requestedAddress = null; 097 } 098 if (throttle != null) { 099 throttle.removePropertyChangeListener(this); 100 throttleManager.releaseThrottle(throttle, this); 101 notifyListenersOfThrottleRelease(); 102 throttle = null; 103 } 104 if (consistThrottle != null) { 105 consistThrottle.removePropertyChangeListener(this); 106 throttleManager.releaseThrottle(consistThrottle, this); 107 notifyListenersOfThrottleRelease(); 108 consistThrottle = null; 109 } 110 } 111 112 /** 113 * Add an AddressListener. 114 * AddressListeners are notified when the user 115 * selects a new address and when a Throttle is acquired for that address 116 * @param l listener to add. 117 * 118 */ 119 public void addAddressListener(AddressListener l) { 120 if (listeners == null) { 121 listeners = new ArrayList<>(); 122 } 123 if (!listeners.contains(l)) { 124 listeners.add(l); 125 } 126 } 127 128 /** 129 * Remove an AddressListener. 130 * 131 * @param l listener to remove. 132 */ 133 public void removeAddressListener(AddressListener l) { 134 if (listeners == null) { 135 return; 136 } 137 listeners.remove(l); 138 } 139 140 /** 141 * Gets the selected index of the roster combo box. Implemented to support 142 * xboxThrottle.py 143 * 144 * @return the selected index of the roster combo box 145 */ 146 public int getRosterSelectedIndex() { 147 return getRosterEntrySelector().getRosterEntryComboBox().getSelectedIndex(); 148 } 149 150 /** 151 * Sets the selected index of the roster combo box. 152 * This method temporarily disables roster box actions so it 153 * can change the selected index without triggering a cascade of events. 154 * selectRosterEntry() as to be called afterward to actually select the roster entry 155 * 156 * @param index the index to select in the combo box 157 */ 158 public void setRosterSelectedIndex(int index) { 159 if (getRosterEntrySelector().isEnabled() && index >= 0 && index < getRosterEntrySelector().getRosterEntryComboBox().getItemCount()) { 160 disableRosterBoxActions = true; //Temporarily disable roster box actions 161 getRosterEntrySelector().getRosterEntryComboBox().setSelectedIndex(index); 162 disableRosterBoxActions = false; 163 } 164 if ((backgroundPanel != null) && (rosterBox.getSelectedRosterEntries().length > 0)) { 165 backgroundPanel.setImagePath(null); 166 String rosterEntryTitle = getRosterEntrySelector().getSelectedRosterEntries()[0].titleString(); 167 RosterEntry re = Roster.getDefault().entryFromTitle(rosterEntryTitle); 168 if (re != null) { 169 backgroundPanel.setImagePath(re.getImagePath()); 170 } 171 } 172 } 173 174 private BackgroundPanel backgroundPanel; 175 176 public void setBackgroundPanel(BackgroundPanel bp) { 177 backgroundPanel = bp; 178 } 179 180 /** 181 * "Sets" the current roster entry. Equivalent to the user pressing the 182 * "Set" button. Implemented to support xboxThrottle.py 183 */ 184 public void selectRosterEntry() { 185 if (isUpdatingUI || disableRosterBoxActions) { 186 return; 187 } 188 if (getRosterEntrySelector().getSelectedRosterEntries().length != 0) { 189 setRosterEntry(getRosterEntrySelector().getSelectedRosterEntries()[0]); 190 consistAddress = null; 191 } 192 } 193 194 /** 195 * Get notification that a throttle has been found as we requested. 196 * 197 * @param t An instantiation of the DccThrottle with the address requested. 198 */ 199 @Override 200 public void notifyThrottleFound(DccThrottle t) { 201 log.debug("Throttle found : {} ", t.getLocoAddress()); 202 // is this a consist address? 203 if (consistAddress == null && consistManager != null && consistManager.isEnabled() && consistManager.getConsistList().contains(t.getLocoAddress())) { 204 // we found a consist with this address, this is a consist 205 consistAddress = (DccLocoAddress) t.getLocoAddress(); 206 } 207 if (consistAddress != null && t.getLocoAddress().equals(consistAddress)) { 208 // notify the listeners that a throttle was found 209 // for the consist address. 210 log.debug("notifying that this is a consist"); 211 notifyConsistThrottleFound(t); 212 return; 213 } 214 if (t.getLocoAddress().getNumber() != currentAddress.getNumber()) { 215 log.warn("Not correct address, asked for {} got {}, requesting again...", currentAddress.getNumber(), t.getLocoAddress()); 216 boolean requestOK 217 = throttleManager.requestThrottle(currentAddress, this, true); 218 if (!requestOK) { 219 JmriJOptionPane.showMessageDialog(mainPanel, Bundle.getMessage("AddressInUse")); 220 requestedAddress = null; 221 } 222 return; 223 } 224 225 requestedAddress = null; 226 currentAddress = (DccLocoAddress) t.getLocoAddress(); 227 // can we find a roster entry? 228 if ((rosterEntry == null) 229 && (InstanceManager.getDefault(ThrottlesPreferences.class).isUsingExThrottle()) 230 && (InstanceManager.getDefault(ThrottlesPreferences.class).isEnablingRosterSearch()) 231 && currentAddress != null) { 232 List<RosterEntry> l = Roster.getDefault().matchingList(null, null, "" + currentAddress.getNumber(), null, null, null, null); 233 if (!l.isEmpty()) { 234 rosterEntry = l.get(0); 235 } 236 } 237 238 if (consistAddress != null) { 239 // if we get there, it means we got the throttle for the head locomotive of a consist 240 // only update the function panel 241 log.debug("Advanced consist throttle, got the throttle for the head locomotive functions control"); 242 (new ArrayList<AddressListener>(listeners)).forEach((l) -> { 243 if (l instanceof FunctionPanel) { 244 l.notifyAddressThrottleFound(t); 245 } 246 }); 247 return; 248 } 249 250 if (throttle != null) { 251 log.debug("notifyThrottleFound() throttle non null, called for loc {}",t.getLocoAddress()); 252 return; 253 } 254 255 throttle = t; 256 throttle.addPropertyChangeListener(this); 257 258 // update GUI 259 updateGUIOnThrottleFound(true); 260 261 // send notification of new address 262 // work on a copy because some new listeners may be added while notifying the existing ones 263 (new ArrayList<AddressListener>(listeners)).forEach((l) -> { 264 // log.debug("Notify address listener of address change {}", l.getClass()); 265 l.notifyAddressThrottleFound(t); 266 }); 267 } 268 269 @Override 270 public void notifyFailedThrottleRequest(LocoAddress address, String reason) { 271 JmriJOptionPane.showMessageDialog(null, reason, Bundle.getMessage("FailedSetupRequestTitle"), JmriJOptionPane.WARNING_MESSAGE); 272 } 273 274 /** 275 * A decision is required for Throttle creation to continue. 276 * <p> 277 * Steal / Cancel, Share / Cancel, or Steal / Share Cancel 278 */ 279 @Override 280 public void notifyDecisionRequired(LocoAddress address, DecisionType question) { 281 if ( null != question ) { 282 switch (question) { 283 case STEAL: 284 if (InstanceManager.getDefault(ThrottlesPreferences.class).isSilentSteal() ){ 285 throttleManager.responseThrottleDecision(address, this, DecisionType.STEAL ); 286 return; 287 } 288 jmri.util.ThreadingUtil.runOnGUI(() -> { 289 if ( JmriJOptionPane.YES_OPTION == JmriJOptionPane.showConfirmDialog( 290 this, Bundle.getMessage("StealQuestionText",address.toString()), 291 Bundle.getMessage("StealRequestTitle"), JmriJOptionPane.YES_NO_OPTION)) { 292 throttleManager.responseThrottleDecision(address, this, DecisionType.STEAL ); 293 } else { 294 throttleManager.cancelThrottleRequest(address, this); 295 requestedAddress = null; 296 } 297 }); 298 break; 299 case SHARE: 300 if (InstanceManager.getDefault(ThrottlesPreferences.class).isSilentShare() ){ 301 throttleManager.responseThrottleDecision(address, this, DecisionType.SHARE ); 302 return; 303 } 304 jmri.util.ThreadingUtil.runOnGUI(() -> { 305 if ( JmriJOptionPane.YES_OPTION == JmriJOptionPane.showConfirmDialog( 306 this, Bundle.getMessage("ShareQuestionText",address.toString()), 307 Bundle.getMessage("ShareRequestTitle"), JmriJOptionPane.YES_NO_OPTION)) { 308 throttleManager.responseThrottleDecision(address, this, DecisionType.SHARE ); 309 } else { 310 throttleManager.cancelThrottleRequest(address, this); 311 requestedAddress = null; 312 } 313 }); 314 break; 315 case STEAL_OR_SHARE: 316 if ( InstanceManager.getDefault(ThrottlesPreferences.class).isSilentSteal() ){ 317 throttleManager.responseThrottleDecision(address, this, DecisionType.STEAL ); 318 return; 319 } 320 if ( InstanceManager.getDefault(ThrottlesPreferences.class).isSilentShare() ){ 321 throttleManager.responseThrottleDecision(address, this, DecisionType.SHARE ); 322 return; 323 } 324 String[] options = new String[] {Bundle.getMessage("StealButton"), Bundle.getMessage("ShareButton"), Bundle.getMessage("ButtonCancel")}; 325 jmri.util.ThreadingUtil.runOnGUI(() -> { 326 int response = JmriJOptionPane.showOptionDialog(AddressPanel.this, 327 Bundle.getMessage("StealShareQuestionText",address.toString()), Bundle.getMessage("StealShareRequestTitle"), 328 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, options, options[1]); 329 switch (response) { 330 case 0: 331 log.debug("steal clicked"); 332 throttleManager.responseThrottleDecision(address, AddressPanel.this, DecisionType.STEAL); 333 break; 334 case 1: 335 log.debug("share clicked"); 336 throttleManager.responseThrottleDecision(address, AddressPanel.this, DecisionType.SHARE); 337 break; 338 default: 339 log.debug("cancel clicked"); 340 throttleManager.cancelThrottleRequest(address, AddressPanel.this); 341 requestedAddress = null; 342 break; 343 } 344 }); 345 break; 346 default: 347 break; 348 } 349 } 350 } 351 352 /** 353 * Get notification that a consist throttle has been found as we requested. 354 * 355 * @param t An instantiation of the DccThrottle with the address requested. 356 */ 357 public void notifyConsistThrottleFound(DccThrottle t) { 358 if (consistThrottle != null) { 359 log.debug("notifyConsistThrottleFound() consistThrottle non null, called for loc {}",t.getLocoAddress()); 360 return; 361 } 362 requestedAddress = null; 363 consistThrottle = t; 364 currentAddress = (DccLocoAddress) t.getLocoAddress(); 365 consistThrottle.addPropertyChangeListener(this); 366 367 Consist consist = getConsistEntry(); 368 if (consist != null && consist.getConsistType() == Consist.CS_CONSIST) { 369 // CS Consist, consist has the head locomotive id 370 // can we find a roster entry? 371 if ((rosterEntry == null) 372 && (InstanceManager.getDefault(ThrottlesPreferences.class).isUsingExThrottle()) 373 && (InstanceManager.getDefault(ThrottlesPreferences.class).isEnablingRosterSearch()) 374 && currentAddress != null) { 375 List<RosterEntry> l = Roster.getDefault().matchingList(null, null, "" + currentAddress.getNumber(), null, null, null, null); 376 if (!l.isEmpty()) { 377 rosterEntry = l.get(0); 378 } 379 } 380 } 381 382 updateGUIOnThrottleFound(true); 383 384 // send notification of new address 385 // work on a clone because some new listeners may be added while notifying the existing ones 386 (new ArrayList<AddressListener>(listeners)).forEach((l) -> { 387 l.notifyConsistAddressThrottleFound(t); 388 }); 389 390 if (consist != null && consist.getConsistType() == Consist.ADVANCED_CONSIST) { 391 // request a throttle for head locomotive for functions 392 DccLocoAddress headLocoAddress = consist.getConsistList().get(0); 393 // only if consist address is not head locomotive address 394 if (! headLocoAddress.equals(currentAddress)) { 395 log.debug("Advanced consist throttle, requesting secondary throttle for head locomotive function control."); 396 changeOfAddress(headLocoAddress); 397 } 398 } 399 } 400 401 private void updateGUIOnThrottleFound(boolean throttleActive) { 402 // update GUI 403 isUpdatingUI = true; 404 //addrSelector.setAddress(currentAddress); 405 setButton.setEnabled(!throttleActive); 406 addrSelector.setEnabled(!throttleActive); 407 releaseButton.setEnabled(throttleActive); 408 if (throttleActive && rosterEntry != null) { 409 getRosterEntrySelector().setSelectedRosterEntry(rosterEntry); 410 } else { 411 getRosterEntrySelector().getRosterEntryComboBox().setSelectedItem(Bundle.getMessage("NoLocoSelected")); 412 } 413 getRosterEntrySelector().setEnabled(!throttleActive); 414 if (conRosterBox != null) { 415 if (throttleActive && consistThrottle != null) { 416 conRosterBox.setSelectedItem(consistThrottle.getLocoAddress()); 417 } else { 418 conRosterBox.setSelectedItem(Bundle.getMessage("NoConsistSelected")); 419 } 420 conRosterBox.setEnabled(!throttleActive); 421 } 422 if (throttleManager.hasDispatchFunction()) { 423 dispatchButton.setEnabled(throttleActive); 424 } 425 // enable program button if programmer available 426 // for ops-mode programming 427 if ((rosterEntry != null) && (ProgDefault.getDefaultProgFile() != null) 428 && (InstanceManager.getNullableDefault(jmri.AddressedProgrammerManager.class) != null) 429 && (InstanceManager.getDefault(jmri.AddressedProgrammerManager.class).isAddressedModePossible())) { 430 progButton.setEnabled(true); 431 } else { 432 progButton.setEnabled(false); 433 } 434 isUpdatingUI = false; 435 } 436 437 /** 438 * Receive notification that an address has been release or dispatched. 439 */ 440 public void notifyThrottleDisposed() { 441 log.debug("notifyThrottleDisposed"); 442 notifyListenersOfThrottleRelease(); 443 updateGUIOnThrottleFound(false); 444 rosterEntry = null; 445 if (consistThrottle != null) { 446 consistThrottle.removePropertyChangeListener(this); 447 } 448 if (throttle != null) { 449 throttle.removePropertyChangeListener(this); 450 } 451 } 452 453 /** 454 * Get the RosterEntry if there's one for this throttle. 455 * 456 * @return RosterEntry or null 457 */ 458 public RosterEntry getRosterEntry() { 459 return rosterEntry; 460 } 461 462 /** 463 * Get the selected Consist if there's one for this throttle. 464 * 465 * @return Consist or null 466 */ 467 public Consist getConsistEntry() { 468 if (consistManager == null || consistAddress == null || !consistManager.isEnabled()) { 469 return null; 470 } 471 if (consistManager.getConsistList().contains(consistAddress)) { 472 return consistManager.getConsist(consistAddress); 473 } 474 return null; 475 } 476 477 /** 478 * Set the RosterEntry for this throttle and initiate a throttle request 479 * @param entry roster entry to set. 480 */ 481 public void setRosterEntry(RosterEntry entry) { 482 isUpdatingUI = true; 483 getRosterEntrySelector().setSelectedRosterEntry(entry); 484 addrSelector.setAddress(entry.getDccLocoAddress()); 485 isUpdatingUI = false; 486 rosterEntry = entry; 487 changeOfAddress(addrSelector.getAddress()); 488 } 489 490 /** 491 * Create, initialize and place the GUI objects. 492 */ 493 @SuppressWarnings("unchecked") //for the onRosterBox.insertItemAt(), to be a removed once NCE consists clarified 494 private void initGUI() { 495 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 496 mainPanel = new JPanel(); 497 mainPanel.setLayout(new BorderLayout()); 498 this.setContentPane(mainPanel); 499 500 // center: address input 501 addrSelector.setVariableSize(true); 502 mainPanel.add(addrSelector.getCombinedJPanel(), BorderLayout.CENTER); 503 addrSelector.getTextField().addActionListener(e -> { 504 if (isUpdatingUI) { 505 return; 506 } 507 consistAddress = null; 508 changeOfAddress(addrSelector.getAddress()); 509 }); 510 511 // top : roster and consists selectors 512 JPanel topPanel = new JPanel(); 513 topPanel.setLayout(new WrapLayout(FlowLayout.CENTER, 2, 2)); 514 515 rosterBox = new RosterEntrySelectorPanel(); 516 if ((InstanceManager.getDefault(ThrottlesPreferences.class).isUsingExThrottle()) && (InstanceManager.getDefault(ThrottlesPreferences.class).isAddressSelectorShowingAllRosterGroup())) { 517 rosterBox.getRosterGroupComboBox().setAllEntriesEnabled(true); 518 } 519 getRosterEntrySelector().setNonSelectedItem(Bundle.getMessage("NoLocoSelected")); 520 getRosterEntrySelector().setToolTipText(Bundle.getMessage("SelectLocoFromRosterTT")); 521 getRosterEntrySelector().addPropertyChangeListener("selectedRosterEntries", pce -> selectRosterEntry()); 522 getRosterEntrySelector().setLayout(new WrapLayout(FlowLayout.CENTER, 2, 2)); 523 topPanel.add(getRosterEntrySelector()); 524 525 if (InstanceManager.getDefault(NceConsistRoster.class).numEntries() > 0) { // NCE consists 526 // NCE implementation of consists is specific, TODO: refactor to use generic JMRI consists 527 conRosterBox = InstanceManager.getDefault(NceConsistRoster.class).fullRosterComboBox(); 528 conRosterBox.insertItemAt(Bundle.getMessage("NoConsistSelected"), 0); // empty entry 529 conRosterBox.setSelectedIndex(0); 530 conRosterBox.setToolTipText(Bundle.getMessage("SelectConsistFromRosterTT")); 531 conRosterBox.addActionListener(e -> nceConsistRosterSelected()); 532 topPanel.add(conRosterBox); 533 } else { 534 if ((consistManager != null) && (consistManager.isEnabled())) { // JMRI consists 535 JPanel consistPanel = new JPanel(); 536 JButton consistToolButton = new JButton(new jmri.jmrit.consisttool.ConsistToolAction()); 537 consistPanel.add(consistToolButton); 538 conRosterBox = new ConsistComboBox(); 539 conRosterBox.addActionListener(e -> jmriConsistRosterSelected()); 540 consistPanel.add(conRosterBox); 541 topPanel.add(consistPanel); 542 } 543 } 544 545 mainPanel.add(topPanel, BorderLayout.NORTH); 546 547 // bottom : buttons 548 JPanel buttonPanel = new JPanel(); 549 buttonPanel.setLayout(new WrapLayout(FlowLayout.CENTER, 2, 2)); 550 551 progButton = new JButton(Bundle.getMessage("ButtonProgram")); 552 buttonPanel.add(progButton); 553 progButton.setEnabled(false); 554 progButton.addActionListener(e -> openProgrammer()); 555 556 dispatchButton = new JButton(Bundle.getMessage("ButtonDispatch")); 557 buttonPanel.add(dispatchButton); 558 dispatchButton.setEnabled(false); 559 dispatchButton.addActionListener(e -> dispatchAddress()); 560 561 releaseButton = new JButton(Bundle.getMessage("ButtonRelease")); 562 buttonPanel.add(releaseButton); 563 releaseButton.setEnabled(false); 564 releaseButton.addActionListener(e -> releaseAddress()); 565 566 setButton = new JButton(Bundle.getMessage("ButtonSet")); 567 setButton.addActionListener(e -> { 568 consistAddress = null; 569 changeOfAddress(addrSelector.getAddress()); 570 }); 571 buttonPanel.add(setButton); 572 573 mainPanel.add(buttonPanel, BorderLayout.SOUTH); 574 575 pack(); 576 } 577 578 private void jmriConsistRosterSelected() { 579 if (isUpdatingUI) { 580 return; 581 } 582 if ((conRosterBox.getSelectedIndex() != 0) && (conRosterBox.getSelectedItem() instanceof DccLocoAddress)) { 583 consistAddress = (DccLocoAddress) conRosterBox.getSelectedItem() ; 584 changeOfConsistAddress(); 585 } 586 } 587 588 private void nceConsistRosterSelected() { 589 if (isUpdatingUI) { 590 return; 591 } 592 if (!(Objects.equals(conRosterBox.getSelectedItem(), Bundle.getMessage("NoConsistSelected")))) { 593 String rosterEntryTitle = Objects.requireNonNull(conRosterBox.getSelectedItem()).toString(); 594 NceConsistRosterEntry nceConsistRosterEntry = InstanceManager.getDefault(NceConsistRoster.class) 595 .entryFromTitle(rosterEntryTitle); 596 597 DccLocoAddress a = new DccLocoAddress(Integer.parseInt(nceConsistRosterEntry 598 .getLoco1DccAddress()), nceConsistRosterEntry.isLoco1LongAddress()); 599 addrSelector.setAddress(a); 600 consistAddress = null; 601 int cA = 0; 602 try { 603 cA = Integer.parseInt(nceConsistRosterEntry.getConsistNumber()); 604 } catch (NumberFormatException ignored) { 605 606 } 607 if (0 < cA && cA < 128) { 608 consistAddress = new DccLocoAddress(cA, false); 609 } else { 610 log.warn("consist number missing {}", nceConsistRosterEntry.getLoco1DccAddress()); 611 JmriJOptionPane.showMessageDialog(mainPanel, 612 Bundle.getMessage("ConsistNumberHasNotBeenAssigned"), 613 Bundle.getMessage("NeedsConsistNumber"), 614 JmriJOptionPane.ERROR_MESSAGE); 615 return; 616 } 617 if (JmriJOptionPane.showConfirmDialog(mainPanel, 618 Bundle.getMessage("SendFunctionToLead"), Bundle.getMessage("NCEconsistThrottle"), 619 JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) { 620 addrSelector.setAddress(consistAddress); 621 consistAddress = null; 622 } 623 changeOfAddress(addrSelector.getAddress()); 624 } 625 } 626 627 /** 628 * The user has selected a new address. Notify all listeners. 629 */ 630 private void changeOfAddress(DccLocoAddress a) { 631 currentAddress = a; 632 if (currentAddress == null) { 633 return; // no address 634 } 635 // send notification of new address 636 listeners.forEach((l) -> { 637 l.notifyAddressChosen(currentAddress); 638 }); 639 log.debug("Requesting new slot for address {} rosterEntry {}",currentAddress,rosterEntry); 640 boolean requestOK; 641 if (rosterEntry == null) { 642 requestedAddress = currentAddress; 643 requestOK = throttleManager.requestThrottle(currentAddress, this, true); 644 } 645 else { 646 requestedAddress = rosterEntry.getDccLocoAddress(); 647 requestOK = throttleManager.requestThrottle(rosterEntry, this, true); 648 } 649 if (!requestOK) { 650 requestedAddress = null; 651 JmriJOptionPane.showMessageDialog(mainPanel, Bundle.getMessage("AddressInUse")); 652 } 653 } 654 655 private void changeOfConsistAddress() { 656 if (consistAddress == null) { 657 return; // no address 658 } 659 addrSelector.setAddress(consistAddress); 660 // send notification of new address 661 listeners.forEach((l) -> { 662 l.notifyAddressChosen(currentAddress); 663 }); 664 log.debug("Requesting new slot for consist address {}",consistAddress); 665 requestedAddress = consistAddress; 666 boolean requestOK = throttleManager.requestThrottle(consistAddress, this, true); 667 if (!requestOK) { 668 requestedAddress = null; 669 JmriJOptionPane.showMessageDialog(mainPanel, Bundle.getMessage("AddressInUse")); 670 } 671 } 672 673 /** 674 * Open a programmer for this address 675 */ 676 protected void openProgrammer() { 677 if (rosterEntry == null) { 678 return; 679 } 680 681 java.util.ResourceBundle rbt = java.util.ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle"); 682 String ptitle = java.text.MessageFormat.format(rbt.getString("FrameOpsProgrammerTitle"), rosterEntry.getId()); 683 // find the ops-mode programmer 684 int address = Integer.parseInt(rosterEntry.getDccAddress()); 685 boolean longAddr = true; 686 if (address < 100) { 687 longAddr = false; 688 } 689 Programmer programmer = InstanceManager.getDefault(jmri.AddressedProgrammerManager.class).getAddressedProgrammer(longAddr, address); 690 // and created the frame 691 JFrame p = new PaneOpsProgFrame(null, rosterEntry, 692 ptitle, "programmers" + File.separator + ProgDefault.getDefaultProgFile() + ".xml", 693 programmer); 694 p.pack(); 695 p.setVisible(true); 696 } 697 698 /** 699 * Dispatch the current address for use by other throttles 700 */ 701 public void dispatchAddress() { 702 if (throttle != null) { 703 int usageCount = throttleManager.getThrottleUsageCount(throttle.getLocoAddress()) - 1; 704 if ( usageCount != 0 ) { 705 JmriJOptionPane.showMessageDialog(mainPanel, Bundle.getMessage("CannotDispatch", usageCount)); 706 return; 707 } 708 notifyThrottleDisposed(); 709 throttleManager.dispatchThrottle(throttle, this); 710 throttle = null; 711 } 712 } 713 714 /** 715 * Release the current address. 716 */ 717 public void releaseAddress() { 718 notifyThrottleDisposed(); 719 if (throttle != null) { 720 throttleManager.releaseThrottle(throttle, this); 721 throttle = null; 722 } 723 if (consistThrottle != null) { 724 throttleManager.releaseThrottle(consistThrottle, this); 725 consistThrottle = null; 726 } 727 } 728 729 private void notifyListenersOfThrottleRelease() { 730 if (listeners != null) { 731 listeners.forEach((l) -> { 732 // log.debug("Notify address listener {} of release", l.getClass()); 733 if (consistAddress != null) { 734 l.notifyConsistAddressReleased(consistAddress); 735 } 736 l.notifyAddressReleased(currentAddress); 737 }); 738 } 739 } 740 741 /** 742 * Create an Element of this object's preferences. 743 * <ul> 744 * <li> Window Preferences 745 * <li> Address value 746 * </ul> 747 * 748 * @return org.jdom2.Element for this objects preferences. Defined in 749 * DTD/throttle-config 750 */ 751 public Element getXml() { 752 Element me = new Element("AddressPanel"); 753 //Element window = new Element("window"); 754 java.util.ArrayList<Element> children = new java.util.ArrayList<>(1); 755 children.add(WindowPreferences.getPreferences(this)); 756 children.add((new jmri.configurexml.LocoAddressXml()) 757 .store(addrSelector.getAddress())); 758 children.add((new jmri.configurexml.LocoAddressXml()) 759 .store(consistAddress)); 760 me.setContent(children); 761 return me; 762 } 763 764 /** 765 * Use the Element passed to initialize based on user prefs. 766 * 767 * @param e The Element containing prefs as defined in DTD/throttle-config 768 */ 769 public void setXml(Element e) { 770 Element window = e.getChild("window"); 771 WindowPreferences.setPreferences(this, window); 772 773 Element addressElement = e.getChild("address"); 774 if ((addressElement != null) && (this.getRosterEntry() == null)) { 775 String address = addressElement.getAttribute("value").getValue(); 776 addrSelector.setAddress(new DccLocoAddress(Integer 777 .parseInt(address), false)); // guess at the short/long 778 consistAddress = null; 779 changeOfAddress(addrSelector.getAddress()); 780 } 781 782 List<Element> elementList = e.getChildren("locoaddress"); 783 if ((!elementList.isEmpty()) && (getThrottle() == null)) { 784 log.debug("found {} locoaddress(es)", elementList.size() ); 785 currentAddress = (DccLocoAddress) (new jmri.configurexml.LocoAddressXml()) 786 .getAddress(elementList.get(0)); 787 log.debug("Loaded address {} from xml",currentAddress); 788 addrSelector.setAddress(currentAddress); 789 consistAddress = null; 790 // if there are two locoaddress, the second is the consist address 791 if (elementList.size() > 1) { 792 DccLocoAddress tmpAdd = ((DccLocoAddress) (new jmri.configurexml.LocoAddressXml()) 793 .getAddress(elementList.get(1))); 794 if (tmpAdd !=null && ! currentAddress.equals(tmpAdd)) { 795 log.debug("and consist with {}",tmpAdd); 796 consistAddress = tmpAdd; 797 } 798 } 799 changeOfAddress(addrSelector.getAddress()); 800 } 801 } 802 803 /** 804 * @return the RosterEntrySelectorPanel 805 */ 806 public RosterEntrySelectorPanel getRosterEntrySelector() { 807 return rosterBox; 808 } 809 810 /** 811 * @return the curently assigned motor throttle for regular locomotives or consist 812 */ 813 public DccThrottle getThrottle() { 814 if (consistThrottle != null) { 815 return consistThrottle; 816 } 817 return throttle; 818 } 819 820 /** 821 * @return the curently assigned function throttle for regular locomotives or consist 822 */ 823 public DccThrottle getFunctionThrottle() { 824 if (throttle != null) { 825 return throttle; 826 } 827 return consistThrottle; 828 } 829 830 831 /** 832 * @return the currently used decoder address 833 */ 834 public DccLocoAddress getCurrentAddress() { 835 return currentAddress; 836 } 837 838 /** 839 * set the currently used decoder address and initiate a throttle request 840 * if a consist address is already set, this address will be used only for functions 841 * 842 * @param currentAddress the address to use 843 * 844 */ 845 public void setCurrentAddress(DccLocoAddress currentAddress) { 846 if (log.isDebugEnabled()) { 847 log.debug("Setting CurrentAddress to {}", currentAddress); 848 } 849 addrSelector.setAddress(currentAddress); 850 changeOfAddress(addrSelector.getAddress()); 851 } 852 853 /** 854 * set the currently used decoder address and initiate a throttle request (same as setCurrentAddress) 855 * if a consist address is already set, this address will be used only for functions 856 * 857 * @param number the address 858 * @param isLong long/short (true/false) address 859 * 860 */ 861 public void setAddress(int number, boolean isLong) { 862 setCurrentAddress(new DccLocoAddress(number, isLong)); 863 } 864 865 /** 866 * @return the current consist address if any 867 */ 868 @CheckForNull 869 public DccLocoAddress getConsistAddress() { 870 return consistAddress; 871 } 872 873 /** 874 * set the currently used consist address and initiate a throttle request 875 * 876 * @param consistAddress the consist address to use 877 */ 878 public void setConsistAddress(DccLocoAddress consistAddress) { 879 log.debug("Setting Consist Address to {}", consistAddress); 880 this.consistAddress = consistAddress; 881 changeOfConsistAddress(); 882 } 883 884 @Override 885 public void propertyChange(PropertyChangeEvent evt) { 886 if (evt == null) { 887 return; 888 } 889 if ("ThrottleConnected".compareTo(evt.getPropertyName()) == 0) { 890 if (((Boolean) evt.getOldValue()) && (!((Boolean) evt.getNewValue()))) { 891 log.debug("propertyChange: ThrottleConnected to false"); 892 notifyThrottleDisposed(); 893 throttle = null; 894 consistThrottle = null; 895 } 896 } 897 898 if ("DispatchEnabled".compareTo(evt.getPropertyName()) == 0) { 899 log.debug("propertyChange: Dispatch Button Enabled {}" , evt.getNewValue() ); 900 dispatchButton.setEnabled( (Boolean) evt.getNewValue() ); 901 } 902 903 if ("ReleaseEnabled".compareTo(evt.getPropertyName()) == 0) { 904 log.debug("propertyChange: release Button Enabled {}" , evt.getNewValue() ); 905 releaseButton.setEnabled( (Boolean) evt.getNewValue() ); 906 } 907 } 908 909 void applyPreferences() { 910 if ((InstanceManager.getDefault(ThrottlesPreferences.class).isUsingExThrottle())) { 911 rosterBox.getRosterGroupComboBox().setAllEntriesEnabled(InstanceManager.getDefault(ThrottlesPreferences.class).isAddressSelectorShowingAllRosterGroup()); 912 } 913 } 914 915 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AddressPanel.class); 916 917} 918