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