001package jmri.jmrit.beantable.signalmast; 002 003import java.awt.*; 004import java.awt.event.*; 005import java.util.*; 006import java.util.List; 007 008import javax.annotation.Nonnull; 009import javax.swing.*; 010import javax.swing.border.TitledBorder; 011 012import jmri.*; 013import jmri.implementation.DccSignalMast; 014import jmri.util.*; 015import jmri.util.swing.JmriJOptionPane; 016 017import org.openide.util.lookup.ServiceProvider; 018 019/** 020 * A pane for configuring DCC SignalMast objects. 021 * 022 * @see jmri.jmrit.beantable.signalmast.SignalMastAddPane 023 * @author Bob Jacobsen Copyright (C) 2018 024 * @since 4.11.2 025 */ 026public class DccSignalMastAddPane extends SignalMastAddPane { 027 028 public DccSignalMastAddPane() { 029 init(); 030 } 031 032 final void init() { 033 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 034 035 add(unLitOption()); 036 add(connectionData()); 037 038 dccMastScroll = new JScrollPane(dccMastPanel); 039 dccMastScroll.setBorder(BorderFactory.createEmptyBorder()); 040 add(dccMastScroll); 041 } 042 043 JPanel connectionData() { 044 JPanel p = new JPanel(); 045 046 TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 047 border.setTitle(Bundle.getMessage("DCCMastConnection")); 048 p.setBorder(border); 049 050 p.setLayout(new jmri.util.javaworld.GridLayout2(3, 3)); 051 052 p.add(systemPrefixBoxLabel); 053 p.add(systemPrefixBox); 054 p.add(new JLabel()); // Empty 1,3 cell 055 056 p.add(dccAspectAddressLabel); 057 dccAspectAddressField.setText(""); 058 dccOffSetAddress.setToolTipText(Bundle.getMessage("DccOffsetTooltip")); 059 p.add(dccAspectAddressField); 060 p.add(dccOffSetAddress); 061 062 p.add(new JLabel(Bundle.getMessage("DCCMastPacketSendCount"))); 063 packetSendCountSpinner.setModel(new SpinnerNumberModel(3, 1, 4, 1)); 064 p.add(packetSendCountSpinner); 065 packetSendCountSpinner.setToolTipText(Bundle.getMessage("DCCMastPacketSendCountToolTip")); 066 067 return p; 068 } 069 070 JPanel unLitOption() { 071 JPanel p = new JPanel(); 072 073 p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); 074 p.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("AllowUnLitLabel")))); 075 p.add(allowUnLit); 076 077 p.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("DCCUnlitAspectId")))); 078 unlitIdSpinner.setModel(new SpinnerNumberModel(31, 0, 31, 1)); 079 p.add(unlitIdSpinner); 080 081 return p; 082 } 083 084 /** {@inheritDoc} */ 085 @Override 086 @Nonnull public String getPaneName() { 087 return Bundle.getMessage("DCCMast"); 088 } 089 090 JScrollPane dccMastScroll; 091 JPanel dccMastPanel = new JPanel(); 092 093 JLabel systemPrefixBoxLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("DCCSystem"))); 094 JComboBox<String> systemPrefixBox = new JComboBox<>(); 095 096 JLabel dccAspectAddressLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("DCCMastAddress"))); 097 JTextField dccAspectAddressField = new JTextField(5); 098 099 JCheckBox dccOffSetAddress = new JCheckBox(Bundle.getMessage("DccAccessoryAddressOffSet")); 100 101 JCheckBox allowUnLit = new JCheckBox(); 102// JTextField unLitAspectField = new JTextField(5); 103 104 LinkedHashMap<String, DCCAspectPanel> dccAspect = new LinkedHashMap<>(NOTIONAL_ASPECT_COUNT); 105 106 DccSignalMast currentMast = null; 107 SignalSystem sigsys; 108 /* IMM Send Count */ 109 JSpinner packetSendCountSpinner = new JSpinner(); 110 JSpinner unlitIdSpinner = new JSpinner(); 111 112 /** 113 * Check if a command station will work for this subtype. 114 * @param cs The current command station. 115 * @return true if cs supports IMM packets. 116 */ 117 protected boolean usableCommandStation(CommandStation cs) { 118 return true; 119 } 120 121 /** {@inheritDoc} */ 122 @Override 123 public void setAspectNames(@Nonnull SignalAppearanceMap map, 124 @Nonnull SignalSystem sigSystem) { 125 log.trace("setAspectNames(...) start"); 126 127 dccAspect.clear(); 128 129 Enumeration<String> aspects = map.getAspects(); 130 sigsys = map.getSignalSystem(); 131 132 while (aspects.hasMoreElements()) { 133 String aspect = aspects.nextElement(); 134 DCCAspectPanel aPanel = new DCCAspectPanel(aspect); 135 dccAspect.put(aspect, aPanel); 136 log.trace(" in loop, dccAspect: {} ", map.getProperty(aspect, "dccAspect")); 137 aPanel.setAspectId((String) sigSystem.getProperty(aspect, "dccAspect")); 138 } 139 140 systemPrefixBox.removeAllItems(); 141 List<CommandStation> connList = InstanceManager.getList(CommandStation.class); 142 if (!connList.isEmpty()) { 143 for (int x = 0; x < connList.size(); x++) { 144 CommandStation station = connList.get(x); 145 if (usableCommandStation(station)) { 146 systemPrefixBox.addItem(station.getUserName()); 147 } 148 } 149 } else { 150 systemPrefixBox.addItem("None"); 151 } 152 153 dccMastPanel.removeAll(); 154 155 for (Map.Entry<String, DCCAspectPanel> entry : dccAspect.entrySet()) { 156 log.trace(" aspect: {}", entry.getKey()); 157 dccMastPanel.add(entry.getValue().getPanel()); 158 } 159 160 if (dccAspect.size() % 2 > 0) { 161 dccMastPanel.add(new JLabel()); // finish odd number aspect list 162 } 163 164 dccMastPanel.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("DCCMastCopyAspectId")))); 165 dccMastPanel.add(copyFromMastSelection()); 166 167 dccMastPanel.setLayout(new jmri.util.javaworld.GridLayout2(0, 2)); // 0 means enough 168 dccMastPanel.revalidate(); 169 dccMastScroll.revalidate(); 170 171 log.trace("setAspectNames(...) end"); 172 } 173 174 /** {@inheritDoc} */ 175 @Override 176 public boolean canHandleMast(@Nonnull SignalMast mast) { 177 // because that mast can be subtyped by something 178 // completely different, we text for exact here. 179 return mast.getClass().getCanonicalName().equals(DccSignalMast.class.getCanonicalName()); 180 } 181 182 /** {@inheritDoc} */ 183 @Override 184 public void setMast(SignalMast mast) { 185 log.debug("setMast({}) start", mast); 186 if (mast == null) { 187 currentMast = null; 188 log.debug("setMast() end early with null"); 189 return; 190 } 191 192 if (! (mast instanceof DccSignalMast) ) { 193 log.error("mast was wrong type: {} {}", mast.getSystemName(), mast.getClass().getName()); 194 log.debug("setMast({}) end early: wrong type", mast); 195 return; 196 } 197 198 currentMast = (DccSignalMast) mast; 199 SignalAppearanceMap appMap = mast.getAppearanceMap(); 200 201 if (appMap != null) { 202 Enumeration<String> aspects = appMap.getAspects(); 203 while (aspects.hasMoreElements()) { 204 String key = aspects.nextElement(); 205 DCCAspectPanel dccPanel = dccAspect.get(key); 206 dccPanel.setAspectDisabled(currentMast.isAspectDisabled(key)); 207 if (!currentMast.isAspectDisabled(key)) { 208 dccPanel.setAspectId(currentMast.getOutputForAppearance(key)); 209 } 210 } 211 } 212 List<CommandStation> connList = InstanceManager.getList(CommandStation.class); 213 if (!connList.isEmpty()) { 214 for (int x = 0; x < connList.size(); x++) { 215 CommandStation station = connList.get(x); 216 if (usableCommandStation(station)) { 217 systemPrefixBox.addItem(station.getUserName()); 218 } 219 } 220 } else { 221 systemPrefixBox.addItem("None"); 222 } 223 dccAspectAddressField.setText("" + currentMast.getDccSignalMastAddress()); 224 dccOffSetAddress.setSelected(currentMast.useAddressOffSet()); 225 systemPrefixBox.setSelectedItem(currentMast.getCommandStation().getUserName()); 226 227 systemPrefixBoxLabel.setEnabled(false); 228 systemPrefixBox.setEnabled(false); 229 dccAspectAddressLabel.setEnabled(false); 230 dccAspectAddressField.setEnabled(false); 231 232 allowUnLit.setSelected(currentMast.allowUnLit()); 233 if (currentMast.allowUnLit()) { 234 unlitIdSpinner.setValue(currentMast.getUnlitId()); 235 } 236 237 // set up DCC IMM send count 238 packetSendCountSpinner.setValue(currentMast.getDccSignalMastPacketSendCount()); 239 log.debug("setMast({}) end", mast); 240 } 241 242 static boolean validateAspectId(@Nonnull String strAspect) { 243 int aspect; 244 try { 245 aspect = Integer.parseInt(strAspect.trim()); 246 } catch (java.lang.NumberFormatException e) { 247 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("DCCMastAspectNumber")); 248 return false; 249 } 250 if (aspect < 0 || aspect > 31) { 251 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("DCCMastAspectOutOfRange")); 252 log.error("invalid aspect {}", aspect); 253 return false; 254 } 255 return true; 256 } 257 258 /** 259 * Get the first part of the system name 260 * for the specific mast type. 261 * @return For this specific class, "F$dsm:" 262 */ 263 protected @Nonnull String getNamePrefix() { 264 return "F$dsm:"; 265 } 266 267 /** 268 * Create a mast of the specific subtype. 269 * @param name A valid subtype name 270 * @return A SignalMast of that subtype 271 */ 272 protected DccSignalMast constructMast(@Nonnull String name) { 273 return new DccSignalMast(name); 274 } 275 276 /** {@inheritDoc} */ 277 @Override 278 public boolean createMast(@Nonnull 279 String sigsysname, @Nonnull 280 String mastname, @Nonnull 281 String username) { 282 log.debug("createMast({},{} start)", sigsysname, mastname); 283 284 // are we already editing? If no, create a new one. 285 if (currentMast == null) { 286 log.trace("Creating new mast"); 287 if (!validateDCCAddress()) { 288 log.trace("validateDCCAddress failed, return from createMast"); 289 return false; 290 } 291 String systemNameText = ConnectionNameFromSystemName.getPrefixFromName((String) systemPrefixBox.getSelectedItem()); 292 // if we return a null string then we will set it to use internal, thus picking up the default command station at a later date. 293 if (systemNameText == null || systemNameText.isEmpty()) { 294 systemNameText = "I"; 295 } 296 systemNameText = systemNameText + getNamePrefix(); 297 298 String name = systemNameText 299 + sigsysname 300 + ":" + mastname.substring(11, mastname.length() - 4); 301 name += "(" + dccAspectAddressField.getText() + ")"; 302 currentMast = constructMast(name); 303 InstanceManager.getDefault(SignalMastManager.class).register(currentMast); 304 } 305 306 for (Map.Entry<String, DCCAspectPanel> entry : dccAspect.entrySet()) { 307 dccMastPanel.add(entry.getValue().getPanel()); // update mast from aspect subpanel panel 308 currentMast.setOutputForAppearance(entry.getKey(), entry.getValue().getAspectId()); 309 if (entry.getValue().isAspectDisabled()) { 310 currentMast.setAspectDisabled(entry.getKey()); 311 } else { 312 currentMast.setAspectEnabled(entry.getKey()); 313 } 314 } 315 if (!username.isEmpty()) { 316 currentMast.setUserName(username); 317 } 318 319 currentMast.useAddressOffSet(dccOffSetAddress.isSelected()); 320 currentMast.setAllowUnLit(allowUnLit.isSelected()); 321 if (allowUnLit.isSelected()) { 322 currentMast.setUnlitId((Integer) unlitIdSpinner.getValue()); 323 } 324 325 int sendCount = (Integer) packetSendCountSpinner.getValue(); // from a JSpinner with 1 set as minimum 4 max 326 currentMast.setDccSignalMastPacketSendCount(sendCount); 327 328 log.debug("createMast({},{} end)", sigsysname, mastname); 329 return true; 330 } 331 332 333 @ServiceProvider(service = SignalMastAddPane.SignalMastAddPaneProvider.class) 334 static public class SignalMastAddPaneProvider extends SignalMastAddPane.SignalMastAddPaneProvider { 335 /** {@inheritDoc} */ 336 @Override 337 @Nonnull public String getPaneName() { 338 return Bundle.getMessage("DCCMast"); 339 } 340 /** {@inheritDoc} */ 341 @Override 342 @Nonnull public SignalMastAddPane getNewPane() { 343 return new DccSignalMastAddPane(); 344 } 345 } 346 347 private boolean validateDCCAddress() { 348 if (dccAspectAddressField.getText().isEmpty()) { 349 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("DCCMastAddressBlank")); 350 return false; 351 } 352 int address; 353 try { 354 address = Integer.parseInt(dccAspectAddressField.getText().trim()); 355 } catch (java.lang.NumberFormatException e) { 356 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("DCCMastAddressNumber")); 357 return false; 358 } 359 360 if (address < NmraPacket.accIdLowLimit || address > NmraPacket.accIdAltHighLimit) { 361 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("DCCMastAddressOutOfRange")); 362 log.error("invalid address {}", address); 363 return false; 364 } 365 if (DccSignalMast.isDCCAddressUsed(address) != null) { 366 String msg = Bundle.getMessage("DCCMastAddressAssigned", new Object[]{dccAspectAddressField.getText(), DccSignalMast.isDCCAddressUsed(address)}); 367 JmriJOptionPane.showMessageDialog(this, msg); 368 return false; 369 } 370 return true; 371 } 372 373 @Nonnull JComboBox<String> copyFromMastSelection() { 374 JComboBox<String> mastSelect = new JComboBox<>(); 375 for (SignalMast mast : InstanceManager.getDefault(SignalMastManager.class).getNamedBeanSet()) { 376 if (mast instanceof DccSignalMast){ 377 mastSelect.addItem(mast.getDisplayName()); 378 } 379 } 380 if (mastSelect.getItemCount() == 0) { 381 mastSelect.setEnabled(false); 382 } else { 383 mastSelect.insertItemAt("", 0); 384 mastSelect.setSelectedIndex(0); 385 mastSelect.addActionListener((ActionEvent e) -> { 386 @SuppressWarnings("unchecked") // e.getSource() cast from mastSelect source 387 JComboBox<String> eb = (JComboBox<String>) e.getSource(); 388 String sourceMast = (String) eb.getSelectedItem(); 389 if (sourceMast != null && !sourceMast.isEmpty()) { 390 copyFromAnotherDCCMastAspect(sourceMast); 391 } 392 }); 393 } 394 return mastSelect; 395 } 396 397 /** 398 * Copy aspects by name from another DccSignalMast. 399 * @param strMast User or system name of mast to copy from 400 */ 401 void copyFromAnotherDCCMastAspect(@Nonnull String strMast) { 402 DccSignalMast mast = (DccSignalMast) InstanceManager.getDefault(SignalMastManager.class).getNamedBean(strMast); 403 if (mast == null) { 404 log.error("can't copy from another mast because {} doesn't exist", strMast); 405 return; 406 } 407 Vector<String> validAspects = mast.getValidAspects(); 408 for (Map.Entry<String, DCCAspectPanel> entry : dccAspect.entrySet()) { 409 if (validAspects.contains(entry.getKey()) || mast.isAspectDisabled(entry.getKey())) { // valid doesn't include disabled 410 // present, copy 411 entry.getValue().setAspectId(mast.getOutputForAppearance(entry.getKey())); 412 entry.getValue().setAspectDisabled(mast.isAspectDisabled(entry.getKey())); 413 } else { 414 // not present, log 415 log.info("Can't get aspect \"{}\" from head \"{}\", leaving unchanged", entry.getKey(), mast); 416 } 417 } 418 } 419 420 /** 421 * JPanel to define properties of an Aspect for a DCC Signal Mast. 422 * <p> 423 * Invoked from the {@link AddSignalMastPanel} class when a DCC Signal Mast is 424 * selected. 425 */ 426 static class DCCAspectPanel { 427 428 String aspect = ""; 429 JCheckBox disabledCheck = new JCheckBox(Bundle.getMessage("DisableAspect")); 430 JLabel aspectLabel = new JLabel(Bundle.getMessage("DCCMastSetAspectId") + ":"); 431 JTextField aspectId = new JTextField(5); 432 433 DCCAspectPanel(String aspect) { 434 this.aspect = aspect; 435 } 436 437 void setAspectDisabled(boolean boo) { 438 disabledCheck.setSelected(boo); 439 if (boo) { 440 aspectLabel.setEnabled(false); 441 aspectId.setEnabled(false); 442 } else { 443 aspectLabel.setEnabled(true); 444 aspectId.setEnabled(true); 445 } 446 } 447 448 boolean isAspectDisabled() { 449 return disabledCheck.isSelected(); 450 } 451 452 int getAspectId() { 453 try { 454 String value = aspectId.getText(); 455 return Integer.parseInt(value); 456 457 } catch (NumberFormatException ex) { 458 log.error("failed to convert DCC number"); 459 } 460 return -1; 461 } 462 463 void setAspectId(int i) { 464 aspectId.setText("" + i); 465 } 466 467 void setAspectId(String s) { 468 aspectId.setText(s); 469 } 470 471 JPanel panel; 472 473 JPanel getPanel() { 474 if (panel == null) { 475 panel = new JPanel(); 476 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 477 JPanel dccDetails = new JPanel(); 478 dccDetails.add(aspectLabel); 479 dccDetails.add(aspectId); 480 panel.add(dccDetails); 481 panel.add(disabledCheck); 482 TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 483 border.setTitle(aspect); 484 panel.setBorder(border); 485 aspectId.addFocusListener(new FocusListener() { 486 @Override 487 public void focusLost(FocusEvent e) { 488 if (aspectId.getText().isEmpty()) { 489 return; 490 } 491 if (!validateAspectId(aspectId.getText())) { 492 aspectId.requestFocusInWindow(); 493 } 494 } 495 496 @Override 497 public void focusGained(FocusEvent e) { 498 } 499 500 }); 501 disabledCheck.addActionListener((ActionEvent e) -> { 502 setAspectDisabled(disabledCheck.isSelected()); 503 }); 504 505 } 506 return panel; 507 } 508 } 509 510 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DccSignalMastAddPane.class); 511 512}