001package jmri.jmrix.openlcb.swing; 002 003import jmri.*; 004import jmri.jmrit.beantable.signalmast.SignalMastAddPane; 005import jmri.jmrit.catalog.NamedIcon; 006import jmri.SystemConnectionMemo; 007import jmri.util.ConnectionNameFromSystemName; 008 009import jmri.jmrix.ConnectionConfig; 010import jmri.jmrix.ConnectionConfigManager; 011import jmri.jmrix.can.CanSystemConnectionMemo; 012import jmri.jmrix.openlcb.*; 013 014import java.awt.*; 015import java.text.DecimalFormat; 016import java.util.*; 017import java.util.ArrayList; 018import java.util.List; 019 020import javax.swing.*; 021import javax.swing.border.TitledBorder; 022import javax.annotation.Nonnull; 023 024import org.openide.util.lookup.ServiceProvider; 025 026/** 027 * A pane for configuring OlcbSignalMast objects 028 * 029 * @see jmri.jmrit.beantable.signalmast.SignalMastAddPane 030 * @author Bob Jacobsen Copyright (C) 2018 031 * @since 4.11.2 032 */ 033public class OlcbSignalMastAddPane extends SignalMastAddPane { 034 035 public OlcbSignalMastAddPane() { 036 037 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 038 039 litEventID.setText("00.00.00.00.00.00.00.00"); 040 notLitEventID.setText("00.00.00.00.00.00.00.00"); 041 heldEventID.setText("00.00.00.00.00.00.00.00"); 042 notHeldEventID.setText("00.00.00.00.00.00.00.00"); 043 044 // populate the OpenLCB connections list before creating GUI components. 045 getOlcbConnections(); 046 047 // If the connections list has less than 2 items, don't show a selector. 048 // This maintains backward compatibility with previous versions. 049 if (olcbConnections != null && olcbConnections.size() > 1) { 050 // Connection selector 051 JPanel p = new JPanel(); 052 TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 053 border.setTitle(Bundle.getMessage("OlcbConnection")); 054 p.setBorder(border); 055 p.setLayout(new jmri.util.javaworld.GridLayout2(3, 1)); 056 057 p.add(connSelectionBox); 058 add(p); 059 } 060 061 // lit/unlit controls 062 JPanel p = new JPanel(); 063 p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); 064 p.add(new JLabel(Bundle.getMessage("AllowUnLitLabel") + ": ")); 065 p.add(allowUnLit); 066 p.setAlignmentX(Component.LEFT_ALIGNMENT); 067 add(p); 068 069 // aspects controls 070 TitledBorder aspectsBorder = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 071 aspectsBorder.setTitle(Bundle.getMessage("EnterAspectsLabel")); 072 JScrollPane allAspectsScroll = new JScrollPane(allAspectsPanel); 073 allAspectsScroll.setBorder(aspectsBorder); 074 add(allAspectsScroll); 075 076 JPanel p5; 077 078 // Lit 079 TitledBorder litborder = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 080 litborder.setTitle(Bundle.getMessage("LitUnLit")); 081 JPanel pLit = new JPanel(); 082 pLit.setBorder(litborder); 083 pLit.setLayout(new BoxLayout(pLit, BoxLayout.Y_AXIS)); 084 085 p5 = new JPanel(); 086 p5.setLayout(new BoxLayout(p5, BoxLayout.X_AXIS)); 087 p5.add(new JLabel(Bundle.getMessage("LitLabel"))); 088 p5.add(Box.createHorizontalGlue()); 089 pLit.add(p5); 090 pLit.add(litEventID); 091 092 p5 = new JPanel(); 093 p5.setLayout(new BoxLayout(p5, BoxLayout.X_AXIS)); 094 p5.add(new JLabel(Bundle.getMessage("NotLitLabel"))); 095 p5.add(Box.createHorizontalGlue()); 096 pLit.add(p5); 097 pLit.add(notLitEventID); 098 099 add(pLit); 100 101 // Held 102 TitledBorder heldborder = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 103 heldborder.setTitle(Bundle.getMessage("HeldUnHeld")); 104 JPanel pHeld= new JPanel(); 105 pHeld.setBorder(heldborder); 106 pHeld.setLayout(new BoxLayout(pHeld, BoxLayout.Y_AXIS)); 107 108 p5 = new JPanel(); 109 p5.setLayout(new BoxLayout(p5, BoxLayout.X_AXIS)); 110 p5.add(new JLabel(Bundle.getMessage("HeldLabel"))); 111 p5.add(Box.createHorizontalGlue()); 112 pHeld.add(p5); 113 pHeld.add(heldEventID); 114 115 p5 = new JPanel(); 116 p5.setLayout(new BoxLayout(p5, BoxLayout.X_AXIS)); 117 p5.add(new JLabel(Bundle.getMessage("NotHeldLabel"))); 118 p5.add(Box.createHorizontalGlue()); 119 pHeld.add(p5); 120 pHeld.add(notHeldEventID); 121 122 add(pHeld); 123 124 // set up selection of connections, if needed 125 populateConnSelectionBox(); 126 127 } 128 129 /** {@inheritDoc} */ 130 @Override 131 @Nonnull public String getPaneName() { 132 return Bundle.getMessage("OlcbSignalMastPane"); 133 } 134 135 final JCheckBox allowUnLit = new JCheckBox(); 136 137 CanSystemConnectionMemo memo = InstanceManager.getDefault(CanSystemConnectionMemo.class); 138 // needs to be done at ctor time; static would be initialized too soon 139 140 // This used to be called "disabledAspects", but that's misleading: it's actually a map of 141 // ALL aspects' "disabled" checkboxes, regardless of their enabled/disabled state. 142 LinkedHashMap<String, JCheckBox> allAspectsCheckBoxes = new LinkedHashMap<>(NOTIONAL_ASPECT_COUNT); 143 final LinkedHashMap<String, NamedEventIdTextField> aspectEventIDs = new LinkedHashMap<>(NOTIONAL_ASPECT_COUNT); 144 final JPanel allAspectsPanel = new JPanel(); 145 final NamedEventIdTextField litEventID = new NamedEventIdTextField(memo); 146 final NamedEventIdTextField notLitEventID = new NamedEventIdTextField(memo); 147 final NamedEventIdTextField heldEventID = new NamedEventIdTextField(memo); 148 final NamedEventIdTextField notHeldEventID = new NamedEventIdTextField(memo); 149 150 JComboBox<String> connSelectionBox = new JComboBox<String>(); 151 152 OlcbSignalMast currentMast = null; 153 154 // Support for multiple OpenLCB connections with different prefixes 155 ArrayList<String> olcbConnections = null; 156 157 /** {@inheritDoc} */ 158 @Override 159 public void setAspectNames(@Nonnull SignalAppearanceMap map, @Nonnull SignalSystem sigSystem) { 160 Enumeration<String> aspectNames = map.getAspects(); 161 // update immediately 162 allAspectsCheckBoxes = new LinkedHashMap<>(NOTIONAL_ASPECT_COUNT); 163 allAspectsPanel.removeAll(); 164 while (aspectNames.hasMoreElements()) { 165 String aspectName = aspectNames.nextElement(); 166 JCheckBox disabled = new JCheckBox(aspectName); 167 allAspectsCheckBoxes.put(aspectName, disabled); 168 NamedEventIdTextField eventID = new NamedEventIdTextField(memo); 169 eventID.setText("00.00.00.00.00.00.00.00"); 170 aspectEventIDs.put(aspectName, eventID); 171 } 172 allAspectsPanel.setLayout(new BoxLayout(allAspectsPanel, BoxLayout.Y_AXIS)); 173 for (Map.Entry<String, JCheckBox> entry : allAspectsCheckBoxes.entrySet()) { 174 JPanel p1 = new JPanel(); 175 TitledBorder p1border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 176 p1border.setTitle(entry.getKey()); // Note this is not I18N'd: as-is from xml file 177 p1.setBorder(p1border); 178 p1.setLayout(new BoxLayout(p1, BoxLayout.X_AXIS)); 179 180 // Attempt to load an icon for display 181 String iconLink = map.getImageLink(entry.getKey(), "default"); 182 if (iconLink == null || iconLink.isEmpty()) { 183 log.debug("Got empty image link for {}", entry.getKey()); 184 } else { 185 log.debug("Image link for {} is {}", entry.getKey(), iconLink); 186 if (!iconLink.contains("preference:")) { 187 // This logic copied from SignalMastItemPanel.java 188 iconLink = iconLink.substring(iconLink.indexOf("resources")); 189 } 190 NamedIcon n = null; 191 try { 192 n = new NamedIcon(iconLink, iconLink); 193 log.debug("Loaded icon {}", iconLink); 194 } catch (Exception e) { 195 log.debug("Got exception trying to load icon link {}: {}", iconLink, e.getMessage()); 196 } 197 // display icon 198 if (n != null) { 199 p1.add(new JLabel(n)); 200 } 201 } 202 203 JPanel p2 = new JPanel(); 204 p2.setLayout(new BoxLayout(p2, BoxLayout.Y_AXIS)); 205 // event ID text box 206 p2.add(aspectEventIDs.get(entry.getKey())); 207 // "Disable" checkbox 208 p2.add(entry.getValue()); 209 entry.getValue().setName(entry.getKey()); 210 entry.getValue().setText(Bundle.getMessage("DisableAspect")); 211 p1.add(p2); 212 allAspectsPanel.add(p1); 213 } 214 215 populateConnSelectionBox(); 216 217 litEventID.setText("00.00.00.00.00.00.00.00"); 218 notLitEventID.setText("00.00.00.00.00.00.00.00"); 219 heldEventID.setText("00.00.00.00.00.00.00.00"); 220 notHeldEventID.setText("00.00.00.00.00.00.00.00"); 221 222 allAspectsPanel.revalidate(); 223 } 224 225 /* 226 * Populate the list of valid OpenLCB connection names. 227 * Only connections that have "OpenLCB" or "LCC" as their manufacturer are added. 228 */ 229 private void getOlcbConnections() { 230 olcbConnections = null; 231 ConnectionConfig[] conns = null; 232 try { 233 conns = InstanceManager.getDefault(ConnectionConfigManager.class).getConnections(); 234 } catch (Exception e) { 235 log.info("No ConnectionConfigManager installed: Using default Olcb Connections"); 236 } 237 if (conns == null || conns.length == 0) { 238 log.debug("Found null or empty connections list"); 239 return; 240 } 241 for (int x = 0; x < conns.length; x++) { 242 ConnectionConfig cc = conns[x]; 243 log.debug("conns[{}]: name={} info={} adapter={} conn={} man={}", 244 x, cc.name(), cc.getInfo(), cc.getAdapter(), 245 cc.getConnectionName(), cc.getManufacturer()); 246 /* As this is the Olcb signal mast add pane, only show OpenLCB/LCC connections */ 247 String man = cc.getManufacturer(); 248 String name = cc.getConnectionName(); 249 if (man != null && name != null && !name.isEmpty() && 250 (man.equals("OpenLCB") || man.equals("LCC"))) { 251 if (olcbConnections == null) { 252 olcbConnections = new ArrayList<String>(); 253 } 254 olcbConnections.add(name); 255 } 256 } 257 } 258 259 /* 260 * Populate the GUI connection selection box, if there are 261 * multiple OpenLCB connections. 262 */ 263 private void populateConnSelectionBox() { 264 connSelectionBox.removeAllItems(); 265 if (olcbConnections == null || olcbConnections.size() < 2) { 266 return; 267 } 268 for (String conn : olcbConnections) { 269 connSelectionBox.addItem(conn); 270 } 271 if (currentMast == null) { 272 connSelectionBox.setEnabled(true); 273 return; 274 } 275 // set the selected connection based on the current mast 276 String mastPrefix = currentMast.getSystemPrefix(); 277 if (mastPrefix != null) { 278 for (String conn : olcbConnections) { 279 String connectionPrefix = ConnectionNameFromSystemName.getPrefixFromName(conn); 280 if (connectionPrefix != null && connectionPrefix.equals(mastPrefix)) { 281 connSelectionBox.setSelectedItem(conn); 282 break; 283 } 284 } 285 } 286 // Can't change connection on existing masts 287 connSelectionBox.setEnabled(false); 288 } 289 290 /** {@inheritDoc} */ 291 @Override 292 public boolean canHandleMast(@Nonnull SignalMast mast) { 293 return mast instanceof OlcbSignalMast; 294 } 295 296 /** {@inheritDoc} */ 297 @Override 298 public void setMast(SignalMast mast) { 299 if (mast == null) { 300 currentMast = null; 301 // re-enable connection selector 302 populateConnSelectionBox(); 303 return; 304 } 305 306 if (! (mast instanceof OlcbSignalMast) ) { 307 log.error("mast was wrong type: {} {}", mast.getSystemName(), mast.getClass().getName()); 308 return; 309 } 310 311 currentMast = (OlcbSignalMast) mast; 312 List<String> disabledList = currentMast.getDisabledAspects(); 313 if (disabledList != null) { 314 for (String aspect : disabledList) { 315 if (allAspectsCheckBoxes.containsKey(aspect)) { 316 allAspectsCheckBoxes.get(aspect).setSelected(true); 317 } 318 } 319 } 320 for (String aspect : currentMast.getAllKnownAspects()) { 321 if (aspectEventIDs.get(aspect) == null) { 322 NamedEventIdTextField eventID = new NamedEventIdTextField(memo); 323 eventID.setText("00.00.00.00.00.00.00.00"); 324 aspectEventIDs.put(aspect, eventID); 325 } 326 if (currentMast.isOutputConfigured(aspect)) { 327 aspectEventIDs.get(aspect).setText(currentMast.getOutputForAppearance(aspect)); 328 } else { 329 aspectEventIDs.get(aspect).setText("00.00.00.00.00.00.00.00"); 330 } 331 } 332 333 litEventID.setText(currentMast.getLitEventId()); 334 notLitEventID.setText(currentMast.getNotLitEventId()); 335 heldEventID.setText(currentMast.getHeldEventId()); 336 notHeldEventID.setText(currentMast.getNotHeldEventId()); 337 338 allowUnLit.setSelected(currentMast.allowUnLit()); 339 340 // show current connection in selector 341 populateConnSelectionBox(); 342 343 log.debug("setMast({})", mast); 344 } 345 346 final DecimalFormat paddedNumber = new DecimalFormat("0000"); 347 348 /** {@inheritDoc} */ 349 @Override 350 public boolean createMast(@Nonnull String sigsysname, 351 @Nonnull String mastname, 352 @Nonnull String username) { 353 if (currentMast == null) { 354 // create a mast 355 String selItem = (String)connSelectionBox.getSelectedItem(); 356 String connectionPrefix = ConnectionNameFromSystemName.getPrefixFromName(selItem); 357 log.debug("selected name={}, prefix={}", selItem, connectionPrefix); 358 // if prefix is null, use default 359 if (connectionPrefix == null || connectionPrefix.isEmpty()) { 360 connectionPrefix = "M"; 361 } 362 363 String type = mastname.substring(11, mastname.length() - 4); 364 String name = connectionPrefix + "F$olm:" + sigsysname + ":" + type; 365 name += "($" + (paddedNumber.format(OlcbSignalMast.getLastRef() + 1)) + ")"; 366 log.debug("Creating mast: {}", name); 367 currentMast = new OlcbSignalMast(name); 368 if (!username.equals("")) { 369 currentMast.setUserName(username); 370 } 371 currentMast.setMastType(type); 372 InstanceManager.getDefault(jmri.SignalMastManager.class).register(currentMast); 373 } 374 375 // load a new or existing mast 376 for (Map.Entry<String, JCheckBox> entry : allAspectsCheckBoxes.entrySet()) { 377 if (entry.getValue().isSelected()) { 378 currentMast.setAspectDisabled(entry.getKey()); 379 } else { 380 currentMast.setAspectEnabled(entry.getKey()); 381 } 382 currentMast.setOutputForAppearance(entry.getKey(), aspectEventIDs.get(entry.getKey()).getText()); 383 } 384 385 currentMast.setLitEventId(litEventID.getText()); 386 currentMast.setNotLitEventId(notLitEventID.getText()); 387 currentMast.setHeldEventId(heldEventID.getText()); 388 currentMast.setNotHeldEventId(notHeldEventID.getText()); 389 390 currentMast.setAllowUnLit(allowUnLit.isSelected()); 391 return true; 392 } 393 394 395 @ServiceProvider(service = SignalMastAddPane.SignalMastAddPaneProvider.class) 396 static public class SignalMastAddPaneProvider extends SignalMastAddPane.SignalMastAddPaneProvider { 397 398 /** 399 * {@inheritDoc} 400 * Requires a valid OpenLCB connection 401 */ 402 @Override 403 public boolean isAvailable() { 404 for (SystemConnectionMemo memo : InstanceManager.getList(SystemConnectionMemo.class)) { 405 if (memo instanceof jmri.jmrix.can.CanSystemConnectionMemo) { 406 return true; 407 } 408 } 409 return false; 410 } 411 412 /** {@inheritDoc} */ 413 @Override 414 @Nonnull public String getPaneName() { 415 return Bundle.getMessage("OlcbSignalMastPane"); 416 } 417 418 /** {@inheritDoc} */ 419 @Override 420 @Nonnull public SignalMastAddPane getNewPane() { 421 return new OlcbSignalMastAddPane(); 422 } 423 } 424 425 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OlcbSignalMastAddPane.class); 426 427}