001package jmri.jmrit.progsupport; 002 003import java.awt.event.ActionListener; 004import java.beans.PropertyChangeListener; 005import java.util.ArrayList; 006import java.util.HashMap; 007import java.util.List; 008import java.util.Map; 009import javax.swing.BoxLayout; 010import javax.swing.ButtonGroup; 011import javax.swing.JCheckBox; 012import javax.swing.JComboBox; 013import javax.swing.JLabel; 014import javax.swing.JPanel; 015import javax.swing.JRadioButton; 016import javax.swing.JSpinner; 017import javax.swing.SpinnerNumberModel; 018import jmri.AddressedProgrammer; 019import jmri.AddressedProgrammerManager; 020import jmri.InstanceManager; 021import jmri.Programmer; 022import jmri.ProgrammingMode; 023import jmri.implementation.AccessoryOpsModeProgrammerFacade; 024import jmri.jmrix.loconet.LnProgrammerManager; 025 026/** 027 * Provide a JPanel to configure the ops programming (Adressed) mode. 028 * <p> 029 * Note that you should call the dispose() method when you're really done, so 030 * that a ProgModePane object can disconnect its listeners. 031 * 032 * @author Bob Jacobsen Copyright (C) 2001 033 * @author Daniel Bergqvist Copyright (C) 2021 034 */ 035public class ProgOpsModePane extends ProgModeSelector implements PropertyChangeListener, ActionListener { 036 037 // GUI member declarations 038 ButtonGroup modeGroup; 039 HashMap<ProgrammingMode, JRadioButton> buttonMap = new HashMap<>(); 040 JComboBox<AddressedProgrammerManager> progBox; 041 ArrayList<JRadioButton> buttonPool = new ArrayList<>(); 042 // JTextField mAddrField = new JTextField(4); 043 // use JSpinner for CV number input 044 SpinnerNumberModel model = new SpinnerNumberModel(0, 0, 10239, 1); // 10239 is highest DCC Long Address documented by NMRA as per 2017 045 JSpinner mAddrField = new JSpinner(model); 046 int lowAddrLimit = 0; 047 int highAddrLimit = 10239; 048 int oldAddrValue = 3; // Default start value 049 ButtonGroup addrGroup = new ButtonGroup(); 050 JRadioButton shortAddrButton = new JRadioButton(Bundle.getMessage("ShortAddress")); 051 JRadioButton longAddrButton = new JRadioButton(Bundle.getMessage("LongAddress")); 052 JCheckBox offsetAddrCheckBox = new JCheckBox(Bundle.getMessage("DccAccessoryAddressOffSet")); 053 JLabel addressLabel = new JLabel(Bundle.getMessage("AddressLabel")); 054 boolean oldLongAddr = false; 055 boolean opsAccyMode = false; 056 boolean oldOpsAccyMode = false; 057 boolean opsSigMode = false; 058 boolean oldOpsSigMode = false; 059 boolean lnAttachedBoardMode = false; // LOCONETOPSBOARD programming 060 boolean oldLnAttachedBoardMode = false; 061 boolean lnSv2Mode = false; // LOCONETSV2MODE programming 062 boolean oldLnSv2Mode = false; 063 boolean lncvMode = false; // LOCONETLNCVMODE programming 064 boolean oldLncvMode = false; 065 boolean oldoffsetAddrCheckBox = false; 066 transient volatile AddressedProgrammer programmer = null; 067 transient volatile AccessoryOpsModeProgrammerFacade facadeProgrammer = null; 068 069 /** 070 * Get the selected programmer. 071 */ 072 @Override 073 public Programmer getProgrammer() { 074 log.debug("getProgrammer mLongAddrCheck.isSelected()={}, oldLongAddr={}, mAddrField.getValue()={}, oldAddrValue={}, opsAccyMode={}, oldOpsAccyMode={}, opsSigMode={}, oldOpsSigMode={}, lnSv2Mode={}, oldLnSv2Mode={}, lncvMode={}, oldLncvMode={}, oldoffsetAddrCheckBox={})", 075 longAddrButton.isSelected(), oldLongAddr, mAddrField.getValue(), oldAddrValue, opsAccyMode, oldOpsAccyMode, opsSigMode, oldOpsSigMode, lnSv2Mode, oldLnSv2Mode, lncvMode, oldLncvMode, oldoffsetAddrCheckBox); 076 if (longAddrButton.isSelected() == oldLongAddr 077 && mAddrField.getValue().equals(oldAddrValue) 078 && offsetAddrCheckBox.isSelected() == oldoffsetAddrCheckBox 079 && opsAccyMode == oldOpsAccyMode 080 && opsSigMode == oldOpsSigMode 081 && lnAttachedBoardMode == oldLnAttachedBoardMode 082 && lnSv2Mode == oldLnSv2Mode 083 && lncvMode == oldLncvMode) { 084 log.debug("getProgrammer hasn't changed"); 085 // hasn't changed 086 if (opsAccyMode || opsSigMode) { 087 return facadeProgrammer; 088 } else { 089 return programmer; 090 } 091 } 092 093 // here values have changed, try to create a new one 094 AddressedProgrammerManager pm = ((AddressedProgrammerManager) progBox.getSelectedItem()); 095 oldLongAddr = longAddrButton.isSelected(); 096 oldAddrValue = (Integer) mAddrField.getValue(); 097 oldOpsAccyMode = opsAccyMode; 098 oldOpsSigMode = opsSigMode; 099 oldLnAttachedBoardMode = lnAttachedBoardMode; 100 oldLnSv2Mode = lnSv2Mode; 101 oldLncvMode = lncvMode; 102 oldoffsetAddrCheckBox = offsetAddrCheckBox.isSelected(); 103 setAddrParams(); 104 105 if (pm != null) { 106 int address = 3; 107 try { 108 address = (Integer) mAddrField.getValue(); 109 } catch (java.lang.NumberFormatException e) { 110 log.error("loco address \"{}\" not correct", mAddrField.getValue()); 111 programmer = null; 112 } 113 boolean longAddr = longAddrButton.isSelected(); 114 log.debug("ops programmer for address {}, long address {}", address, longAddr); 115 programmer = pm.getAddressedProgrammer(longAddr, address); 116 log.debug(" programmer: {}", programmer); 117 118 // whole point is to get mode... 119 setProgrammerFromGui(programmer); 120 } else { 121 log.warn("request for ops mode programmer with no ProgrammerManager configured"); 122 programmer = null; 123 } 124 if (opsAccyMode) { 125 log.debug(" getting AccessoryOpsModeProgrammerFacade"); 126 facadeProgrammer = new AccessoryOpsModeProgrammerFacade(programmer, 127 longAddrButton.isSelected() ? "accessory" : "decoder", 200, programmer); 128 return facadeProgrammer; 129 } else if (opsSigMode) { 130 String addrType = offsetAddrCheckBox.isSelected() ? "signal" : "altsignal"; 131 log.debug(" getting AccessoryOpsModeProgrammerFacade {}", addrType); 132 facadeProgrammer = new AccessoryOpsModeProgrammerFacade(programmer, addrType, 200, programmer); 133 return facadeProgrammer; 134 } 135 return programmer; 136 } 137 138 /** 139 * Are any of the modes selected? 140 * 141 * @return true is any button is selected 142 */ 143 @Override 144 public boolean isSelected() { 145 for (JRadioButton button : buttonMap.values()) { 146 if (button.isSelected()) { 147 return true; 148 } 149 } 150 return false; 151 } 152 153 /** 154 * Constructor for the Programming settings pane. 155 * 156 * @param direction controls layout, either BoxLayout.X_AXIS or 157 * BoxLayout.Y_AXIS 158 */ 159 public ProgOpsModePane(int direction) { 160 this(direction, new javax.swing.ButtonGroup()); 161 } 162 163 /** 164 * Constructor for the Programming settings pane. 165 * 166 * @param direction controls layout, either BoxLayout.X_AXIS or 167 * BoxLayout.Y_AXIS 168 * @param group A set of JButtons to display programming modes 169 */ 170 public ProgOpsModePane(int direction, javax.swing.ButtonGroup group) { 171 modeGroup = group; 172 addrGroup.add(shortAddrButton); 173 addrGroup.add(longAddrButton); 174 175 // general GUI config 176 setLayout(new BoxLayout(this, direction)); 177 178 setBorder(javax.swing.BorderFactory.createTitledBorder(Bundle.getMessage("TitleProgramOnMain"))); 179 180 // create the programmer display combo box 181 List<AddressedProgrammerManager> v = new ArrayList<>(InstanceManager.getList(AddressedProgrammerManager.class)); 182 add(progBox = new JComboBox<>(v.toArray(new AddressedProgrammerManager[0]))); 183 // if only one, don't show 184 if (progBox.getItemCount() < 2) { 185 progBox.setVisible(false); 186 } 187 progBox.setSelectedItem(InstanceManager.getDefault(jmri.AddressedProgrammerManager.class)); // set default 188 progBox.addActionListener((java.awt.event.ActionEvent e) -> { 189 // new programmer selection 190 programmerSelected(); 191 }); 192 193 add(new JLabel(" ")); 194 add(shortAddrButton); 195 add(longAddrButton); 196 add(offsetAddrCheckBox); 197 offsetAddrCheckBox.setToolTipText(Bundle.getMessage("DccOffsetTooltip")); 198 JPanel panel = new JPanel(); 199 panel.setLayout(new java.awt.FlowLayout()); 200 panel.add(addressLabel); 201 panel.add(mAddrField); 202 mAddrField.setToolTipText(Bundle.getMessage("ToolTipEnterDecoderAddress")); 203 add(panel); 204 add(new JLabel(Bundle.getMessage("OpsModeLabel"))); 205 206// mAddrField.addActionListener(new java.awt.event.ActionListener() { 207// @Override 208// public void actionPerformed(java.awt.event.ActionEvent e) { 209 // new programmer selection 210// programmerSelected(); // in case it has valid address now 211// } 212// }); 213 shortAddrButton.addActionListener((java.awt.event.ActionEvent e) -> { 214 // new programmer selection 215 programmerSelected(); // in case it has valid address now 216 }); 217 218 longAddrButton.addActionListener((java.awt.event.ActionEvent e) -> { 219 // new programmer selection 220 programmerSelected(); // in case it has valid address now 221 }); 222 223 offsetAddrCheckBox.addActionListener((java.awt.event.ActionEvent e) -> { 224 // new programmer selection 225 programmerSelected(); // in case it has valid address now 226 }); 227 228 shortAddrButton.setSelected(true); 229 offsetAddrCheckBox.setSelected(false); 230 offsetAddrCheckBox.setVisible(false); 231 232 // and execute the setup for 1st time 233 programmerSelected(); 234 } 235 236 /** 237 * Reload the interface with the new programmers. 238 */ 239 void programmerSelected() { 240 log.debug("programmerSelected starts with {} buttons", buttonPool.size()); 241 // hide buttons 242 for (JRadioButton button : buttonPool) { 243 button.setVisible(false); 244 } 245 246 // clear map 247 buttonMap.clear(); 248 249 // require new programmer if possible 250 oldAddrValue = -1; 251 252 // configure buttons 253 int index = 0; 254 List<ProgrammingMode> modes = new ArrayList<>(); 255 if (getProgrammer() != null) { 256 modes.addAll(programmer.getSupportedModes()); 257 } else if (progBox.getSelectedItem() != null) { 258 modes.addAll(((AddressedProgrammerManager) progBox.getSelectedItem()).getDefaultModes()); 259 } 260 // add OPSACCBYTEMODE & OPSACCEXTBYTEMODE if possible 261 if (modes.contains(ProgrammingMode.OPSBYTEMODE)) { 262 if (!modes.contains(ProgrammingMode.OPSACCBYTEMODE)) { 263 log.debug(" adding button for {} via AccessoryOpsModeProgrammerFacade", ProgrammingMode.OPSACCBYTEMODE); 264 modes.add(ProgrammingMode.OPSACCBYTEMODE); 265 } 266 if (!modes.contains(ProgrammingMode.OPSACCEXTBYTEMODE)) { 267 log.debug(" adding button for {} via AccessoryOpsModeProgrammerFacade", ProgrammingMode.OPSACCEXTBYTEMODE); 268 modes.add(ProgrammingMode.OPSACCEXTBYTEMODE); 269 } 270 } 271 log.debug(" has {} modes", modes.size()); 272 for (ProgrammingMode mode : modes) { 273 JRadioButton button; 274 // need a new button? 275 if (index >= buttonPool.size()) { 276 log.debug(" add button"); 277 button = new JRadioButton(); 278 buttonPool.add(button); 279 modeGroup.add(button); 280 button.addActionListener(this); 281 add(button); // add to GUI 282 } 283 // configure next button in pool 284 log.debug(" set for {}", mode.toString()); 285 button = buttonPool.get(index++); 286 button.setVisible(true); 287 modeGroup.add(button); 288 button.setText(mode.toString()); 289 buttonMap.put(mode, button); 290 } 291 292 setGuiFromProgrammer(); 293 } 294 295 /** 296 * Listen to buttons for mode changes. 297 * 298 * @param e ActionEvent heard 299 */ 300 @Override 301 public void actionPerformed(java.awt.event.ActionEvent e) { 302 // find selected button 303 log.debug("Selected button: {}", e.getActionCommand()); 304 for (ProgrammingMode mode : buttonMap.keySet()) { 305 if (mode.toString().equals(e.getActionCommand())) { 306 log.debug(" setting mode {} on {}", mode, getProgrammer()); 307 if (getProgrammer() != null) { 308 log.debug("getProgrammer() != null"); 309 if (mode == ProgrammingMode.OPSACCBYTEMODE) { 310 log.debug("OPS ACCY was selected in actionPerformed"); 311 opsAccyMode = true; 312 opsSigMode = false; 313 lnAttachedBoardMode = false; 314 lnSv2Mode = false ; 315 lncvMode = false ; 316 } else if (mode == ProgrammingMode.OPSACCEXTBYTEMODE) { 317 log.debug("OPS SIG was selected in actionPerformed"); 318 opsAccyMode = false; 319 opsSigMode = true; 320 lnAttachedBoardMode = false; 321 lnSv2Mode = false ; 322 lncvMode = false ; 323 } else { 324 opsAccyMode = false; 325 opsSigMode = false; 326 lnAttachedBoardMode = (mode == LnProgrammerManager.LOCONETOPSBOARD); 327 lnSv2Mode = (mode == LnProgrammerManager.LOCONETSV2MODE); 328 lncvMode = (mode == LnProgrammerManager.LOCONETLNCVMODE); 329 getProgrammer().setMode(mode); 330 } 331 } 332 setAddrParams(); 333 return; // 1st match 334 } 335 } 336 } 337 338 /** 339 * Change the programmer (mode). 340 * 341 * @param programmer The type of programmer (i.e. Byte Mode) 342 */ 343 void setProgrammerFromGui(Programmer programmer) { 344 for (Map.Entry<ProgrammingMode, JRadioButton> entry : buttonMap.entrySet()) { 345 if (entry.getValue().isSelected()) { 346 if (entry.getKey() == ProgrammingMode.OPSACCBYTEMODE) { 347 log.debug("OPS ACCY was selected in setProgrammerFromGui"); 348 opsAccyMode = true; 349 opsSigMode = false; 350 lnAttachedBoardMode = false; 351 lnSv2Mode = false; 352 lncvMode = false; 353 } else if (entry.getKey() == ProgrammingMode.OPSACCEXTBYTEMODE) { 354 log.debug("OPS SIG was selected in setProgrammerFromGui"); 355 opsAccyMode = false; 356 opsSigMode = true; 357 lnAttachedBoardMode = false; 358 lnSv2Mode = false; 359 lncvMode = false; 360 } else { 361 opsAccyMode = false; 362 opsSigMode = false; 363 lnAttachedBoardMode = (entry.getKey() == LnProgrammerManager.LOCONETOPSBOARD); 364 lnSv2Mode = (entry.getKey() == LnProgrammerManager.LOCONETSV2MODE); 365 lncvMode = (entry.getKey() == LnProgrammerManager.LOCONETLNCVMODE); 366 getProgrammer().setMode(entry.getKey()); 367 } 368 } 369 } 370 } 371 372 /** 373 * Listen to programmer for mode changes. 374 * 375 * @param e ActionEvent heard 376 */ 377 @Override 378 public void propertyChange(java.beans.PropertyChangeEvent e 379 ) { 380 if ("Mode".equals(e.getPropertyName()) && getProgrammer().equals(e.getSource())) { 381 // mode changed in programmer, change GUI here if needed 382 if (isSelected()) { // only change mode if we have a selected mode, in case some other selector with shared group has the selection 383 setGuiFromProgrammer(); 384 } 385 } 386 } 387 388 /** 389 * Change the selected mode in GUI when programmer is changed elsewhere. 390 */ 391 void setGuiFromProgrammer() { 392 if (getProgrammer() == null) { 393 // no mode selected 394 for (JRadioButton button : buttonPool) { 395 button.setSelected(false); 396 } 397 return; 398 } 399 400 ProgrammingMode mode = getProgrammer().getMode(); 401 if (opsAccyMode) { 402 mode = ProgrammingMode.OPSACCBYTEMODE; 403 } else if (opsSigMode) { 404 mode = ProgrammingMode.OPSACCEXTBYTEMODE; 405 } 406 JRadioButton button = buttonMap.get(mode); 407 if (button == null) { 408 log.error("setGuiFromProgrammer found mode \"{}\" that's not supported by the programmer", mode); 409 return; 410 } 411 log.debug(" setting button for mode {}", mode); 412 button.setSelected(true); 413 setAddrParams(); 414 } 415 416 /** 417 * Set address limits and field names depending on address type. 418 */ 419 void setAddrParams() { 420 if (opsAccyMode) { 421 shortAddrButton.setText(Bundle.getMessage("DecoderAddress")); 422 shortAddrButton.setToolTipText(Bundle.getMessage("ToolTipDecoderAddress")); 423 shortAddrButton.setVisible(true); 424 longAddrButton.setText(Bundle.getMessage("AccessoryAddress")); 425 longAddrButton.setToolTipText(Bundle.getMessage("ToolTipAccessoryAddress")); 426 longAddrButton.setVisible(true); 427 offsetAddrCheckBox.setVisible(false); 428 addressLabel.setText(Bundle.getMessage("AddressLabel")); 429 if (longAddrButton.isSelected()) { 430 lowAddrLimit = 1; 431 highAddrLimit = 2044; 432 } else { 433 lowAddrLimit = 1; 434 highAddrLimit = 511; 435 } 436 } else if (opsSigMode) { 437 shortAddrButton.setVisible(false); 438 longAddrButton.setVisible(false); 439 offsetAddrCheckBox.setVisible(true); 440 addressLabel.setText(Bundle.getMessage("SignalAddressLabel")); 441 lowAddrLimit = 1; 442 highAddrLimit = 2044; 443 } else if (lnAttachedBoardMode) { 444 shortAddrButton.setVisible(false); 445 longAddrButton.setVisible(false); 446 offsetAddrCheckBox.setVisible(false); 447 addressLabel.setText(Bundle.getMessage("NodeLabel")); 448 lowAddrLimit = 0; 449 highAddrLimit = 16383; 450 } else if (lnSv2Mode) { 451 shortAddrButton.setVisible(false); 452 longAddrButton.setVisible(false); 453 offsetAddrCheckBox.setVisible(false); 454 addressLabel.setText(Bundle.getMessage("NodeLabel")); 455 lowAddrLimit = 0; 456 highAddrLimit = 65535; 457 } else if (lncvMode) { 458 shortAddrButton.setVisible(false); 459 longAddrButton.setVisible(false); 460 offsetAddrCheckBox.setVisible(false); 461 addressLabel.setText(Bundle.getMessage("ModuleLabel")); 462 lowAddrLimit = 0; 463 highAddrLimit = 65535; 464 } else { 465 shortAddrButton.setText(Bundle.getMessage("ShortAddress")); 466 shortAddrButton.setToolTipText(Bundle.getMessage("ToolTipShortAddress")); 467 shortAddrButton.setVisible(true); 468 longAddrButton.setText(Bundle.getMessage("LongAddress")); 469 longAddrButton.setToolTipText(Bundle.getMessage("ToolTipLongAddress")); 470 longAddrButton.setVisible(true); 471 offsetAddrCheckBox.setVisible(false); 472 addressLabel.setText(Bundle.getMessage("AddressLabel")); 473 if (longAddrButton.isSelected()) { 474 lowAddrLimit = 0; 475 highAddrLimit = 10239; 476 } else { 477 lowAddrLimit = 1; 478 highAddrLimit = 127; 479 } 480 } 481 482 log.debug("Setting lowAddrLimit={}, highAddrLimit={}", lowAddrLimit, highAddrLimit); 483 model.setMinimum(lowAddrLimit); 484 485 model.setMaximum(highAddrLimit); 486 int address; 487 488 try { 489 address = (Integer) mAddrField.getValue(); 490 } catch (java.lang.NumberFormatException e) { 491 log.debug("loco address \"{}\" not correct", mAddrField.getValue()); 492 return; 493 } 494 if (address < lowAddrLimit) { 495 mAddrField.setValue(lowAddrLimit); 496 } else if (address > highAddrLimit) { 497 mAddrField.setValue(highAddrLimit); 498 } 499 } 500 501// Free up memory from no longer needed stuff, disconnect if still connected. 502 @Override 503 public void dispose() { 504 } 505 506 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ProgOpsModePane.class.getName()); 507 508}