001package jmri.jmrit.symbolicprog; 002 003import java.awt.BorderLayout; 004import java.awt.Color; 005import java.awt.Component; 006import java.awt.GridBagConstraints; 007import java.awt.GridBagLayout; 008import java.util.ArrayList; 009import java.util.HashMap; 010import java.util.List; 011import java.util.ResourceBundle; 012 013import javax.swing.BoundedRangeModel; 014import javax.swing.DefaultBoundedRangeModel; 015import javax.swing.JButton; 016import javax.swing.JCheckBox; 017import javax.swing.JComponent; 018import javax.swing.JLabel; 019import javax.swing.JPanel; 020import javax.swing.JSlider; 021import javax.swing.JTextField; 022import javax.swing.event.ChangeEvent; 023import javax.swing.event.ChangeListener; 024 025import jmri.InstanceManager; 026import jmri.UserPreferencesManager; 027import jmri.util.CvUtil; 028 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032/** 033 * Represent an entire speed table as a single Variable. 034 * <p> 035 * This presents as a set of vertically oriented sliders, with numeric values 036 * above them. That it turn is done using VarSlider and DecVariableValue objects 037 * respectively. VarSlider is an interior class to color a JSlider by state. The 038 * respective VarSlider and DecVariableValue communicate through their 039 * underlying CV objects. Changes to CV Values are listened to by this class, 040 * which updates the model objects for the VarSliders; the DecVariableValues 041 * listen directly. 042 * <p> 043 * Color (hence state) of individual sliders (hence CVs) are directly coupled to 044 * the state of those CVs. 045 * <p> 046 * The state of the entire variable has to be a composite of all the sliders, 047 * hence CVs. The mapping is (in order): 048 * <ul> 049 * <li>If any CVs are UNKNOWN, its UNKNOWN.. 050 * <li>If not, and any are EDITED, its EDITED. 051 * <li>If not, and any are FROMFILE, its FROMFILE. 052 * <li>If not, and any are READ, its READ. 053 * <li>If not, and any are STORED, its STORED. 054 * <li>And if we get to here, something awful has happened. 055 * </ul> 056 * <p> 057 * A similar pattern is used for a read or write request. Write writes them all; 058 * Read reads any that aren't READ or WRITTEN. 059 * <p> 060 * Speed tables can have different numbers of entries; 28 is the default, and 061 * also the maximum. 062 * <p> 063 * The NMRA specification says that speed table entries cannot be non-monotonic 064 * (e.g. cannot decrease when moving from lower to higher CV numbers). In 065 * earlier versions of the code, this was enforced any time a value was changed 066 * (for any reason). This caused a problem when CVs were read that were 067 * non-monotonic: That value was read, causing lower CVs to be made consistent, 068 * a change in their value which changed their state, so they were read again. 069 * To avoid this, the class now only enforces non-monotonicity when the slider 070 * is adjusted. 071 * <p> 072 * _value is a holdover from the LongAddrVariableValue, which this was copied 073 * from; it should be removed. 074 * 075 * @author Bob Jacobsen, Alex Shepherd Copyright (C) 2001, 2004, 2013 076 * @author Dave Heap Copyright (C) 2012 Added support for Marklin mfx style speed table 077 * @author Dave Heap Copyright (C) 2013 Changes to fix mfx speed table issue (Vstart and Vhigh not written) 078 * @author Dave Heap - generate cvList array to incorporate Vstart and Vhigh 079 * 080 */ 081public class SpeedTableVarValue extends VariableValue implements ChangeListener { 082 083 int nValues; 084 int numCvs; 085 String[] cvList; 086 BoundedRangeModel[] models; 087 int _min; 088 int _max; 089 int _range; 090 boolean mfx; 091 092 List<JCheckBox> stepCheckBoxes; 093 094 /** 095 * Create the object with a "standard format ctor". 096 * @param name name. 097 * @param comment comment. 098 * @param cvName cv name. 099 * @param readOnly true if read only, else false. 100 * @param infoOnly true if info only, else false. 101 * @param writeOnly true if write only, else false. 102 * @param opsOnly true if ops only, else false. 103 * @param cvNum cv number. 104 * @param mask cv mask. 105 * @param minVal minimum value. 106 * @param maxVal maximum value. 107 * @param v hashmap of string and cv value. 108 * @param status status label. 109 * @param stdname std name. 110 * @param entries number entries. 111 * @param mfxFlag set mx flag true or false. 112 */ 113 public SpeedTableVarValue(String name, String comment, String cvName, 114 boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly, 115 String cvNum, String mask, int minVal, int maxVal, 116 HashMap<String, CvValue> v, JLabel status, String stdname, int entries, boolean mfxFlag) { 117 super(name, comment, cvName, readOnly, infoOnly, writeOnly, opsOnly, cvNum, mask, v, status, stdname); 118 119 nValues = entries; 120 _min = minVal; 121 _max = maxVal; 122 _range = maxVal - minVal; 123 mfx = mfxFlag; 124 125 numCvs = nValues; 126 cvList = new String[numCvs]; 127 128 models = new BoundedRangeModel[nValues]; 129 130 // create the set of models 131 for (int i = 0; i < nValues; i++) { 132 // populate cvList 133 cvList[i] = Integer.toString(Integer.parseInt(getCvNum()) + i); 134 // create each model 135 DefaultBoundedRangeModel j = new DefaultBoundedRangeModel(_range * i / (nValues - 1) + _min, 0, _min, _max); 136 models[i] = j; 137 // connect each model to CV for notification 138 // the connection is to cvNum through cvNum+nValues (28 values total typically) 139 // The invoking code (e.g. VariableTableModel) must ensure the CVs exist 140 // Note that the default values in the CVs are zero, but are the ramp 141 // values here. We leave that as work item 177, and move on to set the 142 // CV states to "FromFile" 143 CvValue c = _cvMap.get(cvList[i]); 144 c.setValue(_range * i / (nValues - 1) + _min); 145 c.addPropertyChangeListener(this); 146 c.setState(ValueState.FROMFILE); 147 } 148 149 _defaultColor = (new JSlider()).getBackground(); 150 // simplifyMask(); // not required as mask is ignored 151 } 152 153 /** 154 * Create a null object. Normally only used for tests and to pre-load 155 * classes. 156 */ 157 public SpeedTableVarValue() { 158 } 159 160 @Override 161 public Object rangeVal() { 162 log.warn("rangeVal doesn't make sense for a speed table"); 163 return "Speed table"; 164 } 165 166 @Override 167 public CvValue[] usesCVs() { 168 CvValue[] retval = new CvValue[numCvs]; 169 int i; 170 for (i = 0; i < numCvs; i++) { 171 retval[i] = _cvMap.get(cvList[i]); 172 } 173 return retval; 174 } 175 176 /** 177 * Called for new values of a slider. 178 * <p> 179 * Sets the CV(s) as needed. 180 * 181 */ 182 @Override 183 public void stateChanged(ChangeEvent e) { 184 // e.getSource() points to the JSlider object - find it in the list 185 JSlider j = (JSlider) e.getSource(); 186 BoundedRangeModel r = j.getModel(); 187 188 for (int i = 0; i < nValues; i++) { 189 if (r == models[i]) { 190 // found it, and i is useful! 191 setModel(i, r.getValue()); 192 break; // no need to continue loop 193 } 194 } 195 // notify that Value property changed 196 prop.firePropertyChange("Value", null, j); 197 } 198 199 void setModel(int i, int value) { // value is _min to _max 200 if (value < _min || (mfx && (i == 0))) { 201 value = _min; 202 } 203 if (value > _max || (mfx && (i == nValues - 1))) { 204 value = _max; 205 } 206 if (i < nValues && models[i].getValue() != value) { 207 models[i].setValue(value); 208 } 209 // update the CV 210 _cvMap.get(cvList[i]).setValue(value); 211 // if programming, that's it 212 if (isReading || isWriting) { 213 return; 214 } else if (i < nValues && !(mfx && (i == 0 || i == (nValues - 1)))) { 215 forceMonotonic(i, value); 216 matchPoints(i); 217 } 218 } 219 220 /** 221 * Check entries on either side to see if they are set monotonically. If 222 * not, adjust. 223 * 224 * @param modifiedStepIndex number (index) of the entry 225 * @param value new value 226 */ 227 void forceMonotonic(int modifiedStepIndex, int value) { 228 // check the neighbors, and force them if needed 229 if (modifiedStepIndex > 0) { 230 // left neighbour 231 if (models[modifiedStepIndex - 1].getValue() > value) { 232 setModel(modifiedStepIndex - 1, value); 233 } 234 } 235 if (modifiedStepIndex < nValues - 1) { 236 // right neighbour 237 if (value > models[modifiedStepIndex + 1].getValue()) { 238 setModel(modifiedStepIndex + 1, value); 239 } 240 } 241 } 242 243 /** 244 * If there are fixed points specified, set linear step settings to them. 245 * @param modifiedStepIndex Index of requested break point 246 * 247 */ 248 void matchPoints(int modifiedStepIndex) { 249 if (stepCheckBoxes == null) { 250 // if no stepCheckBoxes, then GUI not present, and 251 // no need to use the matchPoints algorithm 252 return; 253 } 254 if (modifiedStepIndex < 0) { 255 log.error("matchPoints called with index too small: {}", modifiedStepIndex); 256 } 257 if (modifiedStepIndex >= stepCheckBoxes.size()) { 258 log.error("matchPoints called with index too large: {} >= {}", modifiedStepIndex, stepCheckBoxes.size()); 259 } 260 if (stepCheckBoxes.get(modifiedStepIndex) == null) { 261 log.error("matchPoints found null checkbox {}", modifiedStepIndex); 262 } 263 264 // don't do the match if this step isn't checked, 265 // which is necessary to keep from an infinite 266 // recursion 267 if (!stepCheckBoxes.get(modifiedStepIndex).isSelected()) { 268 return; 269 } 270 matchPointsLeft(modifiedStepIndex); 271 matchPointsRight(modifiedStepIndex); 272 } 273 274 void matchPointsLeft(int modifiedStepIndex) { 275 // search for checkbox if any 276 for (int i = modifiedStepIndex - 1; i >= 0; i--) { 277 if (stepCheckBoxes.get(i).isSelected()) { 278 // now have two ends to adjust 279 int leftval = _cvMap.get(cvList[i]).getValue(); 280 int rightval = _cvMap.get(cvList[modifiedStepIndex]).getValue(); 281 int steps = modifiedStepIndex - i; 282 log.debug("left found {} {} {}", leftval, rightval, steps); 283 // loop to set values 284 for (int j = i + 1; j < modifiedStepIndex; j++) { 285 int newValue = leftval + (rightval - leftval) * (j - i) / steps; 286 log.debug("left set {} to {}", j, newValue); 287 if (_cvMap.get(cvList[j]).getValue() != newValue) { 288 _cvMap.get(cvList[j]).setValue(newValue); 289 } 290 } 291 return; 292 } 293 } 294 // no match, so don't adjust 295 return; 296 } 297 298 void matchPointsRight(int modifiedStepIndex) { 299 // search for checkbox if any 300 for (int i = modifiedStepIndex + 1; i < nValues; i++) { // need at least one intervening point 301 if (stepCheckBoxes.get(i).isSelected()) { 302 // now have two ends to adjust 303 int rightval = _cvMap.get(cvList[i]).getValue(); 304 int leftval = _cvMap.get(cvList[modifiedStepIndex]).getValue(); 305 int steps = i - modifiedStepIndex; 306 log.debug("right found {} {} {}", leftval, rightval, steps); 307 // loop to set values 308 for (int j = modifiedStepIndex + 1; j < i; j++) { 309 int newValue = leftval + (rightval - leftval) * (j - modifiedStepIndex) / steps; 310 log.debug("right set {} to {}", j, newValue); 311 if (_cvMap.get(cvList[j]).getValue() != newValue) { 312 _cvMap.get(cvList[j]).setValue(newValue); 313 } 314 } 315 return; 316 } 317 } 318 // no match, so don't adjust 319 return; 320 } 321 322 @Override 323 public ValueState getState() { 324 int i; 325 for (i = 0; i < numCvs; i++) { 326 if (_cvMap.get(cvList[i]).getState() == ValueState.UNKNOWN) { 327 return ValueState.UNKNOWN; 328 } 329 } 330 for (i = 0; i < numCvs; i++) { 331 if (_cvMap.get(cvList[i]).getState() == ValueState.EDITED) { 332 return ValueState.EDITED; 333 } 334 } 335 for (i = 0; i < numCvs; i++) { 336 if (_cvMap.get(cvList[i]).getState() == ValueState.FROMFILE) { 337 return ValueState.FROMFILE; 338 } 339 } 340 for (i = 0; i < numCvs; i++) { 341 if (_cvMap.get(cvList[i]).getState() == ValueState.READ) { 342 return ValueState.READ; 343 } 344 } 345 for (i = 0; i < numCvs; i++) { 346 if (_cvMap.get(cvList[i]).getState() == ValueState.STORED) { 347 return ValueState.STORED; 348 } 349 } 350 log.error("getState did not decode a possible state"); 351 return ValueState.UNKNOWN; 352 } 353 354 // to complete this class, fill in the routines to handle "Value" parameter 355 // and to read/write/hear parameter changes. 356 @Override 357 public String getValueString() { 358 StringBuffer buf = new StringBuffer(); 359 for (int i = 0; i < models.length; i++) { 360 if (i != 0) { 361 buf.append(","); 362 } 363 buf.append(Integer.toString(models[i].getValue())); 364 } 365 return buf.toString(); 366 } 367 368 /** 369 * Set value from a String value. 370 * <p> 371 * Requires the format written by getValueString, not implemented yet 372 */ 373 @Override 374 public void setValue(String value) { 375 log.debug("skipping setValue in SpeedTableVarValue"); 376 } 377 378 @Override 379 public void setIntValue(int i) { 380 log.warn("setIntValue doesn't make sense for a speed table: {}", i); 381 } 382 383 @Override 384 public int getIntValue() { 385 log.warn("getValue doesn't make sense for a speed table"); 386 return 0; 387 } 388 389 @Override 390 public Object getValueObject() { 391 return null; 392 } 393 394 @Override 395 public Component getCommonRep() { 396 log.warn("getValue not implemented yet"); 397 return new JLabel("speed table"); 398 } 399 400 public void setValue(int value) { 401 log.warn("setValue doesn't make sense for a speed table: {}", value); 402 } 403 404 Color _defaultColor; 405 406 // implement an abstract member to set colors 407 @Override 408 void setColor(Color c) { 409 // prop.firePropertyChange("Value", null, null); 410 } 411 412 @Override 413 public Component getNewRep(String format) { 414 final int GRID_Y_BUTTONS = 3; 415 // put together a new panel in scroll pane 416 JPanel j = new JPanel(); 417 418 GridBagLayout g = new GridBagLayout(); 419 GridBagConstraints cs = new GridBagConstraints(); 420 j.setLayout(g); 421 422 initStepCheckBoxes(); 423 424 for (int i = 0; i < nValues; i++) { 425 cs.gridy = 0; 426 cs.gridx = i; 427 428 CvValue cv = _cvMap.get(cvList[i]); 429 JSlider s = new VarSlider(models[i], cv, i + 1); 430 s.setOrientation(JSlider.VERTICAL); 431 s.addChangeListener(this); 432 433 ValueState currentState = cv.getState(); 434 int currentValue = cv.getValue(); 435 436 DecVariableValue decVal = new DecVariableValue("val" + i, "", "", false, false, false, false, 437 cvList[i], "VVVVVVVV", _min, _max, 438 _cvMap, _status, ""); 439 decVal.setValue(currentValue); 440 decVal.setState(currentState); 441 442 Component v = decVal.getCommonRep(); 443 String start = ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("TextStep") 444 + " " + (i + 1); 445 ((JTextField) v).setToolTipText(CvUtil.addCvDescription(start, "CV " + cvList[i], null)); 446 ((JComponent) v).setBorder(null); // pack tighter 447 448 if (mfx && (i == 0 || i == (nValues - 1))) { 449 ((JTextField) v).setEditable(false); // disable field editing 450 s.setEnabled(false); // disable slider adjustment 451 } 452 453 g.setConstraints(v, cs); 454 455 if (i == 0 && log.isDebugEnabled()) { 456 log.debug("Font size {}", v.getFont().getSize()); 457 } 458 float newSize = v.getFont().getSize() * 0.8f; 459 v.setFont(v.getFont().deriveFont(newSize)); 460 j.add(v); 461 462 cs.gridy++; 463 g.setConstraints(s, cs); 464 465 j.add(s); 466 467 cs.gridy++; 468 JCheckBox b = stepCheckBoxes.get(i); 469 470 g.setConstraints(b, cs); 471 j.add(b, cs); 472 473 UserPreferencesManager upm = InstanceManager.getDefault(UserPreferencesManager.class); 474 Object speedTableNumbersSelectionObj = upm.getProperty(SpeedTableNumbers.class.getName(), "selection"); 475 476 if (speedTableNumbersSelectionObj != null) { 477 SpeedTableNumbers speedTableNumbersSelection = 478 SpeedTableNumbers.valueOf(speedTableNumbersSelectionObj.toString()); 479 480 if ((speedTableNumbersSelection != SpeedTableNumbers.None) 481 && (speedTableNumbersSelection.filter(i) || (i==0) || (i+1 == nValues))) { 482 483 cs.gridy++; 484 JLabel num = new JLabel(""+(i+1)); 485 num.setToolTipText("Step Number"); 486 487 g.setConstraints(num, cs); 488 j.add(num, cs); 489 } 490 } 491 } 492 493 // add control buttons 494 JPanel k = new JPanel(); 495 JButton b; 496 k.add(b = new JButton(ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("ButtonForceStraight"))); 497 b.setToolTipText(ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("TooltipForceStraight")); 498 b.addActionListener(new java.awt.event.ActionListener() { 499 @Override 500 public void actionPerformed(java.awt.event.ActionEvent e) { 501 doForceStraight(e); 502 } 503 }); 504 k.add(b = new JButton(ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("ButtonMatchEnds"))); 505 b.setToolTipText(ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("TooltipMatchEnds")); 506 b.addActionListener(new java.awt.event.ActionListener() { 507 @Override 508 public void actionPerformed(java.awt.event.ActionEvent e) { 509 doMatchEnds(e); 510 } 511 }); 512 k.add(b = new JButton(ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("ButtonConstantRatio"))); 513 b.setToolTipText(ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("TooltipConstantRatio")); 514 b.addActionListener(new java.awt.event.ActionListener() { 515 @Override 516 public void actionPerformed(java.awt.event.ActionEvent e) { 517 doRatioCurve(e); 518 } 519 }); 520 k.add(b = new JButton(ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("ButtonLogCurve"))); 521 b.setToolTipText(ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("TooltipLogCurve")); 522 b.addActionListener(new java.awt.event.ActionListener() { 523 @Override 524 public void actionPerformed(java.awt.event.ActionEvent e) { 525 doLogCurve(e); 526 } 527 }); 528 k.add(b = new JButton(ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("ButtonShiftLeft"))); 529 b.setToolTipText(ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("TooltipShiftLeft")); 530 b.addActionListener(new java.awt.event.ActionListener() { 531 @Override 532 public void actionPerformed(java.awt.event.ActionEvent e) { 533 doShiftLeft(e); 534 } 535 }); 536 k.add(b = new JButton(ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("ButtonShiftRight"))); 537 b.setToolTipText(ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("TooltipShiftRight")); 538 b.addActionListener(new java.awt.event.ActionListener() { 539 @Override 540 public void actionPerformed(java.awt.event.ActionEvent e) { 541 doShiftRight(e); 542 } 543 }); 544 545 cs.gridy = GRID_Y_BUTTONS; 546 cs.gridx = 0; 547 cs.gridwidth = GridBagConstraints.RELATIVE; 548 g.setConstraints(k, cs); 549 550 // add Vstart & Vhigh if applicable 551 JPanel l = new JPanel(); 552 553 JPanel val = new JPanel(); 554 val.setLayout(new BorderLayout()); 555 val.add(j, BorderLayout.NORTH); 556 val.add(k, BorderLayout.CENTER); 557 if (mfx) { 558 val.add(l, BorderLayout.SOUTH); 559 } 560 561 updateRepresentation(val); 562 return val; 563 564 } 565 566 void initStepCheckBoxes() { 567 stepCheckBoxes = new ArrayList<JCheckBox>(); 568 for (int i = 0; i < nValues; i++) { 569 JCheckBox b = new JCheckBox(); 570 b.setToolTipText(ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("TooltipCheckToFix")); 571 stepCheckBoxes.add(b); 572 } 573 } 574 575 /** 576 * Set the values to a straight line from _min to _max 577 * @param e Event triggering this operation 578 */ 579 void doForceStraight(java.awt.event.ActionEvent e) { 580 _cvMap.get(cvList[0]).setValue(_min); 581 _cvMap.get(cvList[nValues - 1]).setValue(_max); 582 doMatchEnds(e); 583 } 584 585 /** 586 * Set the values to a straight line from existing ends 587 * @param e Event triggering this operation 588 */ 589 void doMatchEnds(java.awt.event.ActionEvent e) { 590 int first = _cvMap.get(cvList[0]).getValue(); 591 int last = _cvMap.get(cvList[nValues - 1]).getValue(); 592 log.debug(" first={} last={}", first, last); 593 // to avoid repeatedly bumping up later values, push the first one 594 // all the way up now 595 _cvMap.get(cvList[0]).setValue(last); 596 // and push each one down 597 for (int i = 0; i < nValues; i++) { 598 int value = first + i * (last - first) / (nValues - 1); 599 _cvMap.get(cvList[i]).setValue(value); 600 } 601// enforceEndPointsMfx(); 602 } 603 604 /** 605 * Set a constant ratio curve 606 * @param e Event triggering this operation 607 */ 608 void doRatioCurve(java.awt.event.ActionEvent e) { 609 double first = _cvMap.get(cvList[0]).getValue(); 610 if (first < 1.) { 611 first = 1.; 612 } 613 double last = _cvMap.get(cvList[nValues - 1]).getValue(); 614 if (last < first + 1) { 615 last = first + 1.; 616 } 617 double step = Math.log(last / first) / (nValues - 1); 618 log.debug("log ratio step is {}", step); 619 // to avoid repeatedly bumping up later values, push the first one 620 // all the way up now 621 _cvMap.get(cvList[0]).setValue((int) Math.round(last)); 622 // and push each one down 623 for (int i = 0; i < nValues; i++) { 624 int value = (int) (Math.floor(first * Math.exp(step * i))); 625 _cvMap.get(cvList[i]).setValue(value); 626 } 627// enforceEndPointsMfx(); 628 } 629 630 /** 631 * Set a log curve 632 * @param e Event triggering this operation 633 */ 634 void doLogCurve(java.awt.event.ActionEvent e) { 635 double first = _cvMap.get(cvList[0]).getValue(); 636 double last = _cvMap.get(cvList[nValues - 1]).getValue(); 637 if (last < first + 1.) { 638 last = first + 1.; 639 } 640 double factor = 1. / 10.; 641 // to avoid repeatedly bumping up later values, push the second one 642 // all the way up now 643 _cvMap.get(cvList[1]).setValue((int) Math.round(last)); 644 // and push each one down (except the first, left as it was) 645 double ratio = Math.pow(1. - factor, nValues - 1.); 646 double limit = last + (last - first) * ratio; 647 for (int i = 1; i < nValues; i++) { 648 double previous = limit - (limit - first) * ratio / Math.pow(1. - factor, nValues - 1. - i); 649 int value = (int) (Math.floor(previous)); 650 _cvMap.get(cvList[i]).setValue(value); 651 } 652// enforceEndPointsMfx(); 653 } 654 655 /** 656 * Shift the curve one CV to left. The last entry is left unchanged. 657 * @param e Event triggering this operation 658 */ 659 void doShiftLeft(java.awt.event.ActionEvent e) { 660 for (int i = 0; i < nValues - 1; i++) { 661 int value = _cvMap.get(cvList[i + 1]).getValue(); 662 _cvMap.get(cvList[i]).setValue(value); 663 } 664// enforceEndPointsMfx(); 665 } 666 667 /** 668 * Shift the curve one CV to right. The first entry is left unchanged. 669 * @param e Event triggering this operation 670 */ 671 void doShiftRight(java.awt.event.ActionEvent e) { 672 for (int i = nValues - 1; i > 0; i--) { 673 int value = _cvMap.get(cvList[i - 1]).getValue(); 674 _cvMap.get(cvList[i]).setValue(value); 675 } 676// enforceEndPointsMfx(); 677 } 678 679 /** 680 * IDLE if a read/write operation is not in progress. During an operation, 681 * it indicates the index of the CV to handle when the current programming 682 * operation finishes. 683 */ 684 private int _progState = IDLE; 685 686 private static final int IDLE = -1; 687 boolean isReading; 688 boolean isWriting; 689 690 /** 691 * Count number of retries done 692 */ 693 private int retries = 0; 694 695 /** 696 * Define maximum number of retries of read/write operations before moving 697 * on 698 */ 699 private static final int RETRY_MAX = 2; 700 701 boolean onlyChanges = false; 702 703 /** 704 * Notify the connected CVs of a state change from above 705 * 706 */ 707 @Override 708 public void setCvState(ValueState state) { 709 _cvMap.get(cvList[0]).setState(state); 710 } 711 712 @Override 713 public boolean isChanged() { 714 for (int i = 0; i < numCvs; i++) { 715 if (considerChanged(_cvMap.get(cvList[i]))) { 716 // this one is changed, return true 717 return true; 718 } 719 } 720 return false; 721 } 722 723 @Override 724 public void readChanges() { 725 if (log.isDebugEnabled()) { 726 log.debug("readChanges() invoked"); 727 } 728 if (!isChanged()) { 729 return; 730 } 731 onlyChanges = true; 732 setBusy(true); // will be reset when value changes 733 if (_progState != IDLE) { 734 log.warn("Programming state {}, not IDLE, in read()", _progState); 735 } 736 isReading = true; 737 isWriting = false; 738 _progState = -1; 739 retries = 0; 740 if (log.isDebugEnabled()) { 741 log.debug("start series of read operations"); 742 } 743 readNext(); 744 } 745 746 @Override 747 public void writeChanges() { 748 if (log.isDebugEnabled()) { 749 log.debug("writeChanges() invoked"); 750 } 751 if (!isChanged()) { 752 return; 753 } 754 onlyChanges = true; 755 if (getReadOnly()) { 756 log.error("unexpected write operation when readOnly is set"); 757 } 758 setBusy(true); // will be reset when value changes 759 super.setState(ValueState.STORED); 760 if (_progState != IDLE) { 761 log.warn("Programming state {}, not IDLE, in write()", _progState); 762 } 763 isReading = false; 764 isWriting = true; 765 _progState = -1; 766 retries = 0; 767 if (log.isDebugEnabled()) { 768 log.debug("start series of write operations"); 769 } 770 writeNext(); 771 } 772 773 @Override 774 public void readAll() { 775 if (log.isDebugEnabled()) { 776 log.debug("readAll() invoked"); 777 } 778 onlyChanges = false; 779 setToRead(false); 780 setBusy(true); // will be reset when value changes 781 if (_progState != IDLE) { 782 log.warn("Programming state {}, not IDLE, in read()", _progState); 783 } 784 isReading = true; 785 isWriting = false; 786 _progState = -1; 787 retries = 0; 788 if (log.isDebugEnabled()) { 789 log.debug("start series of read operations"); 790 } 791 readNext(); 792 } 793 794 @Override 795 public void writeAll() { 796 if (log.isDebugEnabled()) { 797 log.debug("writeAll() invoked"); 798 } 799 onlyChanges = false; 800 if (getReadOnly()) { 801 log.error("unexpected write operation when readOnly is set"); 802 } 803 setToWrite(false); 804 setBusy(true); // will be reset when value changes 805 super.setState(ValueState.STORED); 806 if (_progState != IDLE) { 807 log.warn("Programming state {}, not IDLE, in write()", _progState); 808 } 809 isReading = false; 810 isWriting = true; 811 _progState = -1; 812 retries = 0; 813 if (log.isDebugEnabled()) { 814 log.debug("start series of write operations"); 815 } 816 writeNext(); 817 } 818 819 void readNext() { 820 // read operation start/continue 821 // check for retry if needed 822 if ((_progState >= 0) && (retries < RETRY_MAX) 823 && (_cvMap.get(cvList[_progState]).getState() != ValueState.READ)) { 824 // need to retry an error; leave progState (CV number) as it was 825 retries++; 826 } else { 827 // normal read operation of next CV 828 retries = 0; 829 _progState++; // progState is the index of the CV to handle now 830 } 831 832 if (_progState >= numCvs) { 833 // done, clean up and return to invoker 834 _progState = IDLE; 835 isReading = false; 836 isWriting = false; 837 setBusy(false); 838 return; 839 } 840 // not done, proceed to do the next 841 CvValue cv = _cvMap.get(cvList[_progState]); 842 ValueState state = cv.getState(); 843 if (log.isDebugEnabled()) { 844 log.debug("invoke CV read index {} cv state {}", _progState, state); 845 } 846 if (!onlyChanges || considerChanged(cv)) { 847 cv.read(_status); 848 } else { 849 readNext(); // repeat until end 850 } 851 } 852 853 void writeNext() { 854 // write operation start/continue 855 // check for retry if needed 856 if ((_progState >= 0) && (retries < RETRY_MAX) 857 && (_cvMap.get(cvList[_progState]).getState() != ValueState.STORED)) { 858 // need to retry an error; leave progState (CV number) as it was 859 retries++; 860 } else { 861 // normal read operation of next CV 862 retries = 0; 863 _progState++; // progState is the index of the CV to handle now 864 } 865 866 if (_progState >= numCvs) { 867 _progState = IDLE; 868 isReading = false; 869 isWriting = false; 870 setBusy(false); 871 return; 872 } 873 CvValue cv = _cvMap.get(cvList[_progState]); 874 ValueState state = cv.getState(); 875 if (log.isDebugEnabled()) { 876 log.debug("invoke CV write index {} cv state {}", _progState, state); 877 } 878 if (!onlyChanges || considerChanged(cv)) { 879 cv.write(_status); 880 } else { 881 writeNext(); 882 } 883 } 884 885 // handle incoming parameter notification 886 @Override 887 public void propertyChange(java.beans.PropertyChangeEvent e) { 888 if (log.isDebugEnabled()) { 889 log.debug("property changed event - name: {}", e.getPropertyName()); 890 } 891 // notification from CV; check for Value being changed 892 if (e.getPropertyName().equals("Busy") && ((Boolean) e.getNewValue()).equals(Boolean.FALSE)) { 893 // busy transitions drive an ongoing programming operation 894 // see if actually done 895 896 if (isReading) { 897 readNext(); 898 } else if (isWriting) { 899 writeNext(); 900 } else { 901 return; 902 } 903 } else if (e.getPropertyName().equals("State")) { 904 CvValue cv = _cvMap.get(cvList[0]); 905 if (log.isDebugEnabled()) { 906 log.debug("CV State changed to {}", cv.getState()); 907 } 908 setState(cv.getState()); 909 } else if (e.getPropertyName().equals("Value")) { 910 // find the CV that sent this 911 CvValue cv = (CvValue) e.getSource(); 912 int value = cv.getValue(); 913 // find the index of that CV 914 for (int i = 0; i < numCvs; i++) { 915 if (_cvMap.get(cvList[i]) == cv) { 916 // this is the one, so use this i 917 setModel(i, value); 918 break; 919 } 920 } 921// enforceEndPointsMfx(); 922 } 923 } 924 925 /* Internal class extends a JSlider so that its color is consistent with 926 * an underlying CV; we return one of these in getNewRep. 927 * <p> 928 * Unlike similar cases elsewhere, this doesn't have to listen to 929 * value changes. Those are handled automagically since we're sharing the same 930 * model between this object and others. And this is listening to 931 * a CV state, not a variable. 932 * 933 * @author Bob Jacobsen Copyright (C) 2001 934 */ 935 public class VarSlider extends JSlider { 936 937 VarSlider(BoundedRangeModel m, CvValue var, int step) { 938 super(m); 939 _var = var; 940 // get the original color right 941 setBackground(_var.getColor()); 942 if (_var.getColor() == _var.getDefaultColor()) { 943 setOpaque(false); 944 } else { 945 setOpaque(true); 946 } 947 // tooltip label 948 String start = ResourceBundle.getBundle("jmri.jmrit.symbolicprog.SymbolicProgBundle").getString("TextStep") 949 + " " + step; 950 setToolTipText(CvUtil.addCvDescription(start, "CV " + var.number(), null)); 951 // listen for changes to original state 952 _var.addPropertyChangeListener(new java.beans.PropertyChangeListener() { 953 @Override 954 public void propertyChange(java.beans.PropertyChangeEvent e) { 955 originalPropertyChanged(e); 956 } 957 }); 958 } 959 960 CvValue _var; 961 962 void originalPropertyChanged(java.beans.PropertyChangeEvent e) { 963 if (log.isDebugEnabled()) { 964 log.debug("VarSlider saw property change: {}", e); 965 } 966 // update this color from original state 967 if (e.getPropertyName().equals("State")) { 968 setBackground(_var.getColor()); 969 if (_var.getColor() == _var.getDefaultColor()) { 970 setOpaque(false); 971 } else { 972 setOpaque(true); 973 } 974 } 975 } 976 977 } // end class definition 978 979 // clean up connections when done 980 @Override 981 public void dispose() { 982 if (log.isDebugEnabled()) { 983 log.debug("dispose"); 984 } 985 // the connection is to cvNum through cvNum+numCvs (28 values typical) 986 for (int i = 0; i < numCvs; i++) { 987 _cvMap.get(cvList[i]).removePropertyChangeListener(this); 988 } 989 990 // do something about the VarSlider objects 991 } 992 993 // initialize logging 994 private final static Logger log = LoggerFactory.getLogger(SpeedTableVarValue.class); 995 996}