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