001package jmri.jmrit.beantable.signalmast; 002 003import java.awt.*; 004import java.awt.event.*; 005import java.text.DecimalFormat; 006import java.util.*; 007import javax.annotation.Nonnull; 008import javax.swing.*; 009import javax.swing.border.TitledBorder; 010import jmri.*; 011import jmri.implementation.*; 012import jmri.util.*; 013import jmri.util.swing.*; 014 015import org.openide.util.lookup.ServiceProvider; 016 017/** 018 * A pane for configuring MatrixSignalMast objects. 019 * 020 * @see jmri.jmrit.beantable.signalmast.SignalMastAddPane 021 * @author Bob Jacobsen Copyright (C) 2018 022 * @author Egbert Broerse Copyright (C) 2016, 2019 023 * @since 4.11.2 024 */ 025public class MatrixSignalMastAddPane extends SignalMastAddPane { 026 027 public MatrixSignalMastAddPane() { 028 init(); 029 } 030 031 final void init() { 032 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 033 // lit/unlit controls 034 JPanel p = new JPanel(); 035 p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); 036 p.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("AllowUnLitLabel")))); 037 p.add(allowUnLit); 038 p.setAlignmentX(Component.LEFT_ALIGNMENT); 039 add(p); 040 041 add(matrixUnLitPanel); 042 matrixUnLitPanel(); 043 044 matrixMastBitnumPanel = makeMatrixMastBitnumPanel(); // create panel 045 add(matrixMastBitnumPanel); 046 if (prefs.getComboBoxLastSelection(matrixBitNumSelectionCombo) != null) { 047 columnChoice.setSelectedItem(prefs.getComboBoxLastSelection(matrixBitNumSelectionCombo)); // setting for bitNum 048 } 049 050 matrixMastScroll = new JScrollPane(matrixMastPanel); 051 matrixMastScroll.setBorder(BorderFactory.createEmptyBorder()); 052 add(matrixMastScroll); 053 } 054 055 private DefaultSignalAppearanceMap map; 056 private MatrixSignalMast currentMast; // mast being edited, null for new mast 057 private JCheckBox resetPreviousState = new JCheckBox(Bundle.getMessage("ResetPrevious")); 058 private UserPreferencesManager prefs = InstanceManager.getDefault(UserPreferencesManager.class); 059 private String matrixBitNumSelectionCombo = this.getClass().getName() + ".matrixBitNumSelected"; 060 private JCheckBox allowUnLit = new JCheckBox(); 061 062 private JScrollPane matrixMastScroll; 063 private JPanel matrixMastBitnumPanel; 064 private JPanel matrixMastPanel = new JPanel(); 065 // private char[] bitString; 066 private char[] unLitPanelBits; 067 private int numberOfActiveAspects; 068 069 private JLabel bitNumLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("MatrixBitsLabel"))); 070 private JComboBox<String> columnChoice = new JComboBox<>(choiceArray()); 071 private JSpinner timeDelay = new JSpinner(); 072 073 private LinkedHashMap<String, MatrixAspectPanel> matrixAspect = new LinkedHashMap<>(NOTIONAL_ASPECT_COUNT); 074 // LinkedHM type keeps things sorted // only used once, see updateMatrixAspectPanel() 075 076 private DecimalFormat paddedNumber = new DecimalFormat("0000"); 077 078 private String[] choiceArray() { 079 String[] numberOfOutputs = new String[MAXMATRIXBITS]; 080 for (int i = 0; i < MAXMATRIXBITS; i++) { 081 numberOfOutputs[i] = (i + 1) + ""; 082 } 083 log.debug("Created output combo box: {}", Arrays.toString(numberOfOutputs)); 084 return numberOfOutputs; 085 } 086 087 /** 088 * Set the maximum number of outputs for Matrix Signal Masts. 089 * Used in combobox and for loops. 090 */ 091 public static final int MAXMATRIXBITS = 10; // requires code changes to modify this value 092 093 private String emptyChars = "0000000000"; // size of String = MAXMATRIXBITS; extend if MAXMATRIXBITS changed 094 private char[] emptyBits = emptyChars.toCharArray(); 095 096 /** 097 * on = thrown, off = closed, no turnout states asked 098 */ 099 private BeanSelectCreatePanel<Turnout> turnoutBox1 = new BeanSelectCreatePanel<>(InstanceManager.turnoutManagerInstance(), null); 100 private BeanSelectCreatePanel<Turnout> turnoutBox2 = new BeanSelectCreatePanel<>(InstanceManager.turnoutManagerInstance(), null); 101 private BeanSelectCreatePanel<Turnout> turnoutBox3 = new BeanSelectCreatePanel<>(InstanceManager.turnoutManagerInstance(), null); 102 private BeanSelectCreatePanel<Turnout> turnoutBox4 = new BeanSelectCreatePanel<>(InstanceManager.turnoutManagerInstance(), null); 103 private BeanSelectCreatePanel<Turnout> turnoutBox5 = new BeanSelectCreatePanel<>(InstanceManager.turnoutManagerInstance(), null); 104 private BeanSelectCreatePanel<Turnout> turnoutBox6 = new BeanSelectCreatePanel<>(InstanceManager.turnoutManagerInstance(), null); 105 private BeanSelectCreatePanel<Turnout> turnoutBox7 = new BeanSelectCreatePanel<>(InstanceManager.turnoutManagerInstance(), null); 106 private BeanSelectCreatePanel<Turnout> turnoutBox8 = new BeanSelectCreatePanel<>(InstanceManager.turnoutManagerInstance(), null); 107 private BeanSelectCreatePanel<Turnout> turnoutBox9 = new BeanSelectCreatePanel<>(InstanceManager.turnoutManagerInstance(), null); 108 private BeanSelectCreatePanel<Turnout> turnoutBox10= new BeanSelectCreatePanel<>(InstanceManager.turnoutManagerInstance(), null); 109 // repeat in order to extend MAXMATRIXBITS 110 111 /** 112 * The number of columns in logic matrix. 113 * This is set when the user resizes the mast 114 */ 115 private int bitNum; 116 117 // ToDo: add boxes to set DCC Packets (with drop down selection "Output Type": Turnouts/Direct DCC Packets) 118 119 /** {@inheritDoc} */ 120 @Override 121 @Nonnull public String getPaneName() { 122 return Bundle.getMessage("MatrixCtlMast"); 123 } 124 125 /** {@inheritDoc} */ 126 @Override 127 public void setAspectNames(@Nonnull SignalAppearanceMap newMap, @Nonnull SignalSystem sigSystem) { 128 log.debug("setAspectNames(...)"); 129 130 unLitPanelBits = Arrays.copyOf(emptyBits, MAXMATRIXBITS); 131 map = (DefaultSignalAppearanceMap)newMap; 132 133 // set up rest of panel 134 updateMatrixMastPanel(); // show only the correct amount of columns for existing matrixMast 135 136 columnChoice.setSelectedIndex(bitNum - 1); // index of items in list starts counting at 0 while "1" is displayed 137 } 138 139 /** {@inheritDoc} */ 140 @Override 141 public boolean canHandleMast(@Nonnull SignalMast mast) { 142 return mast instanceof MatrixSignalMast; 143 } 144 145 /** {@inheritDoc} */ 146 @Override 147 public void setMast(SignalMast mast) { 148 log.debug("setMast({})", mast); 149 if (mast == null) { 150 currentMast = null; 151 return; 152 } 153 154 if (! (mast instanceof MatrixSignalMast) ) { 155 log.error("mast was wrong type: {} {}", mast.getSystemName(), mast.getClass().getName()); 156 return; 157 } 158 159 currentMast = (MatrixSignalMast) mast; 160 161 bitNum = currentMast.getBitNum(); // number of matrix columns = logic outputs = number of bits per Aspect 162 updateMatrixMastPanel(); // show only the correct amount of columns for existing matrixMast 163 // @see copyFromAnotherMatrixMastAspect(mast) 164 if (map != null) { 165 Enumeration<String> aspects = map.getAspects(); 166 // in matrixPanel LinkedHashtable, fill in mast settings per aspect 167 while (aspects.hasMoreElements()) { 168 String key = aspects.nextElement(); // for each aspect 169 MatrixAspectPanel matrixPanel = matrixAspect.get(key); // load aspectpanel from hashmap 170 matrixPanel.setAspectDisabled(currentMast.isAspectDisabled(key)); // sets a disabled aspect 171 if ( ! currentMast.isAspectDisabled(key)) { // bits not saved in mast when disabled, so we should not load them back in 172 char[] mastBits = currentMast.getBitsForAspect(key); // same as loading an existing MatrixMast 173 char[] panelAspectBits = Arrays.copyOf(mastBits, MAXMATRIXBITS); // store as [6] character array in panel 174 matrixPanel.updateAspectBits(panelAspectBits); 175 matrixPanel.setAspectBoxes(panelAspectBits); 176 // sets boxes 1 - MAXMATRIXBITS on aspect sub panel from values in hashmap char[] like: 1001 177 } 178 } 179 } 180 181 columnChoice.setSelectedIndex(bitNum - 1); // index of items in list starts counting at 0 while "1" is displayed 182 columnChoice.setEnabled(false); 183 // fill in the names of the outputs from mast: 184 if ( !currentMast.getOutputName(1).isEmpty()) { 185 turnoutBox1.setDefaultNamedBean(InstanceManager.turnoutManagerInstance().getTurnout(currentMast.getOutputName(1))); // load input into turnoutBox1 186 } 187 if (bitNum > 1 && !currentMast.getOutputName(2).isEmpty()) { 188 turnoutBox2.setDefaultNamedBean(InstanceManager.turnoutManagerInstance().getTurnout(currentMast.getOutputName(2))); // load input into turnoutBox2 189 } 190 if (bitNum > 2 && !currentMast.getOutputName(3).isEmpty()) { 191 turnoutBox3.setDefaultNamedBean(InstanceManager.turnoutManagerInstance().getTurnout(currentMast.getOutputName(3))); // load input into turnoutBox3 192 } 193 if (bitNum > 3 && !currentMast.getOutputName(4).isEmpty()) { 194 turnoutBox4.setDefaultNamedBean(InstanceManager.turnoutManagerInstance().getTurnout(currentMast.getOutputName(4))); // load input into turnoutBox4 195 } 196 if (bitNum > 4 && !currentMast.getOutputName(5).isEmpty()) { 197 turnoutBox5.setDefaultNamedBean(InstanceManager.turnoutManagerInstance().getTurnout(currentMast.getOutputName(5))); // load input into turnoutBox5 198 } 199 if (bitNum > 5 && !currentMast.getOutputName(6).isEmpty()) { 200 turnoutBox6.setDefaultNamedBean(InstanceManager.turnoutManagerInstance().getTurnout(currentMast.getOutputName(6))); // load input into turnoutBox6 201 } 202 if (bitNum > 6 && !currentMast.getOutputName(6).isEmpty()) { 203 turnoutBox7.setDefaultNamedBean(InstanceManager.turnoutManagerInstance().getTurnout(currentMast.getOutputName(7))); // load input into turnoutBox6 204 } 205 if (bitNum > 7 && !currentMast.getOutputName(6).isEmpty()) { 206 turnoutBox8.setDefaultNamedBean(InstanceManager.turnoutManagerInstance().getTurnout(currentMast.getOutputName(8))); // load input into turnoutBox6 207 } 208 if (bitNum > 8 && !currentMast.getOutputName(6).isEmpty()) { 209 turnoutBox9.setDefaultNamedBean(InstanceManager.turnoutManagerInstance().getTurnout(currentMast.getOutputName(9))); // load input into turnoutBox6 210 } 211 if (bitNum > 9 && !currentMast.getOutputName(6).isEmpty()) { 212 turnoutBox10.setDefaultNamedBean(InstanceManager.turnoutManagerInstance().getTurnout(currentMast.getOutputName(10))); // load input into turnoutBox6 213 } 214 // repeat in order to extend MAXMATRIXBITS 215 if (currentMast.resetPreviousStates()) { 216 resetPreviousState.setSelected(true); 217 } 218 219 unLitPanelBits = Arrays.copyOf(currentMast.getUnLitBits(), MAXMATRIXBITS); // store as MAXMATRIXBITS character array 220 221 // transfer the lit/unlit status to checkboxes 222 for (int i = 0; i < MAXMATRIXBITS; i++) { 223 unlitCheckArray[i].setSelected(unLitPanelBits[i] == '1'); 224 } 225 226 allowUnLit.setSelected(currentMast.allowUnLit()); 227 // set up additional mast specific Delay 228 timeDelay.setValue(currentMast.getMatrixMastCommandDelay()); 229 230 log.trace("setMast {} end", mast); 231 } 232 233 /** {@inheritDoc} */ 234 @Override 235 public boolean createMast(@Nonnull String sigsysname, @Nonnull String mastname, @Nonnull String username) { 236 log.debug("createMast({},{})", sigsysname, mastname); 237 String newMastName; // UserName for mast 238 239 // check all output boxes are filled (calling getDisplayName() would immediately create a new bean (with an empty comment) 240 if ( ( ( turnoutBox1.isEmpty() ) ) 241 || (bitNum > 1 && ( turnoutBox2.isEmpty() ) ) 242 || (bitNum > 2 && ( turnoutBox3.isEmpty() ) ) 243 || (bitNum > 3 && ( turnoutBox4.isEmpty() ) ) 244 || (bitNum > 4 && ( turnoutBox5.isEmpty() ) ) 245 || (bitNum > 5 && ( turnoutBox6.isEmpty() ) ) 246 || (bitNum > 6 && ( turnoutBox7.isEmpty() ) ) 247 || (bitNum > 7 && ( turnoutBox8.isEmpty() ) ) 248 || (bitNum > 8 && ( turnoutBox9.isEmpty() ) ) 249 || (bitNum > 9 && ( turnoutBox10.isEmpty() ) ) 250 ) { 251 // add an extra OR in list above in order to extend MAXMATRIXBITS 252 // error dialog 253 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("MatrixOutputEmpty", mastname), 254 Bundle.getMessage("WarningTitle"), 255 JmriJOptionPane.ERROR_MESSAGE); 256 log.warn("Empty output on panel"); 257 return false; 258 } 259 260 // check/warn if bit sets are identical 261 if (identicalBits()) { 262 // error dialog 263 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("AspectMastBitsWarning", (int) Math.sqrt(numberOfActiveAspects), numberOfActiveAspects), 264 Bundle.getMessage("WarningTitle"), 265 JmriJOptionPane.ERROR_MESSAGE); 266 log.warn("Identical bits on panel"); 267 return false; 268 } 269 270 if (currentMast == null) { 271 // Create was pressed for new mast: create new MatrixMast with props from panel 272 newMastName = "IF$xsm:" 273 + sigsysname 274 + ":" + mastname.substring(11, mastname.length() - 4); 275 newMastName += "($" + (paddedNumber.format(MatrixSignalMast.getLastRef() + 1)); 276 newMastName += ")" + "-" + bitNum + "t"; // for the number of t = "turnout-outputs" 277 // TODO: add d = option for direct packets 278 currentMast = new MatrixSignalMast(newMastName); // timedDelay is stored later on 279 InstanceManager.getDefault(SignalMastManager.class).register(currentMast); 280 } else { 281 newMastName = currentMast.getSystemName(); 282 } 283 284 currentMast.setBitNum(bitNum); // store number of columns in aspect - outputs matrix in mast 285 286 // store outputs from turnoutBoxes; see method MatrixSignalMast#setOutput(colname, turnoutname) line 357 287 log.debug("newMastName = {}", newMastName); 288 currentMast.setOutput("output1", turnoutBox1.getDisplayName()); // store choice from turnoutBox1, creates bean if needed 289 try { 290 turnoutBox1.updateComment(turnoutBox1.getNamedBean(), newMastName + ":output1"); // write mast name to output1 bean comment 291 if (bitNum > 1) { 292 currentMast.setOutput("output2", turnoutBox2.getDisplayName()); // store choice from turnoutBox2 293 turnoutBox2.updateComment(turnoutBox2.getNamedBean(), newMastName + ":output2"); 294 if (bitNum > 2) { 295 currentMast.setOutput("output3", turnoutBox3.getDisplayName()); // store choice from turnoutBox3 296 turnoutBox3.updateComment(turnoutBox3.getNamedBean(), newMastName + ":output3"); 297 if (bitNum > 3) { 298 currentMast.setOutput("output4", turnoutBox4.getDisplayName()); // store choice from turnoutBox4 299 turnoutBox4.updateComment(turnoutBox4.getNamedBean(), newMastName + ":output4"); 300 if (bitNum > 4) { 301 currentMast.setOutput("output5", turnoutBox5.getDisplayName()); // store choice from turnoutBox5 302 turnoutBox5.updateComment(turnoutBox5.getNamedBean(), newMastName + ":output5"); 303 if (bitNum > 5) { 304 currentMast.setOutput("output6", turnoutBox6.getDisplayName()); // store choice from turnoutBox6 305 turnoutBox6.updateComment(turnoutBox6.getNamedBean(), newMastName + ":output6"); 306 if (bitNum > 6) { 307 currentMast.setOutput("output7", turnoutBox7.getDisplayName()); // store choice from turnoutBox7 308 turnoutBox7.updateComment(turnoutBox7.getNamedBean(), newMastName + ":output7"); 309 if (bitNum > 7) { 310 currentMast.setOutput("output8", turnoutBox8.getDisplayName()); // store choice from turnoutBox8 311 turnoutBox8.updateComment(turnoutBox8.getNamedBean(), newMastName + ":output8"); 312 if (bitNum > 8) { 313 currentMast.setOutput("output9", turnoutBox9.getDisplayName()); // store choice from turnoutBox9 314 turnoutBox9.updateComment(turnoutBox9.getNamedBean(), newMastName + ":output9"); 315 if (bitNum > 9) { 316 currentMast.setOutput("output10", turnoutBox10.getDisplayName()); // store choice from turnoutBox10 317 turnoutBox10.updateComment(turnoutBox10.getNamedBean(), newMastName + ":output10"); 318 // repeat in order to extend MAXMATRIXBITS 319 } 320 } 321 } 322 } 323 } 324 } 325 } 326 } 327 } 328 } catch (JmriException e) { 329 log.warn("bean not found"); 330 } 331 332 for (Map.Entry<String, MatrixAspectPanel> entry : matrixAspect.entrySet()) { 333 // store matrix in mast per aspect, compare with line 991 334 matrixMastPanel.add(entry.getValue().getPanel()); // read from aspect panel to mast 335 if (matrixAspect.get(entry.getKey()).isAspectDisabled()) { 336 currentMast.setAspectDisabled(entry.getKey()); // don't store bits when this aspect is disabled 337 } else { 338 currentMast.setAspectEnabled(entry.getKey()); 339 currentMast.setBitsForAspect(entry.getKey(), matrixAspect.get(entry.getKey()).trimAspectBits()); // return as char[] 340 } 341 } 342 currentMast.resetPreviousStates(resetPreviousState.isSelected()); // read from panel, not displayed? 343 344 currentMast.setAllowUnLit(allowUnLit.isSelected()); 345 346 // copy bits from UnLitPanel var unLitPanelBits 347 try { 348 currentMast.setUnLitBits(trimUnLitBits()); 349 } catch (Exception ex) { 350 log.error("failed to read and copy unLitPanelBits"); 351 } 352 353 if (!username.isEmpty()) { 354 currentMast.setUserName(username); 355 } 356 357 prefs.setComboBoxLastSelection(matrixBitNumSelectionCombo, (String) columnChoice.getSelectedItem()); // store bitNum pref 358 359 currentMast.setAllowUnLit(allowUnLit.isSelected()); 360 361 // set MatrixMast specific Delay information, see jmri.implementation.MatrixSignalMast 362 int addDelay = (Integer) timeDelay.getValue(); // from a JSpinner with 0 set as minimum 363 currentMast.setMatrixMastCommandDelay(addDelay); 364 log.debug("mast create completed successfully"); 365 return true; 366 } 367 368 /** 369 * Create bitNumPanel with drop down to set number of columns, separate from 370 * the rest for redraw. 371 * <p> 372 * Auto refresh to show/hide input (turnout) selection boxes. Hide/show 373 * checkboxes in matrix (per aspect). 374 * 375 * @return a JPanel with a comboBox to select number of outputs, set at 376 * current value 377 */ 378 private JPanel makeMatrixMastBitnumPanel() { 379 JPanel bitnumpanel = new JPanel(); 380 bitnumpanel.setLayout(new FlowLayout(FlowLayout.LEADING)); 381 // select number of columns in logic matrix 382 bitnumpanel.add(bitNumLabel); 383 bitnumpanel.add(columnChoice); 384 if (bitNum < 1 || bitNum > MAXMATRIXBITS) { 385 bitNum = 4; // default to 4 col if out-of-range for some reason 386 } 387 columnChoice.setSelectedIndex(bitNum - 1); 388 columnChoice.addActionListener((ActionEvent e) -> { 389 String newBitnumString = (String) columnChoice.getSelectedItem(); 390 if (newBitnumString == null) { 391 newBitnumString = "4"; // error, fall back to default 392 log.debug("null newBitnumString in makeMatrixMastBitnumPanel()"); 393 } 394 bitNumChanged(Integer.valueOf(newBitnumString)); 395 }); 396 return bitnumpanel; 397 } 398 399 /** 400 * @return char[] of length bitNum copied from unLitPanelBits 401 */ 402 private char[] trimUnLitBits() { 403 if (unLitPanelBits != null) { 404 return Arrays.copyOf(unLitPanelBits, bitNum); 405 } else { 406 return Arrays.copyOf(emptyBits, bitNum); 407 } 408 } 409 410 /** 411 * Build lower half of Add Signal Mast panel, specifically for Matrix Mast. 412 * <p> 413 * Called when Mast Type drop down changes. 414 */ 415 private void updateMatrixMastPanel() { 416 matrixAspect = new LinkedHashMap<>(NOTIONAL_ASPECT_COUNT); 417 418 // nothing to do if no map yet present 419 if (map == null) return; 420 421 Enumeration<String> aspects = map.getAspects(); 422 while (aspects.hasMoreElements()) { 423 String aspect = aspects.nextElement(); 424 MatrixAspectPanel aspectpanel = new MatrixAspectPanel(aspect); 425 matrixAspect.put(aspect, aspectpanel); // store in LinkedHashMap 426 // values are filled in later 427 } 428 matrixMastPanel.removeAll(); 429 430 // sub panels (so we can hide all turnouts with Output Type drop down box later) 431 JPanel turnoutpanel = new JPanel(); 432 433 // binary matrix outputs follow: 434 435 JPanel output1panel = makeOutputPanel(turnoutBox1,1); 436 turnoutpanel.add(output1panel); 437 // output1panel always on 438 output1panel.setVisible(true); 439 440 turnoutpanel.add(makeOutputPanel(turnoutBox2,2)); 441 turnoutpanel.add(makeOutputPanel(turnoutBox3,3)); 442 turnoutpanel.add(makeOutputPanel(turnoutBox4,4)); 443 444 // Four in a row is really enough, start another row 445 matrixMastPanel.add(turnoutpanel); 446 turnoutpanel = new JPanel(); 447 448 turnoutpanel.add(makeOutputPanel(turnoutBox5,5)); 449 turnoutpanel.add(makeOutputPanel(turnoutBox6,6)); 450 turnoutpanel.add(makeOutputPanel(turnoutBox7,7)); 451 turnoutpanel.add(makeOutputPanel(turnoutBox8,8)); 452 453 // Four in a row is really enough, start another row 454 matrixMastPanel.add(turnoutpanel); 455 turnoutpanel = new JPanel(); 456 457 turnoutpanel.add(makeOutputPanel(turnoutBox9,9)); 458 turnoutpanel.add(makeOutputPanel(turnoutBox10,10)); 459 460 // repeat in order to extend MAXMATRIXBITS 461 462 matrixMastPanel.add(turnoutpanel); 463 464 // show the active unlit check boxes 465 for (int i = 0; i < MAXMATRIXBITS; i++) { 466 unlitCheckArray[i].setVisible(i < bitNum); 467 } 468 469 JPanel matrixHeader = new JPanel(); 470 JLabel matrixHeaderLabel = new JLabel(Bundle.getMessage("AspectMatrixHeaderLabel", bitNum), JLabel.CENTER); 471 matrixHeader.add(matrixHeaderLabel); 472 matrixHeaderLabel.setToolTipText(Bundle.getMessage("AspectMatrixHeaderTooltip")); 473 matrixMastPanel.add(matrixHeader); 474 475 for (Map.Entry<String, MatrixAspectPanel> entry : matrixAspect.entrySet()) { 476 matrixMastPanel.add(entry.getValue().getPanel()); // load Aspect sub panels to matrixMastPanel from hashmap 477 // build aspect sub panels 478 } 479 if ((matrixAspect.size() & 1) == 1) { 480 // spacer before "Reset previous aspect" 481 matrixMastPanel.add(new JLabel()); 482 } 483 484 matrixMastPanel.add(resetPreviousState); // checkbox 485 486 resetPreviousState.setToolTipText(Bundle.getMessage("ResetPreviousToolTip")); 487 // copy option matrixMast bitstrings = settings 488 JPanel matrixCopyPanel = new JPanel(); 489 matrixCopyPanel.setLayout(new FlowLayout(FlowLayout.LEADING)); 490 matrixCopyPanel.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("MatrixMastCopyAspectBits")))); 491 matrixCopyPanel.add(copyFromMastSelection()); 492 matrixMastPanel.add(matrixCopyPanel); 493 494 // add additional MatrixMast-specific delay 495 JPanel delayPanel = new JPanel(); 496 delayPanel.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("LabelTurnoutDelay")))); 497 timeDelay.setModel(new SpinnerNumberModel(0, 0, 1000, 1)); 498 // timeDelay.setValue(0); // reset from possible previous use 499 timeDelay.setPreferredSize(new JTextField(5).getPreferredSize()); 500 delayPanel.add(timeDelay); 501 timeDelay.setToolTipText(Bundle.getMessage("TooltipTurnoutDelay")); 502 delayPanel.add(new JLabel(Bundle.getMessage("LabelMilliseconds"))); 503 matrixMastPanel.add(delayPanel); 504 505 matrixMastPanel.setLayout(new jmri.util.javaworld.GridLayout2(0, 1)); // 0 means enough 506 matrixMastPanel.revalidate(); 507 } 508 509 /** 510 * Index is 1-based here: 1..MAXMATRIXBITS 511 * @param turnoutPanel The panel to convert to an output panel 512 * @param index The 1..MAXMATRIXBITS index of the one to be set visible 513 * @return An output panel containing the selected turnout panel 514 */ 515 JPanel makeOutputPanel(JPanel turnoutPanel, int index) { 516 JPanel outputpanel = new JPanel(); 517 outputpanel.add(turnoutPanel); 518 TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 519 border.setTitle(Bundle.getMessage("MatrixOutputLabel", index)); 520 outputpanel.setBorder(border); 521 522 outputpanel.setVisible(index <= bitNum); 523 524 return outputpanel; 525 } 526 527 /** 528 * When the user changes the number of columns in matrix from the drop down: 529 * store the new value and redraw pane. 530 * 531 * @param newColNum int with the new value = the number of columns in the 532 * Matrix Table 533 */ 534 private void bitNumChanged(Integer newColNum) { 535 if (newColNum < 1 || newColNum > MAXMATRIXBITS || newColNum == bitNum) { 536 return; 537 } 538 bitNum = newColNum; 539 // hide/show output choices per Aspect 540 updateMatrixMastPanel(); 541 542 validate(); 543 java.awt.Container ancestor = getTopLevelAncestor(); 544 if ((ancestor instanceof JmriJFrame)) { 545 ancestor.setSize(ancestor.getPreferredSize()); 546 ((JmriJFrame) ancestor).pack(); 547 } 548 repaint(); 549 } 550 551 private void copyFromAnotherMatrixMastAspect(String strMast) { 552 MatrixSignalMast mast = (MatrixSignalMast) InstanceManager.getDefault(SignalMastManager.class).getNamedBean(strMast); 553 if (mast == null) { 554 log.error("Cannot copy from mast {} which doesn't exist", strMast); 555 return; 556 } 557 if (bitNum != mast.getBitNum()) { 558 int i = JmriJOptionPane.showConfirmDialog(this, Bundle.getMessage("MatrixColWarning", mast.getBitNum(), bitNum), 559 Bundle.getMessage("MatrixColWarningTitle"), 560 JmriJOptionPane.YES_NO_OPTION); 561 if (i != JmriJOptionPane.YES_OPTION ) { 562 return; 563 } 564 } 565 // cf. line 405 loading an existing mast for edit 566 for (Map.Entry<String, MatrixAspectPanel> entry : matrixAspect.entrySet()) { 567 // select the correct checkboxes 568 MatrixAspectPanel matrixPanel = entry.getValue(); // load aspectpanel from hashmap 569 matrixPanel.setAspectDisabled(mast.isAspectDisabled(entry.getKey())); // sets a disabled aspect 570 if (!mast.isAspectDisabled(entry.getKey())) { 571 char[] mastBits = mast.getBitsForAspect(entry.getKey()); // same as loading an existing MatrixMast 572 char[] panelAspectBits = Arrays.copyOf(mastBits, MAXMATRIXBITS); // store as 6 character array in panel 573 matrixPanel.updateAspectBits(panelAspectBits); 574 matrixPanel.setAspectBoxes(panelAspectBits); 575 // sets boxes 1 - MAXMATRIXBITS on aspect sub panel from values in hashmap char[] like: 1001 576 } 577 } 578 } 579 580/* *//** 581 * Call for sub panel per aspect from hashmap matrixAspect with check boxes 582 * to set properties. 583 * <p> 584 * Invoked when updating MatrixMastPanel. 585 * 586 * @see #updateMatrixMastPanel() 587 *//* 588 void updateMatrixAspectPanel() { 589 Enumeration<String> aspects = map.getAspects(); 590 while (aspects.hasMoreElements()) { 591 String aspect = aspects.nextElement(); 592 MatrixAspectPanel aspectpanel = new MatrixAspectPanel(aspect, bitString); // build 1 line, picking up bitString 593 matrixAspect.put(aspect, aspectpanel); // store that line 594 } 595 // refresh aspects list 596 // TODO sort matrixAspect HashTable, which at this point is not sorted 597 matrixMastPanel.removeAll(); 598 for (Map.Entry<String, MatrixAspectPanel> entry : matrixAspect.entrySet()) { 599 matrixMastPanel.add(entry.getValue().getPanel()); 600 // Matrix checkbox states are set by getPanel() 601 } 602 matrixMastPanel.setLayout(new jmri.util.javaworld.GridLayout2(0, 1)); // 0 means enough 603 matrixMastPanel.revalidate(); 604 }*/ 605 606 private JPanel matrixUnLitPanel = new JPanel(); 607 608 private JCheckBox[] unlitCheckArray = new JCheckBox[MAXMATRIXBITS]; 609 { 610 for (int i = 0; i < MAXMATRIXBITS; i++) { 611 unlitCheckArray[i] = new JCheckBox(); 612 } 613 } 614 615 // add more JCheckBoxes in order to extend MAXMATRIXBITS 616 617 /** 618 * JPanel to set outputs for an unlit (Dark) Matrix Signal Mast. 619 */ 620 private void matrixUnLitPanel() { 621 if (bitNum < 1 || bitNum > MAXMATRIXBITS) { 622 bitNum = 4; // default to 4 col for (first) new mast 623 } 624 625 JPanel matrixUnLitDetails = new JPanel(); 626 matrixUnLitDetails.setLayout(new jmri.util.javaworld.GridLayout2(1, 1)); // stretch to full width 627 628 for (int i = 0; i < MAXMATRIXBITS; i++) matrixUnLitDetails.add(unlitCheckArray[i]); 629 630 matrixUnLitPanel.add(matrixUnLitDetails); 631 TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 632 border.setTitle(Bundle.getMessage("MatrixUnLitDetails")); 633 matrixUnLitPanel.setBorder(border); 634 matrixUnLitPanel.setToolTipText(Bundle.getMessage("MatrixUnlitTooltip")); 635 636 unlitCheckArray[0].addActionListener((ActionEvent e) -> { 637 setUnLitBit(1, unlitCheckArray[0].isSelected()); 638 }); 639 unlitCheckArray[1].addActionListener((ActionEvent e) -> { 640 setUnLitBit(2, unlitCheckArray[1].isSelected()); 641 }); 642 unlitCheckArray[2].addActionListener((ActionEvent e) -> { 643 setUnLitBit(3, unlitCheckArray[2].isSelected()); 644 }); 645 unlitCheckArray[3].addActionListener((ActionEvent e) -> { 646 setUnLitBit(4, unlitCheckArray[3].isSelected()); 647 }); 648 unlitCheckArray[4].addActionListener((ActionEvent e) -> { 649 setUnLitBit(5, unlitCheckArray[4].isSelected()); 650 }); 651 unlitCheckArray[5].addActionListener((ActionEvent e) -> { 652 setUnLitBit(6, unlitCheckArray[5].isSelected()); 653 }); 654 unlitCheckArray[6].addActionListener((ActionEvent e) -> { 655 setUnLitBit(7, unlitCheckArray[6].isSelected()); 656 }); 657 unlitCheckArray[7].addActionListener((ActionEvent e) -> { 658 setUnLitBit(8, unlitCheckArray[7].isSelected()); 659 }); 660 unlitCheckArray[8].addActionListener((ActionEvent e) -> { 661 setUnLitBit(9, unlitCheckArray[8].isSelected()); 662 }); 663 unlitCheckArray[9].addActionListener((ActionEvent e) -> { 664 setUnLitBit(10, unlitCheckArray[9].isSelected()); 665 }); 666 // repeat in order to extend MAXMATRIXBITS 667 } 668 669 670 private JComboBox<String> copyFromMastSelection() { 671 JComboBox<String> mastSelect = new JComboBox<>(); 672 for (SignalMast mast : InstanceManager.getDefault(SignalMastManager.class).getNamedBeanSet()) { 673 if (mast instanceof MatrixSignalMast){ 674 mastSelect.addItem(mast.getDisplayName()); 675 } 676 } 677 if (mastSelect.getItemCount() == 0) { 678 mastSelect.setEnabled(false); 679 } else { 680 mastSelect.insertItemAt("", 0); 681 mastSelect.setSelectedIndex(0); 682 mastSelect.addActionListener((ActionEvent e) -> { 683 @SuppressWarnings("unchecked") // e.getSource() cast from mastSelect source 684 JComboBox<String> eb = (JComboBox<String>) e.getSource(); 685 String sourceMast = (String) eb.getSelectedItem(); 686 if (sourceMast != null && !sourceMast.isEmpty()) { 687 copyFromAnotherMatrixMastAspect(sourceMast); 688 } 689 }); 690 } 691 return mastSelect; 692 } 693 694 /** 695 * Update the on/off positions for the unLitPanelBits char[]. 696 * <p> 697 * Invoked from bit checkboxes 1 to MAXMATRIXBITS on unLitPanel. 698 * 699 * @param column int as index for an output (between 1 and 6) 700 * @param state boolean for the output On (Closed) or Off (Thrown) 701 */ 702 private void setUnLitBit(int column, boolean state) { 703 if (state) { 704 unLitPanelBits[column - 1] = '1'; 705 } else { 706 unLitPanelBits[column - 1] = '0'; 707 } 708 } 709 710 /** 711 * Check all aspects for duplicate bit combos. 712 * 713 * @return true if at least 1 duplicate row of bits is found 714 */ 715 private boolean identicalBits() { 716 boolean identical = false; 717 numberOfActiveAspects = 0; 718 Collection<String> seenBits = new HashSet<>(); // a fast access, no duplicates Collection of bit combinations 719 for (Map.Entry<String, MatrixAspectPanel> entry : matrixAspect.entrySet()) { 720 // check per aspect 721 if (entry.getValue().isAspectDisabled()) { 722 continue; // skip disabled aspects 723 } else if (seenBits.contains(String.valueOf(entry.getValue().trimAspectBits()))) { 724 identical = true; 725 log.debug("-found duplicate {}", String.valueOf(entry.getValue().trimAspectBits())); 726 // don't break, so we can count number of enabled aspects for this mast 727 } else { 728 seenBits.add(String.valueOf(entry.getValue().trimAspectBits())); // convert back from char[] to String 729 log.debug("-added new {}; seenBits = {}", String.valueOf(entry.getValue().trimAspectBits()), seenBits.toString()); 730 } 731 ++numberOfActiveAspects; 732 } 733 return identical; 734 } 735 736 /** 737 * JPanel to define properties of an Aspect for a Matrix Signal Mast. 738 * <p> 739 * Invoked from the AddSignalMastPanel class when Output Matrix Signal Mast is 740 * selected as mast type. 741 * 742 * @author Egbert Broerse 743 */ 744 class MatrixAspectPanel { 745 746 JCheckBox disabledCheck = new JCheckBox(Bundle.getMessage("DisableAspect")); 747 JCheckBox bitCheck1 = new JCheckBox(); 748 JCheckBox bitCheck2 = new JCheckBox(); 749 JCheckBox bitCheck3 = new JCheckBox(); 750 JCheckBox bitCheck4 = new JCheckBox(); 751 JCheckBox bitCheck5 = new JCheckBox(); 752 JCheckBox bitCheck6 = new JCheckBox(); 753 JCheckBox bitCheck7 = new JCheckBox(); 754 JCheckBox bitCheck8 = new JCheckBox(); 755 JCheckBox bitCheck9 = new JCheckBox(); 756 JCheckBox bitCheck10= new JCheckBox(); 757 // repeat in order to extend MAXMATRIXBITS 758 759 JTextField aspectBitsField = new JTextField(MAXMATRIXBITS); // for debug 760 String aspect = ""; 761 String emptyChars = "0000000000"; // size of String = MAXMATRIXBITS; add another 0 in order to extend MAXMATRIXBITS 762 char[] aspectBits = emptyChars.toCharArray(); 763 764 /** 765 * Build new aspect matrix panel. 766 * Called when Add Signal Mast Pane is built. 767 * 768 * @param aspect String like "Clear" 769 */ 770 MatrixAspectPanel(String aspect) { 771 this.aspect = aspect; 772 } 773 774 /** 775 * Build an aspect matrix panel using char[] previously entered. Called 776 * when number of outputs = columns is changed (possible during new mast 777 * creation only). 778 * 779 * @param aspect String like "Clear" 780 * @param panelBits char[] of up to 5 1's and 0's 781 */ 782 MatrixAspectPanel(String aspect, char[] panelBits) { 783 if (panelBits == null || panelBits.length == 0) { 784 return; 785 } 786 this.aspect = aspect; 787 // aspectBits is char[] of length(bitNum) describing state of on/off checkboxes 788 // i.e. "0101" provided as char[] array 789 // easy to manipulate by index 790 // copy to checkbox states: 791 aspectBits = panelBits; 792 setAspectBoxes(aspectBits); 793 } 794 795 void updateAspectBits(char[] newBits) { 796 aspectBits = newBits; 797 } 798 799 boolean isAspectDisabled() { 800 return disabledCheck.isSelected(); 801 } 802 803 /** 804 * Set an Aspect Panels elements inactive. 805 * <p> 806 * Invoked from Disabled (aspect) checkbox and from Edit mast pane. 807 * 808 * @param boo true (On) or false (Off) 809 */ 810 void setAspectDisabled(boolean boo) { 811 disabledCheck.setSelected(boo); 812 if (boo) { // disable all (output bit) checkboxes on this aspect panel 813 // aspectBitsField always Disabled 814 bitCheck1.setEnabled(false); 815 if (bitNum > 1) { 816 bitCheck2.setEnabled(false); 817 } 818 if (bitNum > 2) { 819 bitCheck3.setEnabled(false); 820 } 821 if (bitNum > 3) { 822 bitCheck4.setEnabled(false); 823 } 824 if (bitNum > 4) { 825 bitCheck5.setEnabled(false); 826 } 827 if (bitNum > 5) { 828 bitCheck6.setEnabled(false); 829 } 830 if (bitNum > 6) { 831 bitCheck7.setEnabled(false); 832 } 833 if (bitNum > 7) { 834 bitCheck8.setEnabled(false); 835 } 836 if (bitNum > 8) { 837 bitCheck9.setEnabled(false); 838 } 839 if (bitNum > 9) { 840 bitCheck10.setEnabled(false); 841 } 842 // repeat in order to extend MAXMATRIXBITS 843 844 } else { // enable all (output bit) checkboxes on this aspect panel 845 // aspectBitsField always Disabled 846 bitCheck1.setEnabled(true); 847 if (bitNum > 1) { 848 bitCheck2.setEnabled(true); 849 } 850 if (bitNum > 2) { 851 bitCheck3.setEnabled(true); 852 } 853 if (bitNum > 3) { 854 bitCheck4.setEnabled(true); 855 } 856 if (bitNum > 4) { 857 bitCheck5.setEnabled(true); 858 } 859 if (bitNum > 5) { 860 bitCheck6.setEnabled(true); 861 } 862 if (bitNum > 6) { 863 bitCheck7.setEnabled(true); 864 } 865 if (bitNum > 7) { 866 bitCheck8.setEnabled(true); 867 } 868 if (bitNum > 8) { 869 bitCheck9.setEnabled(true); 870 } 871 if (bitNum > 9) { 872 bitCheck10.setEnabled(true); 873 } 874 // repeat in order to set extend MAXMATRIXBITS 875 } 876 } 877 878 /** 879 * Update the on/off positions for an Aspect in the aspectBits char[]. 880 * <p> 881 * Invoked from bit checkboxes 1 to MAXMATRIXBITS on aspectPanels. 882 * 883 * @param column int of the output (between 1 and MAXMATRIXBITS) 884 * @param state boolean for the output On (Closed) or Off (Thrown) 885 * @see #aspectBits 886 */ 887 void setBit(int column, boolean state) { 888 if (state) { 889 aspectBits[column - 1] = '1'; 890 } else { 891 aspectBits[column - 1] = '0'; 892 } 893 String value = String.valueOf(aspectBits); // convert back from char[] to String 894 aspectBitsField.setText(value); 895 } 896 897 /** 898 * Send the on/off positions for an Aspect to mast. 899 * 900 * @return A char[] of '1' and '0' elements with a length between 1 and 901 * 5 corresponding with the number of outputs for this mast 902 * @see jmri.implementation.MatrixSignalMast 903 */ 904 char[] trimAspectBits() { 905 try { 906 return Arrays.copyOf(aspectBits, bitNum); // copy to new char[] of length bitNum 907 } catch (Exception ex) { 908 log.error("failed to read and copy aspectBits"); 909 return new char[] {}; 910 } 911 } 912 913 /** 914 * Activate the corresponding checkboxes on a MatrixApectPanel. 915 * 916 * @param aspectBits A char[] of '1' and '0' elements with a length 917 * between 1 and 5 corresponding with the number of 918 * outputs for this mast 919 */ 920 private void setAspectBoxes(char[] aspectBits) { 921 bitCheck1.setSelected(aspectBits[0] == '1'); 922 if (bitNum > 1) { 923 bitCheck2.setSelected(aspectBits[1] == '1'); 924 } 925 if (bitNum > 2) { 926 bitCheck3.setSelected(aspectBits[2] == '1'); 927 } 928 if (bitNum > 3) { 929 bitCheck4.setSelected(aspectBits[3] == '1'); 930 } 931 if (bitNum > 4) { 932 bitCheck5.setSelected(aspectBits[4] == '1'); 933 } 934 if (bitNum > 5) { 935 bitCheck6.setSelected(aspectBits[5] == '1'); 936 } 937 if (bitNum > 6) { 938 bitCheck7.setSelected(aspectBits[6] == '1'); 939 } 940 if (bitNum > 7) { 941 bitCheck8.setSelected(aspectBits[7] == '1'); 942 } 943 if (bitNum > 8) { 944 bitCheck9.setSelected(aspectBits[8] == '1'); 945 } 946 if (bitNum > 9) { 947 bitCheck10.setSelected(aspectBits[9] == '1'); 948 } 949 // repeat in order to set extend MAXMATRIXBITS 950 951 String value = String.valueOf(aspectBits); // convert back from char[] to String 952 aspectBitsField.setText(value); 953 } 954 955 JPanel panel; 956 957 /** 958 * Build a JPanel for an Aspect Matrix row. 959 * 960 * @return JPanel to be displayed on the Add/Edit Signal Mast panel 961 */ 962 JPanel getPanel() { 963 if (panel == null) { 964 panel = new JPanel(); 965 panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 966 967 JPanel matrixDetails = new JPanel(); 968 matrixDetails.add(disabledCheck); 969 matrixDetails.add(bitCheck1); 970 matrixDetails.add(bitCheck2); 971 matrixDetails.add(bitCheck3); 972 matrixDetails.add(bitCheck4); 973 matrixDetails.add(bitCheck5); 974 matrixDetails.add(bitCheck6); 975 matrixDetails.add(bitCheck7); 976 matrixDetails.add(bitCheck8); 977 matrixDetails.add(bitCheck9); 978 matrixDetails.add(bitCheck10); 979 // repeat in order to extend MAXMATRIXBITS 980 981 // TODO refresh aspectSetting, can be in OKPressed() to store/warn for duplicates (with button 'Ignore') 982 983 // hide if id > bitNum (var in panel) 984 bitCheck2.setVisible(bitNum > 1); 985 bitCheck3.setVisible(bitNum > 2); 986 bitCheck4.setVisible(bitNum > 3); 987 bitCheck5.setVisible(bitNum > 4); 988 bitCheck6.setVisible(bitNum > 5); 989 bitCheck7.setVisible(bitNum > 6); 990 bitCheck8.setVisible(bitNum > 7); 991 bitCheck9.setVisible(bitNum > 8); 992 bitCheck10.setVisible(bitNum> 9); 993 // repeat in order to extend MAXMATRIXBITS 994 995 matrixDetails.add(aspectBitsField); 996 aspectBitsField.setEnabled(false); // not editable, just for debugging 997 aspectBitsField.setVisible(false); // set to true to check/debug 998 999 panel.add(matrixDetails); 1000 TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 1001 border.setTitle(aspect); 1002 panel.setBorder(border); 1003 1004 disabledCheck.addActionListener((ActionEvent e) -> { 1005 setAspectDisabled(disabledCheck.isSelected()); 1006 }); 1007 1008 bitCheck1.addActionListener((ActionEvent e) -> { 1009 setBit(1, bitCheck1.isSelected()); 1010 }); 1011 bitCheck2.addActionListener((ActionEvent e) -> { 1012 setBit(2, bitCheck2.isSelected()); 1013 }); 1014 bitCheck3.addActionListener((ActionEvent e) -> { 1015 setBit(3, bitCheck3.isSelected()); 1016 }); 1017 bitCheck4.addActionListener((ActionEvent e) -> { 1018 setBit(4, bitCheck4.isSelected()); 1019 }); 1020 bitCheck5.addActionListener((ActionEvent e) -> { 1021 setBit(5, bitCheck5.isSelected()); 1022 }); 1023 bitCheck6.addActionListener((ActionEvent e) -> { 1024 setBit(6, bitCheck6.isSelected()); 1025 }); 1026 bitCheck7.addActionListener((ActionEvent e) -> { 1027 setBit(7, bitCheck7.isSelected()); 1028 }); 1029 bitCheck8.addActionListener((ActionEvent e) -> { 1030 setBit(8, bitCheck8.isSelected()); 1031 }); 1032 bitCheck9.addActionListener((ActionEvent e) -> { 1033 setBit(9, bitCheck9.isSelected()); 1034 }); 1035 bitCheck10.addActionListener((ActionEvent e) -> { 1036 setBit(10, bitCheck10.isSelected()); 1037 }); 1038 // repeat in order to extend MAXMATRIXBITS 1039 } 1040 return panel; 1041 } 1042 1043 } 1044 1045 @ServiceProvider(service = SignalMastAddPane.SignalMastAddPaneProvider.class) 1046 static public class SignalMastAddPaneProvider extends SignalMastAddPane.SignalMastAddPaneProvider { 1047 /** {@inheritDoc} */ 1048 @Override 1049 @Nonnull public String getPaneName() { 1050 return Bundle.getMessage("MatrixCtlMast"); 1051 } 1052 /** {@inheritDoc} */ 1053 @Override 1054 @Nonnull public SignalMastAddPane getNewPane() { 1055 return new MatrixSignalMastAddPane(); 1056 } 1057 } 1058 1059 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MatrixSignalMastAddPane.class); 1060 1061}