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