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 // during testing listeners can be null 264 if (listeners != null) { 265 (new ArrayList<AddressListener>(listeners)).forEach((l) -> { 266 // log.debug("Notify address listener of address change {}", l.getClass()); 267 l.notifyAddressThrottleFound(t); 268 }); 269 } 270 } 271 272 @Override 273 public void notifyFailedThrottleRequest(LocoAddress address, String reason) { 274 JmriJOptionPane.showMessageDialog(null, reason, Bundle.getMessage("FailedSetupRequestTitle"), JmriJOptionPane.WARNING_MESSAGE); 275 } 276 277 /** 278 * A decision is required for Throttle creation to continue. 279 * <p> 280 * Steal / Cancel, Share / Cancel, or Steal / Share Cancel 281 */ 282 @Override 283 public void notifyDecisionRequired(LocoAddress address, DecisionType question) { 284 if ( null != question ) { 285 switch (question) { 286 case STEAL: 287 if (InstanceManager.getDefault(ThrottlesPreferences.class).isSilentSteal() ){ 288 throttleManager.responseThrottleDecision(address, this, DecisionType.STEAL ); 289 return; 290 } 291 jmri.util.ThreadingUtil.runOnGUI(() -> { 292 if ( JmriJOptionPane.YES_OPTION == JmriJOptionPane.showConfirmDialog( 293 this, Bundle.getMessage("StealQuestionText",address.toString()), 294 Bundle.getMessage("StealRequestTitle"), JmriJOptionPane.YES_NO_OPTION)) { 295 throttleManager.responseThrottleDecision(address, this, DecisionType.STEAL ); 296 } else { 297 throttleManager.cancelThrottleRequest(address, this); 298 requestedAddress = null; 299 } 300 }); 301 break; 302 case SHARE: 303 if (InstanceManager.getDefault(ThrottlesPreferences.class).isSilentShare() ){ 304 throttleManager.responseThrottleDecision(address, this, DecisionType.SHARE ); 305 return; 306 } 307 jmri.util.ThreadingUtil.runOnGUI(() -> { 308 if ( JmriJOptionPane.YES_OPTION == JmriJOptionPane.showConfirmDialog( 309 this, Bundle.getMessage("ShareQuestionText",address.toString()), 310 Bundle.getMessage("ShareRequestTitle"), JmriJOptionPane.YES_NO_OPTION)) { 311 throttleManager.responseThrottleDecision(address, this, DecisionType.SHARE ); 312 } else { 313 throttleManager.cancelThrottleRequest(address, this); 314 requestedAddress = null; 315 } 316 }); 317 break; 318 case STEAL_OR_SHARE: 319 if ( InstanceManager.getDefault(ThrottlesPreferences.class).isSilentSteal() ){ 320 throttleManager.responseThrottleDecision(address, this, DecisionType.STEAL ); 321 return; 322 } 323 if ( InstanceManager.getDefault(ThrottlesPreferences.class).isSilentShare() ){ 324 throttleManager.responseThrottleDecision(address, this, DecisionType.SHARE ); 325 return; 326 } 327 String[] options = new String[] {Bundle.getMessage("StealButton"), Bundle.getMessage("ShareButton"), Bundle.getMessage("ButtonCancel")}; 328 jmri.util.ThreadingUtil.runOnGUI(() -> { 329 int response = JmriJOptionPane.showOptionDialog(AddressPanel.this, 330 Bundle.getMessage("StealShareQuestionText",address.toString()), Bundle.getMessage("StealShareRequestTitle"), 331 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE, null, options, options[1]); 332 switch (response) { 333 case 0: 334 log.debug("steal clicked"); 335 throttleManager.responseThrottleDecision(address, AddressPanel.this, DecisionType.STEAL); 336 break; 337 case 1: 338 log.debug("share clicked"); 339 throttleManager.responseThrottleDecision(address, AddressPanel.this, DecisionType.SHARE); 340 break; 341 default: 342 log.debug("cancel clicked"); 343 throttleManager.cancelThrottleRequest(address, AddressPanel.this); 344 requestedAddress = null; 345 break; 346 } 347 }); 348 break; 349 default: 350 break; 351 } 352 } 353 } 354 355 /** 356 * Get notification that a consist throttle has been found as we requested. 357 * 358 * @param t An instantiation of the DccThrottle with the address requested. 359 */ 360 public void notifyConsistThrottleFound(DccThrottle t) { 361 if (consistThrottle != null) { 362 log.debug("notifyConsistThrottleFound() consistThrottle non null, called for loc {}",t.getLocoAddress()); 363 return; 364 } 365 requestedAddress = null; 366 consistThrottle = t; 367 currentAddress = (DccLocoAddress) t.getLocoAddress(); 368 consistThrottle.addPropertyChangeListener(this); 369 370 Consist consist = getConsistEntry(); 371 if (consist != null && consist.getConsistType() == Consist.CS_CONSIST) { 372 // CS Consist, consist has the head locomotive id 373 // can we find a roster entry? 374 if ((rosterEntry == null) 375 && (InstanceManager.getDefault(ThrottlesPreferences.class).isUsingExThrottle()) 376 && (InstanceManager.getDefault(ThrottlesPreferences.class).isEnablingRosterSearch()) 377 && currentAddress != null) { 378 List<RosterEntry> l = Roster.getDefault().matchingList(null, null, "" + currentAddress.getNumber(), null, null, null, null); 379 if (!l.isEmpty()) { 380 rosterEntry = l.get(0); 381 } 382 } 383 } 384 385 updateGUIOnThrottleFound(true); 386 387 // send notification of new address 388 // work on a clone because some new listeners may be added while notifying the existing ones 389 (new ArrayList<AddressListener>(listeners)).forEach((l) -> { 390 l.notifyConsistAddressThrottleFound(t); 391 }); 392 393 if (consist != null && consist.getConsistType() == Consist.ADVANCED_CONSIST) { 394 // request a throttle for head locomotive for functions 395 DccLocoAddress headLocoAddress = consist.getConsistList().get(0); 396 // only if consist address is not head locomotive address 397 if (! headLocoAddress.equals(currentAddress)) { 398 log.debug("Advanced consist throttle, requesting secondary throttle for head locomotive function control."); 399 changeOfAddress(headLocoAddress); 400 } 401 } 402 } 403 404 private void updateGUIOnThrottleFound(boolean throttleActive) { 405 // update GUI 406 isUpdatingUI = true; 407 //addrSelector.setAddress(currentAddress); 408 setButton.setEnabled(!throttleActive); 409 addrSelector.setEnabled(!throttleActive); 410 releaseButton.setEnabled(throttleActive); 411 if (throttleActive && rosterEntry != null) { 412 getRosterEntrySelector().setSelectedRosterEntry(rosterEntry); 413 } else { 414 getRosterEntrySelector().getRosterEntryComboBox().setSelectedItem(Bundle.getMessage("NoLocoSelected")); 415 } 416 getRosterEntrySelector().setEnabled(!throttleActive); 417 if (conRosterBox != null) { 418 if (throttleActive && consistThrottle != null) { 419 conRosterBox.setSelectedItem(consistThrottle.getLocoAddress()); 420 } else { 421 conRosterBox.setSelectedItem(Bundle.getMessage("NoConsistSelected")); 422 } 423 conRosterBox.setEnabled(!throttleActive); 424 } 425 if (throttleManager.hasDispatchFunction()) { 426 dispatchButton.setEnabled(throttleActive); 427 } 428 // enable program button if programmer available 429 // for ops-mode programming 430 if ((rosterEntry != null) && (ProgDefault.getDefaultProgFile() != null) 431 && (InstanceManager.getNullableDefault(jmri.AddressedProgrammerManager.class) != null) 432 && (InstanceManager.getDefault(jmri.AddressedProgrammerManager.class).isAddressedModePossible())) { 433 progButton.setEnabled(true); 434 } else { 435 progButton.setEnabled(false); 436 } 437 isUpdatingUI = false; 438 } 439 440 /** 441 * Receive notification that an address has been release or dispatched. 442 */ 443 public void notifyThrottleDisposed() { 444 log.debug("notifyThrottleDisposed"); 445 notifyListenersOfThrottleRelease(); 446 updateGUIOnThrottleFound(false); 447 rosterEntry = null; 448 if (consistThrottle != null) { 449 consistThrottle.removePropertyChangeListener(this); 450 } 451 if (throttle != null) { 452 throttle.removePropertyChangeListener(this); 453 } 454 } 455 456 /** 457 * Get the RosterEntry if there's one for this throttle. 458 * 459 * @return RosterEntry or null 460 */ 461 public RosterEntry getRosterEntry() { 462 return rosterEntry; 463 } 464 465 /** 466 * Get the selected Consist if there's one for this throttle. 467 * 468 * @return Consist or null 469 */ 470 public Consist getConsistEntry() { 471 if (consistManager == null || consistAddress == null || !consistManager.isEnabled()) { 472 return null; 473 } 474 if (consistManager.getConsistList().contains(consistAddress)) { 475 return consistManager.getConsist(consistAddress); 476 } 477 return null; 478 } 479 480 /** 481 * Set the RosterEntry for this throttle and initiate a throttle request 482 * @param entry roster entry to set. 483 */ 484 public void setRosterEntry(RosterEntry entry) { 485 isUpdatingUI = true; 486 getRosterEntrySelector().setSelectedRosterEntry(entry); 487 addrSelector.setAddress(entry.getDccLocoAddress()); 488 isUpdatingUI = false; 489 rosterEntry = entry; 490 changeOfAddress(addrSelector.getAddress()); 491 } 492 493 /** 494 * Create, initialize and place the GUI objects. 495 */ 496 @SuppressWarnings("unchecked") //for the onRosterBox.insertItemAt(), to be a removed once NCE consists clarified 497 private void initGUI() { 498 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 499 mainPanel = new JPanel(); 500 mainPanel.setLayout(new BorderLayout()); 501 this.setContentPane(mainPanel); 502 503 // center: address input 504 addrSelector.setVariableSize(true); 505 mainPanel.add(addrSelector.getCombinedJPanel(), BorderLayout.CENTER); 506 addrSelector.getTextField().addActionListener(e -> { 507 if (isUpdatingUI) { 508 return; 509 } 510 consistAddress = null; 511 changeOfAddress(addrSelector.getAddress()); 512 }); 513 514 // top : roster and consists selectors 515 JPanel topPanel = new JPanel(); 516 topPanel.setLayout(new WrapLayout(FlowLayout.CENTER, 2, 2)); 517 518 rosterBox = new RosterEntrySelectorPanel(); 519 if ((InstanceManager.getDefault(ThrottlesPreferences.class).isUsingExThrottle()) && (InstanceManager.getDefault(ThrottlesPreferences.class).isAddressSelectorShowingAllRosterGroup())) { 520 rosterBox.getRosterGroupComboBox().setAllEntriesEnabled(true); 521 } 522 getRosterEntrySelector().setNonSelectedItem(Bundle.getMessage("NoLocoSelected")); 523 getRosterEntrySelector().setToolTipText(Bundle.getMessage("SelectLocoFromRosterTT")); 524 getRosterEntrySelector().addPropertyChangeListener("selectedRosterEntries", pce -> selectRosterEntry()); 525 getRosterEntrySelector().setLayout(new WrapLayout(FlowLayout.CENTER, 2, 2)); 526 topPanel.add(getRosterEntrySelector()); 527 528 if (InstanceManager.getDefault(NceConsistRoster.class).numEntries() > 0) { // NCE consists 529 // NCE implementation of consists is specific, TODO: refactor to use generic JMRI consists 530 conRosterBox = InstanceManager.getDefault(NceConsistRoster.class).fullRosterComboBox(); 531 conRosterBox.insertItemAt(Bundle.getMessage("NoConsistSelected"), 0); // empty entry 532 conRosterBox.setSelectedIndex(0); 533 conRosterBox.setToolTipText(Bundle.getMessage("SelectConsistFromRosterTT")); 534 conRosterBox.addActionListener(e -> nceConsistRosterSelected()); 535 topPanel.add(conRosterBox); 536 } else { 537 if ((consistManager != null) && (consistManager.isEnabled())) { // JMRI consists 538 JPanel consistPanel = new JPanel(); 539 JButton consistToolButton = new JButton(new jmri.jmrit.consisttool.ConsistToolAction()); 540 consistPanel.add(consistToolButton); 541 conRosterBox = new ConsistComboBox(); 542 conRosterBox.addActionListener(e -> jmriConsistRosterSelected()); 543 consistPanel.add(conRosterBox); 544 topPanel.add(consistPanel); 545 } 546 } 547 548 mainPanel.add(topPanel, BorderLayout.NORTH); 549 550 // bottom : buttons 551 JPanel buttonPanel = new JPanel(); 552 buttonPanel.setLayout(new WrapLayout(FlowLayout.CENTER, 2, 2)); 553 554 progButton = new JButton(Bundle.getMessage("ButtonProgram")); 555 buttonPanel.add(progButton); 556 progButton.setEnabled(false); 557 progButton.addActionListener(e -> openProgrammer()); 558 559 dispatchButton = new JButton(Bundle.getMessage("ButtonDispatch")); 560 buttonPanel.add(dispatchButton); 561 dispatchButton.setEnabled(false); 562 dispatchButton.addActionListener(e -> dispatchAddress()); 563 564 releaseButton = new JButton(Bundle.getMessage("ButtonRelease")); 565 buttonPanel.add(releaseButton); 566 releaseButton.setEnabled(false); 567 releaseButton.addActionListener(e -> releaseAddress()); 568 569 setButton = new JButton(Bundle.getMessage("ButtonSet")); 570 setButton.addActionListener(e -> { 571 consistAddress = null; 572 changeOfAddress(addrSelector.getAddress()); 573 }); 574 buttonPanel.add(setButton); 575 576 mainPanel.add(buttonPanel, BorderLayout.SOUTH); 577 578 pack(); 579 } 580 581 private void jmriConsistRosterSelected() { 582 if (isUpdatingUI) { 583 return; 584 } 585 if ((conRosterBox.getSelectedIndex() != 0) && (conRosterBox.getSelectedItem() instanceof DccLocoAddress)) { 586 consistAddress = (DccLocoAddress) conRosterBox.getSelectedItem() ; 587 changeOfConsistAddress(); 588 } 589 } 590 591 private void nceConsistRosterSelected() { 592 if (isUpdatingUI) { 593 return; 594 } 595 if (!(Objects.equals(conRosterBox.getSelectedItem(), Bundle.getMessage("NoConsistSelected")))) { 596 String rosterEntryTitle = Objects.requireNonNull(conRosterBox.getSelectedItem()).toString(); 597 NceConsistRosterEntry nceConsistRosterEntry = InstanceManager.getDefault(NceConsistRoster.class) 598 .entryFromTitle(rosterEntryTitle); 599 600 DccLocoAddress a = new DccLocoAddress(Integer.parseInt(nceConsistRosterEntry 601 .getLoco1DccAddress()), nceConsistRosterEntry.isLoco1LongAddress()); 602 addrSelector.setAddress(a); 603 consistAddress = null; 604 int cA = 0; 605 try { 606 cA = Integer.parseInt(nceConsistRosterEntry.getConsistNumber()); 607 } catch (NumberFormatException ignored) { 608 609 } 610 if (0 < cA && cA < 128) { 611 consistAddress = new DccLocoAddress(cA, false); 612 } else { 613 log.warn("consist number missing {}", nceConsistRosterEntry.getLoco1DccAddress()); 614 JmriJOptionPane.showMessageDialog(mainPanel, 615 Bundle.getMessage("ConsistNumberHasNotBeenAssigned"), 616 Bundle.getMessage("NeedsConsistNumber"), 617 JmriJOptionPane.ERROR_MESSAGE); 618 return; 619 } 620 if (JmriJOptionPane.showConfirmDialog(mainPanel, 621 Bundle.getMessage("SendFunctionToLead"), Bundle.getMessage("NCEconsistThrottle"), 622 JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) { 623 addrSelector.setAddress(consistAddress); 624 consistAddress = null; 625 } 626 changeOfAddress(addrSelector.getAddress()); 627 } 628 } 629 630 /** 631 * The user has selected a new address. Notify all listeners. 632 */ 633 private void changeOfAddress(DccLocoAddress a) { 634 currentAddress = a; 635 if (currentAddress == null) { 636 return; // no address 637 } 638 // send notification of new address 639 // during testing listeners can be null 640 if (listeners != null) { 641 listeners.forEach((l) -> { 642 l.notifyAddressChosen(currentAddress); 643 }); 644 } 645 log.debug("Requesting new slot for address {} rosterEntry {}",currentAddress,rosterEntry); 646 boolean requestOK; 647 if (rosterEntry == null) { 648 requestedAddress = currentAddress; 649 requestOK = throttleManager.requestThrottle(currentAddress, this, true); 650 } 651 else { 652 requestedAddress = rosterEntry.getDccLocoAddress(); 653 requestOK = throttleManager.requestThrottle(rosterEntry, this, true); 654 } 655 if (!requestOK) { 656 requestedAddress = null; 657 JmriJOptionPane.showMessageDialog(mainPanel, Bundle.getMessage("AddressInUse")); 658 } 659 } 660 661 private void changeOfConsistAddress() { 662 if (consistAddress == null) { 663 return; // no address 664 } 665 addrSelector.setAddress(consistAddress); 666 // send notification of new address 667 listeners.forEach((l) -> { 668 l.notifyAddressChosen(currentAddress); 669 }); 670 log.debug("Requesting new slot for consist address {}",consistAddress); 671 requestedAddress = consistAddress; 672 boolean requestOK = throttleManager.requestThrottle(consistAddress, this, true); 673 if (!requestOK) { 674 requestedAddress = null; 675 JmriJOptionPane.showMessageDialog(mainPanel, Bundle.getMessage("AddressInUse")); 676 } 677 } 678 679 /** 680 * Open a programmer for this address 681 */ 682 protected void openProgrammer() { 683 if (rosterEntry == null) { 684 return; 685 } 686 687 java.util.ResourceBundle rbt = java.util.ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle"); 688 String ptitle = java.text.MessageFormat.format(rbt.getString("FrameOpsProgrammerTitle"), rosterEntry.getId()); 689 // find the ops-mode programmer 690 int address = Integer.parseInt(rosterEntry.getDccAddress()); 691 boolean longAddr = true; 692 if (address < 100) { 693 longAddr = false; 694 } 695 Programmer programmer = InstanceManager.getDefault(jmri.AddressedProgrammerManager.class).getAddressedProgrammer(longAddr, address); 696 // and created the frame 697 JFrame p = new PaneOpsProgFrame(null, rosterEntry, 698 ptitle, "programmers" + File.separator + ProgDefault.getDefaultProgFile() + ".xml", 699 programmer); 700 p.pack(); 701 p.setVisible(true); 702 } 703 704 /** 705 * Dispatch the current address for use by other throttles 706 */ 707 public void dispatchAddress() { 708 if (throttle != null) { 709 int usageCount = throttleManager.getThrottleUsageCount(throttle.getLocoAddress()) - 1; 710 if ( usageCount != 0 ) { 711 JmriJOptionPane.showMessageDialog(mainPanel, Bundle.getMessage("CannotDispatch", usageCount)); 712 return; 713 } 714 notifyThrottleDisposed(); 715 throttleManager.dispatchThrottle(throttle, this); 716 throttle = null; 717 } 718 } 719 720 /** 721 * Release the current address. 722 */ 723 public void releaseAddress() { 724 notifyThrottleDisposed(); 725 if (throttle != null) { 726 throttleManager.releaseThrottle(throttle, this); 727 throttle = null; 728 } 729 if (consistThrottle != null) { 730 throttleManager.releaseThrottle(consistThrottle, this); 731 consistThrottle = null; 732 } 733 } 734 735 private void notifyListenersOfThrottleRelease() { 736 if (listeners != null) { 737 listeners.forEach((l) -> { 738 // log.debug("Notify address listener {} of release", l.getClass()); 739 if (consistAddress != null) { 740 l.notifyConsistAddressReleased(consistAddress); 741 } 742 l.notifyAddressReleased(currentAddress); 743 }); 744 } 745 } 746 747 /** 748 * Create an Element of this object's preferences. 749 * <ul> 750 * <li> Window Preferences 751 * <li> Address value 752 * </ul> 753 * 754 * @return org.jdom2.Element for this objects preferences. Defined in 755 * DTD/throttle-config 756 */ 757 public Element getXml() { 758 Element me = new Element("AddressPanel"); 759 //Element window = new Element("window"); 760 java.util.ArrayList<Element> children = new java.util.ArrayList<>(1); 761 children.add(WindowPreferences.getPreferences(this)); 762 children.add((new jmri.configurexml.LocoAddressXml()) 763 .store(addrSelector.getAddress())); 764 children.add((new jmri.configurexml.LocoAddressXml()) 765 .store(consistAddress)); 766 me.setContent(children); 767 return me; 768 } 769 770 /** 771 * Use the Element passed to initialize based on user prefs. 772 * 773 * @param e The Element containing prefs as defined in DTD/throttle-config 774 */ 775 public void setXml(Element e) { 776 Element window = e.getChild("window"); 777 WindowPreferences.setPreferences(this, window); 778 779 Element addressElement = e.getChild("address"); 780 if ((addressElement != null) && (this.getRosterEntry() == null)) { 781 String address = addressElement.getAttribute("value").getValue(); 782 addrSelector.setAddress(new DccLocoAddress(Integer 783 .parseInt(address), false)); // guess at the short/long 784 consistAddress = null; 785 changeOfAddress(addrSelector.getAddress()); 786 } 787 788 List<Element> elementList = e.getChildren("locoaddress"); 789 if ((!elementList.isEmpty()) && (getThrottle() == null)) { 790 log.debug("found {} locoaddress(es)", elementList.size() ); 791 currentAddress = (DccLocoAddress) (new jmri.configurexml.LocoAddressXml()) 792 .getAddress(elementList.get(0)); 793 log.debug("Loaded address {} from xml",currentAddress); 794 addrSelector.setAddress(currentAddress); 795 consistAddress = null; 796 // if there are two locoaddress, the second is the consist address 797 if (elementList.size() > 1) { 798 DccLocoAddress tmpAdd = ((DccLocoAddress) (new jmri.configurexml.LocoAddressXml()) 799 .getAddress(elementList.get(1))); 800 if (tmpAdd !=null && ! currentAddress.equals(tmpAdd)) { 801 log.debug("and consist with {}",tmpAdd); 802 consistAddress = tmpAdd; 803 } 804 } 805 changeOfAddress(addrSelector.getAddress()); 806 } 807 } 808 809 /** 810 * @return the RosterEntrySelectorPanel 811 */ 812 public RosterEntrySelectorPanel getRosterEntrySelector() { 813 return rosterBox; 814 } 815 816 /** 817 * @return the curently assigned motor throttle for regular locomotives or consist 818 */ 819 public DccThrottle getThrottle() { 820 if (consistThrottle != null) { 821 return consistThrottle; 822 } 823 return throttle; 824 } 825 826 /** 827 * @return the curently assigned function throttle for regular locomotives or consist 828 */ 829 public DccThrottle getFunctionThrottle() { 830 if (throttle != null) { 831 return throttle; 832 } 833 return consistThrottle; 834 } 835 836 837 /** 838 * @return the currently used decoder address 839 */ 840 public DccLocoAddress getCurrentAddress() { 841 return currentAddress; 842 } 843 844 /** 845 * set the currently used decoder address and initiate a throttle request 846 * if a consist address is already set, this address will be used only for functions 847 * 848 * @param currentAddress the address to use 849 * 850 */ 851 public void setCurrentAddress(DccLocoAddress currentAddress) { 852 if (log.isDebugEnabled()) { 853 log.debug("Setting CurrentAddress to {}", currentAddress); 854 } 855 addrSelector.setAddress(currentAddress); 856 changeOfAddress(addrSelector.getAddress()); 857 } 858 859 /** 860 * set the currently used decoder address and initiate a throttle request (same as setCurrentAddress) 861 * if a consist address is already set, this address will be used only for functions 862 * 863 * @param number the address 864 * @param isLong long/short (true/false) address 865 * 866 */ 867 public void setAddress(int number, boolean isLong) { 868 setCurrentAddress(new DccLocoAddress(number, isLong)); 869 } 870 871 /** 872 * @return the current consist address if any 873 */ 874 @CheckForNull 875 public DccLocoAddress getConsistAddress() { 876 return consistAddress; 877 } 878 879 /** 880 * set the currently used consist address and initiate a throttle request 881 * 882 * @param consistAddress the consist address to use 883 */ 884 public void setConsistAddress(DccLocoAddress consistAddress) { 885 log.debug("Setting Consist Address to {}", consistAddress); 886 this.consistAddress = consistAddress; 887 changeOfConsistAddress(); 888 } 889 890 @Override 891 public void propertyChange(PropertyChangeEvent evt) { 892 if (evt == null) { 893 return; 894 } 895 if ("ThrottleConnected".compareTo(evt.getPropertyName()) == 0) { 896 if (((Boolean) evt.getOldValue()) && (!((Boolean) evt.getNewValue()))) { 897 log.debug("propertyChange: ThrottleConnected to false"); 898 notifyThrottleDisposed(); 899 // remove all listeners and destroy the throttle 900 destroy(); 901 throttle = null; 902 consistThrottle = null; 903 } 904 } 905 906 if ("DispatchEnabled".compareTo(evt.getPropertyName()) == 0) { 907 log.debug("propertyChange: Dispatch Button Enabled {}" , evt.getNewValue() ); 908 dispatchButton.setEnabled( (Boolean) evt.getNewValue() ); 909 } 910 911 if ("ReleaseEnabled".compareTo(evt.getPropertyName()) == 0) { 912 log.debug("propertyChange: release Button Enabled {}" , evt.getNewValue() ); 913 releaseButton.setEnabled( (Boolean) evt.getNewValue() ); 914 } 915 } 916 917 void applyPreferences() { 918 if ((InstanceManager.getDefault(ThrottlesPreferences.class).isUsingExThrottle())) { 919 rosterBox.getRosterGroupComboBox().setAllEntriesEnabled(InstanceManager.getDefault(ThrottlesPreferences.class).isAddressSelectorShowingAllRosterGroup()); 920 } 921 } 922 923 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AddressPanel.class); 924 925} 926