001package jmri.jmrix.loconet.cmdstnconfig; 002 003import java.awt.FlowLayout; 004import java.awt.GridBagConstraints; 005import java.awt.GridBagLayout; 006import java.awt.event.ActionEvent; 007import java.util.ResourceBundle; 008import javax.swing.BoxLayout; 009import javax.swing.ButtonGroup; 010import javax.swing.JButton; 011import javax.swing.JCheckBox; 012import javax.swing.JLabel; 013import javax.swing.JPanel; 014import javax.swing.JRadioButton; 015import javax.swing.JScrollPane; 016import javax.swing.ScrollPaneConstants; 017import jmri.jmrix.loconet.LnConstants; 018import jmri.jmrix.loconet.LocoNetListener; 019import jmri.jmrix.loconet.LocoNetMessage; 020import jmri.jmrix.loconet.LocoNetSystemConnectionMemo; 021import jmri.jmrix.loconet.swing.LnPanel; 022import org.slf4j.Logger; 023import org.slf4j.LoggerFactory; 024 025/** 026 * User interface for Command Station Option Programming. 027 * <p> 028 * Some of the message formats used in this class are Copyright Digitrax, Inc. 029 * and used with permission as part of the JMRI project. That permission does 030 * not extend to uses in other software products. If you wish to use this code, 031 * algorithm or these message formats outside of JMRI, please contact Digitrax 032 * Inc for separate permission. 033 * 034 * @author Alex Shepherd Copyright (C) 2004 035 * @author Bob Jacobsen Copyright (C) 2006 036 */ 037public class CmdStnConfigPane extends LnPanel implements LocoNetListener { 038 039 int CONFIG_SLOT = 127; 040 int MIN_OPTION = 1; 041 int MAX_OPTION = 128; 042 int CONFIG_SLOT2 = 126; 043 044 String labelT; 045 String labelC; 046 String labelTop; 047 String read; 048 String write; 049 050 int[] oldcontent = new int[10]; 051 int[] oldcontent2 = new int[10]; 052 053 JCheckBox optionBox; 054 055 ResourceBundle rb; 056 // internal members to hold widgets 057 JButton readButton; 058 JButton writeButton; 059 060 JRadioButton[] closedButtons = new JRadioButton[MAX_OPTION]; 061 JRadioButton[] thrownButtons = new JRadioButton[MAX_OPTION]; 062 JLabel[] labels = new JLabel[MAX_OPTION]; 063 boolean[] isReserved = new boolean[MAX_OPTION]; 064 065 /** 066 * Create a new instance of a Command Station Configuration Pane 067 */ 068 public CmdStnConfigPane() { 069 super(); 070 } 071 072 @Override 073 public String getHelpTarget() { 074 return "package.jmri.jmrix.loconet.cmdstnconfig.CmdStnConfigFrame"; // NOI18N 075 } 076 077 @Override 078 public String getTitle() { 079 String uName = ""; 080 if (memo != null) { 081 uName = memo.getUserName(); 082 if (!"LocoNet".equals(uName)) { // NOI18N 083 uName = uName + ": "; // NOI18N 084 } else { 085 uName = ""; 086 } 087 } 088 return uName + Bundle.getMessage("MenuItemCmdStnConfig"); 089 } 090 091 @Override 092 public void initComponents(LocoNetSystemConnectionMemo memo) { 093 super.initComponents(memo); 094 095 // set up constants from properties file, if possible 096 String name = "<unchanged>"; // NOI18N 097 try { 098 name = memo.getSlotManager().getCommandStationType().getName(); 099 // get first token 100 if (name.indexOf(' ') != -1) { 101 name = name.substring(0, name.indexOf(' ')); 102 } 103 name = name.replace("+", "Plus"); 104 log.debug("match /{}/", name); // NOI18N 105 rb = ResourceBundle.getBundle("jmri.jmrix.loconet.cmdstnconfig." + name + "options"); // NOI18N 106 } catch (Exception e) { // use standard option set 107 log.warn("Failed to find properties for /{}/ command station type", name, e); // NOI18N 108 rb = ResourceBundle.getBundle("jmri.jmrix.loconet.cmdstnconfig.Defaultoptions"); // NOI18N 109 // Localized strings common to all LocoNet command station models 110 // are fetched using Bundle.getMessage() 111 } 112 113 try { 114 CONFIG_SLOT = Integer.parseInt(rb.getString("CONFIG_SLOT")); 115 MIN_OPTION = Integer.parseInt(rb.getString("MIN_OPTION")); 116 MAX_OPTION = Integer.parseInt(rb.getString("MAX_OPTION")); 117 if (MAX_OPTION > 64) { 118 CONFIG_SLOT2 = Integer.parseInt(rb.getString("CONFIG_SLOT2")); 119 } 120 } catch (NumberFormatException e) { 121 log.error("Failed to load values from /{}/ properties", name); // NOI18N 122 } 123 log.debug("Constants: {} {} {}", CONFIG_SLOT, MIN_OPTION, MAX_OPTION); // NOI18N 124 125 labelT = Bundle.getMessage("StateThrownShort"); 126 labelC = Bundle.getMessage("StateClosedShort"); 127 labelTop = rb.getString("LabelTop"); 128 read = Bundle.getMessage("ButtonRead"); 129 write = Bundle.getMessage("ButtonWrite"); 130 String tooltip = Bundle.getMessage("CmdStnConfigFxToolTip"); 131 132 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 133 134 { 135 // start with the CS title 136 add(new JLabel(labelTop)); 137 138 // section holding buttons 139 readButton = new JButton(read); 140 writeButton = new JButton(write); 141 142 JPanel pane = new JPanel(); 143 pane.setLayout(new FlowLayout()); 144 pane.add(readButton); 145 pane.add(writeButton); 146 if (CONFIG_SLOT == -1) { // disable reading/writing for 147 // non-configurable CS types, ie. 148 // Intellibox-I/-II 149 readButton.setEnabled(false); 150 writeButton.setEnabled(false); 151 } 152 add(pane); 153 154 optionBox = new JCheckBox(Bundle.getMessage("CheckBoxReserved")); 155 add(optionBox); 156 157 // heading 158 add(new JLabel(Bundle.getMessage("HeadingText"))); 159 160 // section holding options 161 JPanel options = new JPanel(); 162 GridBagConstraints gc = new GridBagConstraints(); 163 GridBagLayout gl = new GridBagLayout(); 164 gc.gridy = 0; 165 gc.ipady = 0; 166 167 options.setLayout(gl); 168 for (int i = MIN_OPTION; i <= MAX_OPTION; i++) { 169 JPanel p2 = new JPanel(); 170 p2.setLayout(new FlowLayout()); 171 ButtonGroup g = new ButtonGroup(); 172 JRadioButton c = new JRadioButton(labelC); 173 JRadioButton t = new JRadioButton(labelT); 174 g.add(c); 175 g.add(t); 176 177 p2.add(t); 178 p2.add(c); 179 closedButtons[i - MIN_OPTION] = c; 180 thrownButtons[i - MIN_OPTION] = t; 181 gc.weightx = 1.0; 182 gc.gridx = 0; 183 gc.anchor = GridBagConstraints.CENTER; 184 gl.setConstraints(p2, gc); 185 options.add(p2); 186 gc.gridx = 1; 187 gc.weightx = GridBagConstraints.REMAINDER; 188 gc.anchor = GridBagConstraints.WEST; 189 String label; 190 try { 191 label = rb.getString("Option" + i); // model specific Option 192 // descriptions NOI18N 193 isReserved[i - MIN_OPTION] = false; 194 } catch (java.util.MissingResourceException e) { 195 label = "" + i + ": " + Bundle.getMessage("Reserved"); 196 isReserved[i - MIN_OPTION] = true; 197 } 198 JLabel l = new JLabel(label); 199 if (i > 20 && i < 24) { 200 log.debug("CS name: {}", name); 201 if (name.startsWith("DB150")) { 202 // DB150 is the only model using different OpSw 21-23 203 // combos than the common tooltip, which is stored in 204 // LocoNetBundle 205 tooltip = rb.getString("DB150ConfigFxToolTip"); 206 } else if (name.startsWith("DCS52")) { 207 tooltip = rb.getString("DCS52ConfigFxToolTip"); 208 } else if (name.startsWith("DCS240Plus")) { 209 tooltip = rb.getString("DCS240PlusConfigFxToolTip"); 210 } else if (name.startsWith("DCS240")) { 211 tooltip = rb.getString("DCS240ConfigFxToolTip"); 212 } else if (name.startsWith("DCS210Plus")) { 213 tooltip = rb.getString("DCS210PlusConfigFxToolTip"); 214 } else if (name.startsWith("DCS210")) { 215 tooltip = rb.getString("DCS210ConfigFxToolTip"); 216 } 217 t.setToolTipText(tooltip); 218 c.setToolTipText(tooltip); 219 l.setToolTipText(tooltip); 220 } 221 labels[i - MIN_OPTION] = l; 222 gl.setConstraints(l, gc); 223 options.add(l); 224 gc.gridy++; 225 } 226 JScrollPane js = new JScrollPane(options); 227 js.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); 228 js.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); 229 add(js); 230 231 } 232 233 optionBox.addActionListener((ActionEvent e) -> { 234 updateVisibility(optionBox.isSelected()); 235 }); 236 readButton.addActionListener((ActionEvent e) -> { 237 readButtonActionPerformed(e); 238 }); 239 writeButton.addActionListener((ActionEvent e) -> { 240 writeButtonActionPerformed(e); 241 }); 242 243 updateVisibility(optionBox.isSelected()); 244 245 // connect to the LnTrafficController 246 memo.getLnTrafficController().addLocoNetListener(~0, this); 247 248 // and start 249 start(); 250 } 251 252 void updateVisibility(boolean show) { 253 for (int i = MIN_OPTION; i <= MAX_OPTION; i++) { 254 if (isReserved[i - MIN_OPTION]) { 255 closedButtons[i - MIN_OPTION].setVisible(show); 256 thrownButtons[i - MIN_OPTION].setVisible(show); 257 labels[i - MIN_OPTION].setVisible(show); 258 } 259 } 260 revalidate(); 261 } 262 263 public void readButtonActionPerformed(java.awt.event.ActionEvent e) { 264 // format and send request 265 start(); 266 } 267 268 public void writeButtonActionPerformed(java.awt.event.ActionEvent e) { 269 270 updateSlot(CONFIG_SLOT, MIN_OPTION, MAX_OPTION <= 64 ? MAX_OPTION : 64, oldcontent); 271 if (MAX_OPTION > 64) { 272 updateSlot(CONFIG_SLOT2, 65, MAX_OPTION, oldcontent2); 273 } 274 } 275 276 public void updateSlot(int opSwSlot, int firstOpSw, int lastOpsw, int[] oldData) { 277 LocoNetMessage msg = new LocoNetMessage(14); 278 msg.setElement(0, LnConstants.OPC_WR_SL_DATA); 279 msg.setElement(1, 0x0E); 280 msg.setElement(2, opSwSlot); 281 282 // load last seen contents into message 283 for (int i = 0; i < 10; i++) { 284 msg.setElement(3 + i, oldData[i]); 285 } 286 287 int byteBase = (firstOpSw / 64) * 64; 288 // button 0 = opsw 1 289 for (int i = firstOpSw - 1; i <= lastOpsw - 1; i++) { 290 // i indexes over closed buttons - 1 291 int byteIndex = (i - byteBase) / 8; // byteIndex = 0 is the first 292 // payload byte 293 if (byteIndex > 3) { 294 byteIndex++; // Skip the 4th payload byte for some reason 295 } 296 byteIndex += 3; // Add base offset into slot message to first data 297 // byte 298 299 int bitIndex = i % 8; 300 int bitMask = 0x01 << bitIndex; 301 302 if (closedButtons[i].isSelected()) { 303 msg.setElement(byteIndex, msg.getElement(byteIndex) | bitMask); 304 } else { 305 msg.setElement(byteIndex, msg.getElement(byteIndex) & ~bitMask); 306 } 307 } 308 309 // send message 310 memo.getLnTrafficController().sendLocoNetMessage(msg); 311 } 312 313 /** 314 * Start the Frame operating by asking for a read. 315 */ 316 public void start() { 317 // format and send request for slot contents 318 LocoNetMessage l = new LocoNetMessage(4); 319 l.setElement(0, LnConstants.OPC_RQ_SL_DATA); 320 l.setElement(1, CONFIG_SLOT); 321 l.setElement(2, 0); 322 l.setElement(3, 0); 323 memo.getLnTrafficController().sendLocoNetMessage(l); 324 if (MAX_OPTION > 64) { 325 // need second slot 326 l.setElement(1, CONFIG_SLOT2); 327 memo.getLnTrafficController().sendLocoNetMessage(l); 328 } 329 } 330 331 /** 332 * Process the incoming message to look for Slot 127 Read. 333 */ 334 @Override 335 public void message(LocoNetMessage msg) { 336 if (msg.getOpCode() != LnConstants.OPC_SL_RD_DATA) { 337 return; 338 } 339 if (msg.getElement(2) == CONFIG_SLOT) { 340 // save contents for later 341 for (int i = 0; i < 10; i++) { 342 oldcontent[i] = msg.getElement(3 + i); 343 } 344 345 // set the GUI 346 int iLimit = MAX_OPTION <= 63 ? MAX_OPTION - 1 : 63; 347 for (int i = 0; i <= iLimit; i++) { 348 // i indexes over closed/thrown buttons 349 int byteIndex = i / 8; // index = 0 is the first payload byte 350 if (byteIndex > 3) { 351 byteIndex++; // Skip the 4th payload byte for some reason 352 } 353 byteIndex += 3; // Add base offset to first data byte 354 355 int bitIndex = i % 8; 356 int bitMask = 0x01 << bitIndex; 357 358 int data = msg.getElement(byteIndex); // data is the payload 359 // byte 360 if ((data & bitMask) != 0) { 361 closedButtons[i].setSelected(true); 362 } else { 363 thrownButtons[i].setSelected(true); 364 } 365 } 366 } else if (msg.getElement(2) == CONFIG_SLOT2 && MAX_OPTION > 64) { 367 368 // save contents for later 369 for (int i = 0; i < 10; i++) { 370 oldcontent2[i] = msg.getElement(3 + i); 371 } 372 373 // set the GUI for option sw 64 thru MAX 374 // note indexes are 0 based sor start at 63 375 376 for (int i = 63; i <= MAX_OPTION - 1; i++) { 377 // i indexes over closed/thrown buttons 378 int byteIndex = (i - 63) / 8; // index = 0 is the first payload 379 // byte 380 if (byteIndex > 3) { 381 byteIndex++; // Skip the 4th payload byte for some reason 382 } 383 byteIndex += 3; // Add base offset to first data byte 384 385 int bitIndex = i % 8; 386 int bitMask = 0x01 << bitIndex; 387 388 int data = msg.getElement(byteIndex); // data is the payload 389 // byte 390 if ((data & bitMask) != 0) { 391 closedButtons[i].setSelected(true); 392 } else { 393 thrownButtons[i].setSelected(true); 394 } 395 } 396 } else { 397 // do nothing 398 } 399 log.debug("Config Slot Data: {}", msg); 400 } 401 402 @Override 403 public void dispose() { 404 // disconnect from LnTrafficController 405 memo.getLnTrafficController().removeLocoNetListener(~0, this); 406 super.dispose(); 407 } 408 409 // initialize logging 410 private final static Logger log = LoggerFactory.getLogger(CmdStnConfigPane.class); 411 412}