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