001package jmri.jmrit.symbolicprog; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import static java.nio.charset.Charset.defaultCharset; 006import static java.nio.charset.Charset.isSupported; 007 008import java.awt.event.ActionEvent; 009import java.awt.event.ActionListener; 010import java.beans.PropertyChangeEvent; 011import java.beans.PropertyChangeListener; 012import java.util.Arrays; 013import java.util.List; 014import java.util.Objects; 015import java.util.Vector; 016import javax.swing.JButton; 017import javax.swing.JFrame; 018import javax.swing.JLabel; 019import javax.swing.JTextField; 020import javax.swing.table.AbstractTableModel; 021 022import jmri.AddressedProgrammer; 023import jmri.jmrit.decoderdefn.DecoderFile; 024import jmri.util.CvUtil; 025import jmri.util.jdom.LocaleSelector; 026import jmri.util.swing.JmriJOptionPane; 027 028import org.jdom2.Attribute; 029import org.jdom2.Content; 030import org.jdom2.Element; 031import org.jdom2.util.IteratorIterable; 032 033/** 034 * Table data model for display of variables in symbolic programmer. Also 035 * responsible for loading from the XML file. 036 * 037 * @author Bob Jacobsen Copyright (C) 2001, 2006, 2010 038 * @author Howard G. Penny Copyright (C) 2005 039 * @author Daniel Boudreau Copyright (C) 2007 040 * @author Dave Heap Copyright (C) 2012 Added support for Marklin mfx style 041 * speed table 042 */ 043public class VariableTableModel extends AbstractTableModel implements ActionListener, PropertyChangeListener { 044 045 private String[] headers; 046 047 private Vector<VariableValue> rowVector = new Vector<>(); // vector of Variable items 048 private CvTableModel _cvModel; // reference to external table model 049 private Vector<JButton> _writeButtons = new Vector<>(); 050 private Vector<JButton> _readButtons = new Vector<>(); 051 private JLabel _status; 052 protected transient volatile DecoderFile _df = null; 053 054 /** 055 * Define the columns. 056 * <p> 057 * Values understood are: "Name", "Value", "Range", "Read", "Write", 058 * "Comment", "CV", "Mask", "State". 059 * <p> 060 * For each, a property key in SymbolicProgBundle by the same name allows 061 * i18n. 062 * 063 * @param status variable status. 064 * @param h values headers array. 065 * @param cvModel cv table model to use. 066 */ 067 public VariableTableModel(JLabel status, String[] h, CvTableModel cvModel) { 068 super(); 069 _status = status; 070 _cvModel = cvModel; 071 headers = Arrays.copyOf(h, h.length); 072 } 073 074 // basic methods for AbstractTableModel implementation 075 @Override 076 public int getRowCount() { 077 return rowVector.size(); 078 } 079 080 @Override 081 public int getColumnCount() { 082 return headers.length; 083 } 084 085 @Override 086 public String getColumnName(int col) { 087 log.debug("getColumnName {}", col); 088 return Bundle.getMessage(headers[col]); // I18N 089 } 090 091 @Override 092 public Class<?> getColumnClass(int col) { 093 // log.debug("getColumnClass {}", col; 094 switch (headers[col]) { 095 case "Value": 096 return JTextField.class; 097 case "Read": 098 case "Write": 099 return JButton.class; 100 default: 101 return String.class; 102 } 103 } 104 105 @Override 106 public boolean isCellEditable(int row, int col) { 107 log.debug("isCellEditable {}", col); 108 if (headers[col].equals("Value")) { 109 return true; 110 } else if (headers[col].equals("Read")) { 111 return true; 112 } else 113 return headers[col].equals("Write") && !((rowVector.elementAt(row))).getReadOnly(); 114 } 115 116 public VariableValue getVariable(int row) { 117 return (rowVector.elementAt(row)); 118 } 119 120 public String getLabel(int row) { 121 return (rowVector.elementAt(row)).label(); 122 } 123 124 public String getItem(int row) { 125 return (rowVector.elementAt(row)).item(); 126 } 127 128 public String getCvName(int row) { 129 return (rowVector.elementAt(row)).cvName(); 130 } 131 132 public String getValString(int row) { 133 return (rowVector.elementAt(row)).getValueString(); 134 } 135 136 public void setIntValue(int row, int val) { 137 (rowVector.elementAt(row)).setIntValue(val); 138 } 139 140 public void setState(int row, AbstractValue.ValueState val) { 141 log.debug("setState row: {} val: {}", row, val); 142 (rowVector.elementAt(row)).setState(val); 143 } 144 145 public AbstractValue.ValueState getState(int row) { 146 return (rowVector.elementAt(row)).getState(); 147 } 148 149 /* 150 * Request a "unique representation", e.g. something we can show 151 * for the row-th variable. 152 */ 153 public Object getRep(int row, String format) { 154 VariableValue v = rowVector.elementAt(row); 155 return v.getNewRep(format); 156 } 157 158 @Override 159 public Object getValueAt(int row, int col) { 160 // log.debug("getValueAt {} {}", row, col; 161 if (row >= rowVector.size()) { 162 log.debug("row index greater than row vector size"); 163 return "Error"; 164 } 165 VariableValue v = rowVector.elementAt(row); 166 if (v == null) { 167 log.debug("v is null!"); 168 return "Error value"; 169 } 170 switch (headers[col]) { 171 case "Value": 172 return v.getCommonRep(); 173 case "Read": 174 // NOI18N 175 return _readButtons.elementAt(row); 176 case "Write": 177 // NOI18N 178 return _writeButtons.elementAt(row); 179 case "CV": 180 // NOI18N 181 return "" + v.getCvNum(); 182 case "Name": 183 // NOI18N 184 return "" + v.label(); 185 case "Comment": 186 // NOI18N 187 return v.getComment(); 188 case "Mask": 189 // NOI18N 190 return v.getMask(); 191 case "State": 192 // NOI18N 193 AbstractValue.ValueState state = v.getState(); 194 switch (state) { 195 case UNKNOWN: 196 return "Unknown"; 197 case READ: 198 return "Read"; 199 case EDITED: 200 return "Edited"; 201 case STORED: 202 return "Stored"; 203 case FROMFILE: 204 return "From file, known previously valid"; 205 case FROMFILEUNKNOWN: 206 return "From file, possibly still at default"; 207 case FROMDEFAULT: 208 return "From decoder definition defaults"; 209 default: 210 return "inconsistent"; 211 } 212 case "Range": 213 return v.rangeVal(); 214 default: 215 return "Later, dude"; 216 } 217 } 218 219 @Override 220 public void setValueAt(Object value, int row, int col) { 221 log.debug("setvalueAt {} {} {}", row, col, value); 222 setFileDirty(true); 223 } 224 225 /** 226 * Load one row in the VariableTableModel, by reading in the Element 227 * containing its definition. 228 * <p> 229 * Note that this method does not pass a reference to a {@link DecoderFile} 230 * instance, hence include/exclude processing at the sub-variable level is 231 * not possible and will be ignored. 232 * <p> 233 * Use of {@link #setRow(int row, Element e, DecoderFile df)} is preferred. 234 * 235 * @param row number of row to fill 236 * @param e Element of type "variable" 237 */ 238 public void setRow(int row, Element e) { 239 this.setRow(row, e, null); 240 } 241 242 /** 243 * Load one row in the VariableTableModel, by reading in the Element 244 * containing its definition. 245 * <p> 246 * Invoked from {@link DecoderFile} 247 * 248 * @param row number of row to fill 249 * @param e Element of type "variable" 250 * @param df the source {@link DecoderFile} instance (needed for 251 * include/exclude processing at the sub-variable level) 252 */ 253 public void setRow(int row, Element e, DecoderFile df) { 254 // get the values for the VariableValue ctor 255 _df = df; 256 String name = LocaleSelector.getAttribute(e, "label"); // Note the name variable is actually the label attribute 257 log.debug("Starting to setRow \"{}\"", name); 258 String item = (e.getAttribute("item") != null 259 ? e.getAttribute("item").getValue() 260 : null); 261 // as a special case, if no item, use label 262 if (item == null) { 263 item = name; 264 log.debug("no item attribute, used label \"{}\"", name); 265 } 266 // as a special case, if no label, use item 267 if (name == null) { 268 name = item; 269 log.debug("no label attribute, used item attribute \"{}\"", item); 270 } 271 272 String comment = LocaleSelector.getAttribute(e, "comment"); 273 274 String CV = ""; 275 if (e.getAttribute("CV") != null) { 276 CV = e.getAttribute("CV").getValue(); 277 } 278 String mask; 279 if (e.getAttribute("mask") != null) { 280 mask = e.getAttribute("mask").getValue(); 281 } else { 282 mask = "VVVVVVVV"; // default mask is 8 bits 283 // for some VariableValue types this is replaced in #processDecVal() by larger mask if maxVal>256 284 } 285 286 boolean readOnly = e.getAttribute("readOnly") != null && e.getAttribute("readOnly").getValue().equals("yes"); 287 boolean infoOnly = e.getAttribute("infoOnly") != null && e.getAttribute("infoOnly").getValue().equals("yes"); 288 boolean writeOnly = e.getAttribute("writeOnly") != null && e.getAttribute("writeOnly").getValue().equals("yes"); 289 boolean opsOnly = e.getAttribute("opsOnly") != null && e.getAttribute("opsOnly").getValue().equals("yes"); 290 291 // Handle special case of opsOnly mode & specific programmer type 292 if (_cvModel.getProgrammer() != null) { 293 if (opsOnly && !AddressedProgrammer.class.isAssignableFrom(_cvModel.getProgrammer().getClass())) { 294 // opsOnly but not Ops mode, so adjust 295 readOnly = false; 296 writeOnly = false; 297 infoOnly = true; 298 } 299 } 300 301 JButton bw = new JButton(Bundle.getMessage("ButtonWrite")); 302 _writeButtons.addElement(bw); 303 JButton br = new JButton(Bundle.getMessage("ButtonRead")); 304 _readButtons.addElement(br); 305 setButtonsReadWrite(readOnly, infoOnly, writeOnly, bw, br, row); 306 307 if (_cvModel == null) { 308 log.error("CvModel reference is null; cannot add variables"); 309 return; 310 } 311 if (!CV.equals("")) { // some variables have no CV per se 312 List<String> cvList = CvUtil.expandCvList(CV); 313 if (cvList.isEmpty()) { 314 _cvModel.addCV(CV, readOnly, infoOnly, writeOnly); 315 } else { // or require expansion 316 for (String s : cvList) { 317 _cvModel.addCV(s, readOnly, infoOnly, writeOnly); 318 } 319 } 320 } 321 322 // decode and handle specific types 323 Element child; 324 VariableValue v; 325 if ((child = e.getChild("decVal")) != null) { 326 v = processDecVal(child, name, comment, readOnly, infoOnly, writeOnly, opsOnly, CV, mask, item); 327 328 } else if ((child = e.getChild("hexVal")) != null) { 329 v = processHexVal(child, name, comment, readOnly, infoOnly, writeOnly, opsOnly, CV, mask, item); 330 331 } else if ((child = e.getChild("enumVal")) != null) { 332 v = processEnumVal(child, name, comment, readOnly, infoOnly, writeOnly, opsOnly, CV, mask, item); 333 334 } else if ((child = e.getChild("compositeVal")) != null) { 335 // loop over the choices 336 v = processCompositeVal(child, name, comment, readOnly, infoOnly, writeOnly, opsOnly, CV, mask, item); 337 338 } else if ((child = e.getChild("speedTableVal")) != null) { 339 v = processSpeedTableVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 340 341 } else if ((child = e.getChild("longAddressVal")) != null) { 342 v = processLongAddressVal(CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 343 344 } else if ((child = e.getChild("shortAddressVal")) != null) { 345 v = processShortAddressVal(name, comment, readOnly, infoOnly, writeOnly, opsOnly, CV, mask, item, child); 346 347 } else if ((child = e.getChild("splitVal")) != null) { 348 v = processSplitVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 349 350 } else if ((child = e.getChild("splitHexVal")) != null) { 351 v = processSplitHexVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 352 353 } else if ((child = e.getChild("splitHundredsVal")) != null) { 354 v = processSplitHundredsVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 355 356 } else if ((child = e.getChild("splitTextVal")) != null) { 357 v = processSplitTextVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 358 359 } else if ((child = e.getChild("splitDateTimeVal")) != null) { 360 v = processSplitDateTimeVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 361 362 } else if ((child = e.getChild("splitEnumVal")) != null) { 363 v = processSplitEnumVal(child, CV, readOnly, infoOnly, writeOnly, name, comment, opsOnly, mask, item); 364 365 } else { 366 reportBogus(e); 367 return; 368 } 369 370 processModifierElements(e, v); 371 372 setToolTip(e, v); 373 374 // record new variable, update state, hook up listeners 375 rowVector.addElement(v); 376 v.setState(AbstractValue.ValueState.FROMDEFAULT); 377 v.addPropertyChangeListener(this); 378 379 // set to default value if specified (CV load may later override this) 380 if (setDefaultValue(e, v)) { 381 // need to correct state of associated CV(s) & handle a possible CV List 382 List<String> cvList = CvUtil.expandCvList(CV); // see if CV is in list format 383 if (cvList.isEmpty()) { 384 cvList.add(CV); // it's an ordinary CV so add it as such 385 } 386 for (String theCV : cvList) { 387 log.debug("Setting CV={} of '{}'to {}", theCV, CV, AbstractValue.ValueState.FROMDEFAULT.getName()); 388 _cvModel.getCvByNumber(theCV).setState(AbstractValue.ValueState.FROMDEFAULT); // correct for transition to "edited" 389 } 390 } 391 } 392 393 /** 394 * If there are any modifier elements, process them by e.g. setting 395 * attributes on the VariableValue. 396 * 397 * @param e Element that's source of info 398 * @param variable Variable to load 399 */ 400 protected void processModifierElements(final Element e, final VariableValue variable) { 401 QualifierAdder qa = new QualifierAdder() { 402 @Override 403 protected Qualifier createQualifier(VariableValue variable2, String relation, String value) { 404 return new ValueQualifier(variable, variable2, Integer.parseInt(value), relation); 405 } 406 407 @Override 408 protected void addListener(java.beans.PropertyChangeListener qc) { 409 variable.addPropertyChangeListener(qc); 410 } 411 }; 412 413 qa.processModifierElements(e, this); 414 } 415 416 /** 417 * If there's a "default" attribute, or matching defaultItem element, set 418 * that value to start. 419 * 420 * @param e Element that's source of info 421 * @param variable Variable to load 422 * @return true if the value was set 423 */ 424 boolean setDefaultValue(Element e, VariableValue variable) { 425 Attribute a; 426 boolean set = false; 427 if ((a = e.getAttribute("default")) != null) { 428 String val = a.getValue(); 429 variable.setValue(val); 430 set = true; 431 } 432 // check for matching child 433 List<Element> elements = e.getChildren("defaultItem"); 434 for (Element defaultItem : elements) { 435 if (_df != null && DecoderFile.isIncluded(defaultItem, _df.getProductID(), _df.getModel(), _df.getFamily(), "", "")) { 436 log.debug("element included by productID={} model={} family={}", _df.getProductID(), _df.getModel(), _df.getFamily()); 437 variable.setValue(defaultItem.getAttribute("default").getValue()); 438 return true; 439 } 440 } 441 return set; 442 } 443 444 protected VariableValue processCompositeVal(Element child, String name, String comment, boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly, String CV, String mask, String item) { 445 int count = 0; 446 IteratorIterable<Content> iterator = child.getDescendants(); 447 while (iterator.hasNext()) { 448 Object ex = iterator.next(); 449 if (ex instanceof Element) { 450 if (((Element) ex).getName().equals("compositeChoice")) { 451 count++; 452 } 453 } 454 } 455 456 VariableValue v; 457 CompositeVariableValue v1 = new CompositeVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, 0, count, _cvModel.allCvMap(), _status, item); 458 v = v1; // v1 is of CompositeVariableType, so doesn't need casts 459 460 v1.nItems(count); 461 handleCompositeValChildren(child, v1); 462 v1.lastItem(); 463 return v; 464 } 465 466 /** 467 * Recursively walk the child compositeChoice elements, working through the 468 * compositeChoiceGroup elements as needed. 469 * <p> 470 * Adapted from handleEnumValChildren for use in LocoIO Legacy tool. 471 * 472 * @param e Element that's source of info 473 * @param var Variable to load 474 */ 475 protected void handleCompositeValChildren(Element e, CompositeVariableValue var) { 476 List<Element> local = e.getChildren(); 477 for (Element el : local) { 478 log.debug("processing element='{}' name='{}' choice='{}' value='{}'", el.getName(), LocaleSelector.getAttribute(el, "name"), LocaleSelector.getAttribute(el, "choice"), el.getAttribute("value")); 479 if (_df != null && !DecoderFile.isIncluded(el, _df.getProductID(), _df.getModel(), _df.getFamily(), "", "")) { 480 log.debug("element excluded by productID={} model={} family={}", _df.getProductID(), _df.getModel(), _df.getFamily()); 481 continue; 482 } 483 if (el.getName().equals("compositeChoice")) { 484 // Create the choice 485 String choice = LocaleSelector.getAttribute(el, "choice"); 486 var.addChoice(choice); 487 // for each choice, capture the settings 488 List<Element> lSetting = el.getChildren("compositeSetting"); 489 for (Element settingElement : lSetting) { 490 String varName = LocaleSelector.getAttribute(settingElement, "label"); 491 String value = settingElement.getAttribute("value").getValue(); 492 var.addSetting(choice, varName, findVar(varName), value); 493 } 494 } else if (el.getName().equals("compositeChoiceGroup")) { 495 // no tree to manage as in enumGroup 496 handleCompositeValChildren(el, var); 497 } 498 log.debug("element processed"); 499 } 500 } 501 502 protected VariableValue processDecVal(Element child, String name, String comment, boolean readOnly, boolean infoOnly, 503 boolean writeOnly, boolean opsOnly, String CV, String mask, String item) 504 throws NumberFormatException { 505 VariableValue v; 506 Attribute a; 507 int minVal = 0; 508 int maxVal = 255; 509 if ((a = child.getAttribute("min")) != null) { 510 minVal = Integer.parseInt(a.getValue()); 511 } 512 if ((a = child.getAttribute("max")) != null) { 513 maxVal = Integer.parseInt(a.getValue()); 514 } 515 int factor = 1; 516 if ((a = child.getAttribute("factor")) != null) { 517 factor = Integer.parseInt(a.getValue()); 518 } 519 int offset = 0; 520 if ((a = child.getAttribute("offset")) != null) { 521 offset = Integer.parseInt(a.getValue()); 522 } 523 if (maxVal > 255 && Objects.equals(mask, "VVVVVVVV")) { 524 mask = VariableValue.getMaxMask(maxVal); // replaces the default 8 bit mask when no mask is provided in xml 525 log.debug("Created mask {} for DecVar CV {}", mask, name); 526 } 527 v = new DecVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, 528 CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, offset, factor); 529 _cvModel.registerCvToVariableMapping(CV, name); 530 return v; 531 } 532 533 protected VariableValue processEnumVal(Element child, String name, String comment, boolean readOnly, boolean infoOnly, 534 boolean writeOnly, boolean opsOnly, String CV, String mask, String item) 535 throws NumberFormatException { 536 VariableValue v; 537 int count = 0; 538 IteratorIterable<Content> iterator = child.getDescendants(); 539 while (iterator.hasNext()) { 540 Object ex = iterator.next(); 541 if (ex instanceof Element) { 542 if (((Element) ex).getName().equals("enumChoice")) { 543 count++; 544 } 545 } 546 } 547 Attribute a; 548 int maxVal = 255; 549 if ((a = child.getAttribute("max")) != null) { 550 // requires explicit max attribute for Radix mask if not all options are filled by enum 551 maxVal = Integer.parseInt(a.getValue()); 552 } else { 553 maxVal = count; 554 } 555 EnumVariableValue v1 = new EnumVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, 556 CV, mask, 0, maxVal, _cvModel.allCvMap(), _status, item); 557 v = v1; // v1 is of EnumVariableValue type, so doesn't need casts 558 _cvModel.registerCvToVariableMapping(CV, name); 559 560 v1.nItems(count); 561 handleEnumValChildren(child, v1); 562 v1.lastItem(); 563 return v; 564 } 565 566 protected VariableValue processSplitEnumVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 567 VariableValue v; 568 Attribute a; 569 int minVal = 0; 570 int maxVal = 255; 571 String highCV = null; 572 573 int count = 0; 574 IteratorIterable<Content> iterator = child.getDescendants(); 575 while (iterator.hasNext()) { 576 Object ex = iterator.next(); 577 if (ex instanceof Element) { 578 if (((Element) ex).getName().equals("enumChoice")) { 579 count++; 580 } 581 } 582 } 583 584 if ((a = child.getAttribute("highCV")) != null) { 585 highCV = a.getValue(); 586 _cvModel.addCV("" + (highCV), readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 587 _cvModel.registerCvToVariableMapping(highCV, name); 588 } 589 int factor = 1; 590 if ((a = child.getAttribute("factor")) != null) { 591 factor = Integer.parseInt(a.getValue()); 592 } 593 int offset = 0; 594 if ((a = child.getAttribute("offset")) != null) { 595 offset = Integer.parseInt(a.getValue()); 596 } 597 String uppermask = "VVVVVVVV"; 598 if ((a = child.getAttribute("upperMask")) != null) { 599 uppermask = a.getValue(); 600 } 601 String extra3 = "0"; 602 if ((a = child.getAttribute("min")) != null) { 603 extra3 = a.getValue(); 604 } 605 String extra4 = Long.toUnsignedString(~0); 606 if ((a = child.getAttribute("max")) != null) { 607 extra4 = a.getValue(); 608 } 609 610 SplitEnumVariableValue v1 = new SplitEnumVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, highCV, factor, offset, uppermask, null, null, extra3, extra4); 611 v = v1; // v1 is of EnunVariableValue type, so doesn't need casts 612 _cvModel.registerCvToVariableMapping(CV, name); 613 614 v1.nItems(count); 615 616 handleSplitEnumValChildren(child, v1); 617 v1.lastItem(); 618 return v; 619 } 620 621 /** 622 * Recursively walk the child enumChoice elements, working through the 623 * enumChoiceGroup elements as needed. 624 * 625 * @param e Element that's source of info 626 * @param var Variable to load 627 */ 628 protected void handleSplitEnumValChildren(Element e, SplitEnumVariableValue var) { 629 List<Element> local = e.getChildren(); 630 631 for (Element el : local) { 632 log.debug("processing element='{}' name='{}' choice='{}' value='{}'", el.getName(), LocaleSelector.getAttribute(el, "name"), LocaleSelector.getAttribute(el, "choice"), el.getAttribute("value")); 633 if (_df != null && !DecoderFile.isIncluded(el, _df.getProductID(), _df.getModel(), _df.getFamily(), "", "")) { 634 log.debug("element excluded by productID={} model={} family={}", _df.getProductID(), _df.getModel(), _df.getFamily()); 635 continue; 636 } 637 if (el.getName().equals("enumChoice")) { 638 Attribute valAttr = el.getAttribute("value"); 639 if (valAttr == null) { 640 var.addItem(LocaleSelector.getAttribute(el, "choice")); 641 } else { 642 var.addItem(LocaleSelector.getAttribute(el, "choice"), 643 Integer.parseInt(valAttr.getValue())); 644 } 645 } else if (el.getName().equals("enumChoiceGroup")) { 646 var.startGroup(LocaleSelector.getAttribute(el, "name")); 647 handleSplitEnumValChildren(el, var); 648 var.endGroup(); 649 } 650 log.debug("element processed"); 651 } 652 } 653 654 /** 655 * Recursively walk the child enumChoice elements, working through the 656 * enumChoiceGroup elements as needed. 657 * 658 * @param e Element that's source of info 659 * @param var Variable to load 660 */ 661 protected void handleEnumValChildren(Element e, EnumVariableValue var) { 662 List<Element> local = e.getChildren(); 663 for (Element el : local) { 664 log.debug("processing element='{}' name='{}' choice='{}' value='{}'", el.getName(), LocaleSelector.getAttribute(el, "name"), LocaleSelector.getAttribute(el, "choice"), el.getAttribute("value")); 665 if (_df != null && !DecoderFile.isIncluded(el, _df.getProductID(), _df.getModel(), _df.getFamily(), "", "")) { 666 log.debug("element excluded by productID={} model={} family={}", _df.getProductID(), _df.getModel(), _df.getFamily()); 667 continue; 668 } 669 if (el.getName().equals("enumChoice")) { 670 Attribute valAttr = el.getAttribute("value"); 671 if (valAttr == null) { 672 var.addItem(LocaleSelector.getAttribute(el, "choice")); 673 } else { 674 var.addItem(LocaleSelector.getAttribute(el, "choice"), 675 Integer.parseInt(valAttr.getValue())); 676 } 677 } else if (el.getName().equals("enumChoiceGroup")) { 678 var.startGroup(LocaleSelector.getAttribute(el, "name")); 679 handleEnumValChildren(el, var); 680 var.endGroup(); 681 } 682 log.debug("element processed"); 683 } 684 } 685 686 protected VariableValue processHexVal(Element child, String name, String comment, boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly, String CV, String mask, String item) throws NumberFormatException { 687 VariableValue v; 688 Attribute a; 689 int minVal = 0; 690 int maxVal = 255; 691 if ((a = child.getAttribute("min")) != null) { 692 minVal = Integer.valueOf(a.getValue(), 16); 693 } 694 if ((a = child.getAttribute("max")) != null) { 695 maxVal = Integer.valueOf(a.getValue(), 16); 696 } 697 if (maxVal > 255 && Objects.equals(mask, "VVVVVVVV")) { 698 mask = VariableValue.getMaxMask(maxVal); // replaces the default 8 bit mask when no mask is provided in xml 699 log.debug("Created mask {} for Hex CV {}", mask, name); 700 } 701 v = new HexVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item); 702 _cvModel.registerCvToVariableMapping(CV, name); 703 return v; 704 } 705 706 protected VariableValue processLongAddressVal(String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) { 707 VariableValue v; 708 int minVal = 0; 709 int maxVal = 255; 710 _cvModel.addCV("18", readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 711 v = new LongAddrVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, _cvModel.allCvMap().get("18")); 712 _cvModel.registerCvToVariableMapping(CV, name); 713 _cvModel.registerCvToVariableMapping("18", name); // see fixed value two lines up 714 return v; 715 } 716 717 protected VariableValue processShortAddressVal(String name, String comment, boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly, String CV, String mask, String item, Element child) { 718 VariableValue v; 719 ShortAddrVariableValue v1 = new ShortAddrVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, _cvModel.allCvMap(), _status, item); 720 v = v1; 721 // get specifics if any 722 List<Element> l = child.getChildren("shortAddressChanges"); 723 for (Element element : l) { 724 v1.setModifiedCV(element.getAttribute("cv").getValue()); 725 } 726 _cvModel.registerCvToVariableMapping(CV, name); 727 return v; 728 } 729 730 protected VariableValue processSpeedTableVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 731 VariableValue v; 732 Attribute a; 733 int minVal = 0; 734 int maxVal = 255; 735 if ((a = child.getAttribute("min")) != null) { 736 minVal = Integer.parseInt(a.getValue()); 737 } 738 if ((a = child.getAttribute("max")) != null) { 739 maxVal = Integer.parseInt(a.getValue()); 740 } 741 Attribute entriesAttr = child.getAttribute("entries"); 742 int entries = 28; 743 try { 744 if (entriesAttr != null) { 745 entries = entriesAttr.getIntValue(); 746 } 747 } catch (org.jdom2.DataConversionException ignored) { 748 } 749 Attribute ESUAttr = child.getAttribute("mfx"); 750 boolean mfxFlag = false; 751 try { 752 if (ESUAttr != null) { 753 mfxFlag = ESUAttr.getBooleanValue(); 754 } 755 } catch (org.jdom2.DataConversionException ignored) { 756 } 757 // ensure all CVs exist 758 for (int i = 0; i < entries; i++) { 759 _cvModel.addCV(Integer.toString(Integer.parseInt(CV) + i), readOnly, infoOnly, writeOnly); 760 _cvModel.registerCvToVariableMapping(Integer.toString(Integer.parseInt(CV) + i), name); 761 762 } 763 if (mfxFlag) { 764 _cvModel.addCV("2", readOnly, infoOnly, writeOnly); 765 _cvModel.registerCvToVariableMapping("2", name); 766 767 _cvModel.addCV("5", readOnly, infoOnly, writeOnly); 768 _cvModel.registerCvToVariableMapping("5", name); 769 770 } 771 v = new SpeedTableVarValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, entries, mfxFlag); 772 return v; 773 } 774 775 protected VariableValue processSplitVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 776 VariableValue v; 777 Attribute a; 778 int minVal = 0; 779 int maxVal = 255; 780 String highCV = null; 781 782 if ((a = child.getAttribute("highCV")) != null) { 783 highCV = a.getValue(); 784 _cvModel.addCV("" + (highCV), readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 785 _cvModel.registerCvToVariableMapping("" + (highCV), name); 786 787 } 788 int factor = 1; 789 if ((a = child.getAttribute("factor")) != null) { 790 factor = Integer.parseInt(a.getValue()); 791 } 792 int offset = 0; 793 if ((a = child.getAttribute("offset")) != null) { 794 offset = Integer.parseInt(a.getValue()); 795 } 796 String uppermask = "VVVVVVVV"; 797 if ((a = child.getAttribute("upperMask")) != null) { 798 uppermask = a.getValue(); 799 } 800 String extra3 = "0"; 801 if ((a = child.getAttribute("min")) != null) { 802 extra3 = a.getValue(); 803 } 804 String extra4 = Long.toUnsignedString(~0); 805 if ((a = child.getAttribute("max")) != null) { 806 extra4 = a.getValue(); 807 } 808 v = new SplitVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, highCV, factor, offset, uppermask, null, null, extra3, extra4); 809 _cvModel.registerCvToVariableMapping(CV, name); 810 return v; 811 } 812 813 protected VariableValue processSplitHexVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 814 VariableValue v; 815 Attribute a; 816 int minVal = 0; 817 int maxVal = 255; 818 String highCV = null; 819 820 if ((a = child.getAttribute("highCV")) != null) { 821 highCV = a.getValue(); 822 _cvModel.addCV("" + (highCV), readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 823 _cvModel.registerCvToVariableMapping("" + (highCV), name); 824 } 825 int factor = 1; 826 if ((a = child.getAttribute("factor")) != null) { 827 factor = Integer.parseInt(a.getValue()); 828 } 829 int offset = 0; 830 if ((a = child.getAttribute("offset")) != null) { 831 offset = Integer.parseInt(a.getValue()); 832 } 833 String uppermask = "VVVVVVVV"; 834 if ((a = child.getAttribute("upperMask")) != null) { 835 uppermask = a.getValue(); 836 } 837 String extra1 = "default"; 838 if ((a = child.getAttribute("case")) != null) { 839 extra1 = a.getValue(); 840 } 841 String extra3 = "0"; 842 if ((a = child.getAttribute("min")) != null) { 843 extra3 = a.getValue(); 844 } 845 String extra4 = Long.toUnsignedString(~0, 16); 846 if ((a = child.getAttribute("max")) != null) { 847 extra4 = a.getValue(); 848 } 849 v = new SplitHexVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, highCV, factor, offset, uppermask, extra1, null, extra3, extra4); 850 _cvModel.registerCvToVariableMapping(CV, name); 851 return v; 852 } 853 854 protected VariableValue processSplitHundredsVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 855 VariableValue v; 856 Attribute a; 857 int minVal = 0; 858 int maxVal = 255; 859 String highCV = null; 860 861 if ((a = child.getAttribute("highCV")) != null) { 862 highCV = a.getValue(); 863 _cvModel.addCV("" + (highCV), readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 864 _cvModel.registerCvToVariableMapping("" + (highCV), name); 865 } 866 int factor = 1; 867 if ((a = child.getAttribute("factor")) != null) { 868 factor = Integer.parseInt(a.getValue()); 869 } 870 int offset = 0; 871 if ((a = child.getAttribute("offset")) != null) { 872 offset = Integer.parseInt(a.getValue()); 873 } 874 String uppermask = "VVVVVVVV"; 875 if ((a = child.getAttribute("upperMask")) != null) { 876 uppermask = a.getValue(); 877 } 878 String extra3 = "0"; 879 if ((a = child.getAttribute("min")) != null) { 880 extra3 = a.getValue(); 881 } 882 String extra4 = Long.toUnsignedString(~0); 883 if ((a = child.getAttribute("max")) != null) { 884 extra4 = a.getValue(); 885 } 886 v = new SplitHundredsVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, highCV, factor, offset, uppermask, null, null, extra3, extra4); 887 _cvModel.registerCvToVariableMapping(CV, name); 888 return v; 889 } 890 891 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST", 892 justification="I18N of Error Message") 893 protected VariableValue processSplitTextVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 894 VariableValue v; 895 Attribute a; 896 int minVal = 0; 897 int maxVal = 255; 898 String highCV = null; 899 900 if ((a = child.getAttribute("min")) != null) { 901 minVal = Integer.parseInt(a.getValue()); 902 } 903 if ((a = child.getAttribute("max")) != null) { 904 maxVal = Integer.parseInt(a.getValue()); 905 } 906 if ((a = child.getAttribute("highCV")) != null) { 907 highCV = a.getValue(); 908 _cvModel.addCV("" + (highCV), readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 909 _cvModel.registerCvToVariableMapping("" + (highCV), name); 910 } 911 int factor = 1; 912 if ((a = child.getAttribute("factor")) != null) { 913 factor = Integer.parseInt(a.getValue()); 914 } 915 int offset = 0; 916 if ((a = child.getAttribute("offset")) != null) { 917 offset = Integer.parseInt(a.getValue()); 918 } 919 String uppermask = "VVVVVVVV"; 920 if ((a = child.getAttribute("upperMask")) != null) { 921 uppermask = a.getValue(); 922 } 923 String match = null; 924 if ((a = child.getAttribute("match")) != null) { 925 match = a.getValue(); 926 } 927 String termByte = "0"; 928 if ((a = child.getAttribute("termByte")) != null) { 929 termByte = a.getValue(); 930 } 931 String padByte = "0"; 932 if ((a = child.getAttribute("padByte")) != null) { 933 padByte = a.getValue(); 934 } 935 String charSet = defaultCharset().name(); 936 if ((a = child.getAttribute("charSet")) != null) { 937 charSet = a.getValue(); 938 } 939 boolean ok; 940 try { 941 ok = isSupported(charSet); 942 } catch (IllegalArgumentException ex) { 943 ok = false; 944 } 945 if (!ok) { 946 synchronized (this) { 947 JmriJOptionPane.showMessageDialog(new JFrame(), Bundle.getMessage("UnsupportedCharset", charSet, name), 948 Bundle.getMessage("DecoderDefError"), JmriJOptionPane.ERROR_MESSAGE); // NOI18N 949 } 950 log.error(Bundle.getMessage("UnsupportedCharset", charSet, name)); 951 } 952 v = new SplitTextVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, highCV, factor, offset, uppermask, match, termByte, padByte, charSet); 953 _cvModel.registerCvToVariableMapping(CV, name); 954 return v; 955 } 956 957 protected VariableValue processSplitDateTimeVal(Element child, String CV, boolean readOnly, boolean infoOnly, boolean writeOnly, String name, String comment, boolean opsOnly, String mask, String item) throws NumberFormatException { 958 VariableValue v; 959 Attribute a; 960 int minVal = 0; 961 int maxVal = 255; 962 boolean varRreadOnly = true; // unable to parse text dates accurately enough so force variable (but not CVs) to be read only 963 String highCV = null; 964 965 if ((a = child.getAttribute("min")) != null) { 966 minVal = Integer.parseInt(a.getValue()); 967 } 968 if ((a = child.getAttribute("max")) != null) { 969 maxVal = Integer.parseInt(a.getValue()); 970 } 971 if ((a = child.getAttribute("highCV")) != null) { 972 highCV = a.getValue(); 973 _cvModel.addCV("" + (highCV), readOnly, infoOnly, writeOnly); // ensure 2nd CV exists 974 _cvModel.registerCvToVariableMapping("" + (highCV), name); 975 } 976 int factor = 1; 977 int offset = 0; 978 979 String uppermask = "VVVVVVVV"; 980 if ((a = child.getAttribute("upperMask")) != null) { 981 uppermask = a.getValue(); 982 } 983 String extra1 = "2000-01-01T00:00:00"; // The S9.3.2 RailCom epoch 984 // Java epoch is "1970-01-01T00:00:00" 985 if ((a = child.getAttribute("base")) != null) { 986 extra1 = a.getValue(); 987 } 988 String extra2 = "1"; 989 if ((a = child.getAttribute("factor")) != null) { 990 extra2 = a.getValue(); 991 } 992 String extra3 = "Seconds"; 993 if ((a = child.getAttribute("unit")) != null) { 994 extra3 = a.getValue(); 995 } 996 String extra4 = "default"; 997 if ((a = child.getAttribute("display")) != null) { 998 extra4 = a.getValue(); 999 } 1000 v = new SplitDateTimeVariableValue(name, comment, "", varRreadOnly, infoOnly, writeOnly, opsOnly, CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, item, highCV, factor, offset, uppermask, extra1, extra2, extra3, extra4); 1001 _cvModel.registerCvToVariableMapping(CV, name); 1002 return v; 1003 } 1004 1005 protected void setButtonsReadWrite(boolean readOnly, boolean infoOnly, boolean writeOnly, JButton bw, JButton br, int row) { 1006 if (readOnly || infoOnly) { 1007 // readOnly or infoOnly, config write, read buttons 1008 if (writeOnly) { 1009 bw.setEnabled(true); 1010 bw.setActionCommand("W" + row); 1011 bw.addActionListener(this); 1012 } else { 1013 bw.setEnabled(false); 1014 } 1015 if (infoOnly) { 1016 br.setEnabled(false); 1017 } else { 1018 br.setActionCommand("R" + row); 1019 br.addActionListener(this); 1020 } 1021 } else { 1022 // not readOnly or infoOnly, config write, read buttons 1023 bw.setActionCommand("W" + row); 1024 bw.addActionListener(this); 1025 if (writeOnly) { 1026 br.setEnabled(false); 1027 } else { 1028 br.setActionCommand("R" + row); 1029 br.addActionListener(this); 1030 } 1031 } 1032 } 1033 1034 public void setButtonModeFromProgrammer() { 1035 if (_cvModel.getProgrammer() == null || !_cvModel.getProgrammer().getCanRead()) { 1036 for (JButton b : _readButtons) { 1037 b.setEnabled(false); 1038 } 1039 } 1040 } 1041 1042 protected void setToolTip(Element e, VariableValue v) { 1043 // back to general processing 1044 // add tooltip text if present 1045 { 1046 String t; 1047 if ((t = LocaleSelector.getAttribute(e, "tooltip")) != null) { 1048 v.setToolTipText(t); 1049 } 1050 } 1051 } 1052 1053 void reportBogus(Element elem) { 1054 log.error("Did not find a valid type in {}", elem.getChildren()); 1055 for (Attribute a : elem.getAttributes()) log.error(" attribute: {}",a); 1056 } 1057 1058 /** 1059 * Configure from a constant. This is like setRow (which processes a 1060 * variable Element). 1061 * 1062 * @param e element to set. 1063 */ 1064 @SuppressFBWarnings(value = "NP_LOAD_OF_KNOWN_NULL_VALUE", 1065 justification = "null mask parameter to ConstantValue constructor expected.") 1066 public void setConstant(Element e) { 1067 // get the values for the VariableValue ctor 1068 String stdname = e.getAttribute("item").getValue(); 1069 log.debug("Starting to setConstant \"{}\"", stdname); 1070 1071 String name = LocaleSelector.getAttribute(e, "label"); 1072 if (name == null || name.equals("")) { 1073 name = stdname; 1074 } 1075 1076 String comment = LocaleSelector.getAttribute(e, "comment"); 1077 1078 String mask = null; 1079 1080 // intrinsically readOnly, so use just that branch 1081 JButton bw = new JButton(); 1082 _writeButtons.addElement(bw); 1083 1084 // config read button as a dummy - there's really nothing to read 1085 JButton br = new JButton("Read"); // NOI18N 1086 _readButtons.addElement(br); 1087 1088 // no CV references are added here 1089 // have to handle various value types, see "snippet" 1090 Attribute a; 1091 1092 // set to default value if specified (CV load will later override this) 1093 int defaultVal = 0; 1094 if ((a = e.getAttribute("default")) != null) { 1095 String val = a.getValue(); 1096 log.debug("Found default value: {} for {}", val, stdname); 1097 defaultVal = Integer.parseInt(val); 1098 } 1099 1100 // create the specific object 1101 ConstantValue v = new ConstantValue(name, comment, "", true, true, false, false, 1102 "", mask, defaultVal, defaultVal, 1103 _cvModel.allCvMap(), _status, stdname); 1104 1105 // record new variable, update state, hook up listeners 1106 rowVector.addElement(v); 1107 v.setState(AbstractValue.ValueState.FROMFILE); 1108 v.addPropertyChangeListener(this); 1109 1110 // set to default value if specified (CV load will later override this) 1111 if ((a = e.getAttribute("default")) != null) { 1112 String val = a.getValue(); 1113 log.debug("Found default value: {} for {}", val, name); 1114 v.setIntValue(defaultVal); 1115 } 1116 } 1117 1118 /** 1119 * Programmatically create a new DecVariableValue from parameters. 1120 * 1121 * @param name variable name. 1122 * @param CV CV string. 1123 * @param comment variable comment. 1124 * @param mask CV mask. 1125 * @param readOnly true if read only, else false. 1126 * @param infoOnly true if information only, else false. 1127 * @param writeOnly true if write only, else false. 1128 * @param opsOnly true if ops only, else false. 1129 */ 1130 public void newDecVariableValue(String name, String CV, String comment, String mask, 1131 boolean readOnly, boolean infoOnly, boolean writeOnly, boolean opsOnly) { 1132 setFileDirty(true); 1133 1134 int minVal = 0; 1135 int maxVal = 255; 1136 _cvModel.addCV("" + CV, readOnly, infoOnly, writeOnly); 1137 1138 int row = getRowCount(); 1139 1140 // config write button 1141 JButton bw = new JButton(Bundle.getMessage("ButtonWrite")); 1142 bw.setActionCommand("W" + row); 1143 bw.addActionListener(this); 1144 _writeButtons.addElement(bw); 1145 1146 // config read button 1147 JButton br = new JButton(Bundle.getMessage("ButtonRead")); 1148 br.setActionCommand("R" + row); 1149 br.addActionListener(this); 1150 _readButtons.addElement(br); 1151 1152 VariableValue v = new DecVariableValue(name, comment, "", readOnly, infoOnly, writeOnly, opsOnly, 1153 CV, mask, minVal, maxVal, _cvModel.allCvMap(), _status, null); 1154 rowVector.addElement(v); 1155 v.addPropertyChangeListener(this); 1156 } 1157 1158 @Override 1159 public void actionPerformed(ActionEvent e) { 1160 if (log.isDebugEnabled()) { 1161 log.debug("action performed, command: {}", e.getActionCommand()); 1162 } 1163 setFileDirty(true); 1164 char b = e.getActionCommand().charAt(0); 1165 int row = Integer.parseInt(e.getActionCommand().substring(1)); 1166 log.debug("event on {} row {}", b, row); 1167 if (b == 'R') { 1168 // read command 1169 read(row); 1170 } else { 1171 // write command 1172 write(row); 1173 } 1174 } 1175 1176 /** 1177 * Command reading of a particular variable. 1178 * 1179 * @param i row number 1180 */ 1181 public void read(int i) { 1182 VariableValue v = rowVector.elementAt(i); 1183 v.readAll(); 1184 } 1185 1186 /** 1187 * Command writing of a particular variable. 1188 * 1189 * @param i row number 1190 */ 1191 public void write(int i) { 1192 VariableValue v = rowVector.elementAt(i); 1193 v.writeAll(); 1194 } 1195 1196 @Override 1197 public void propertyChange(PropertyChangeEvent e) { 1198 if (log.isDebugEnabled()) { 1199 log.debug("prop changed {} new value: {}{} Source {}", e.getPropertyName(), e.getNewValue(), e.getPropertyName().equals("State") ? (" (" + ((AbstractValue.ValueState) e.getNewValue()).getName() + ") ") : " ", e.getSource()); 1200 } 1201 if (e.getNewValue() == null) { 1202 log.error("new value of {} should not be null!", e.getPropertyName(), new Exception()); 1203 } 1204 // set dirty only if edited or read 1205 if (e.getPropertyName().equals("State") 1206 && ((AbstractValue.ValueState) e.getNewValue()) == AbstractValue.ValueState.READ 1207 || e.getPropertyName().equals("State") 1208 && ((AbstractValue.ValueState) e.getNewValue()) == AbstractValue.ValueState.EDITED) { 1209 setFileDirty(true); 1210 1211 } 1212 fireTableDataChanged(); 1213 } 1214 1215 public void configDone() { 1216 fireTableDataChanged(); 1217 } 1218 1219 /** 1220 * Represents any change to values, etc, hence rewriting the file is 1221 * desirable. 1222 * 1223 * @return true if dirty, else false. 1224 */ 1225 public boolean fileDirty() { 1226 return _fileDirty; 1227 } 1228 1229 public void setFileDirty(boolean b) { 1230 _fileDirty = b; 1231 } 1232 private boolean _fileDirty; 1233 1234 /** 1235 * Check for change to values, etc, hence rewriting the decoder is 1236 * desirable. 1237 * 1238 * @return true if dirty, else false. 1239 */ 1240 public boolean decoderDirty() { 1241 int len = rowVector.size(); 1242 for (int i = 0; i < len; i++) { 1243 if (((rowVector.elementAt(i))).getState() == AbstractValue.ValueState.EDITED) { 1244 return true; 1245 } 1246 } 1247 return false; 1248 } 1249 1250 /** 1251 * Returns the (first) variable that matches a given name string. 1252 * <p> 1253 * Searches first for "item", the true name, but if none found will attempt 1254 * to find a matching "label". In that case, only the default language is 1255 * checked. 1256 * 1257 * @param name search string. 1258 * @return first matching variable found. 1259 */ 1260 public VariableValue findVar(String name) { 1261 for (int i = 0; i < getRowCount(); i++) { 1262 if (name.equals(getItem(i))) { 1263 log.trace("findVar matched '{}' by Item", name); 1264 return getVariable(i); 1265 } 1266 } 1267 for (int i = 0; i < getRowCount(); i++) { 1268 if (name.equals(getLabel(i))) { 1269 log.trace("findVar matched '{}' by Label rather than Item", name); 1270 return getVariable(i); 1271 } 1272 } 1273 log.debug("findVar did not match {}, returns null", name); 1274 return null; 1275 } 1276 1277 /** 1278 * Returns the index of the first variable that matches a given name string. 1279 * <p> 1280 * Checks the search string against every variable's "item", the true name, 1281 * then against their "label" (default language only) and finally the 1282 * CV name before moving on to the next variable if none of those match. 1283 * 1284 * @param name search string. 1285 * @return index of the first matching variable found. 1286 */ 1287 public int findVarIndex(String name) { 1288 return findVarIndex(name, false); 1289 } 1290 1291 /** 1292 * Returns the index of a variable that matches a given name string. 1293 * <p> 1294 * Checks the search string against every variable's "item", the true name, 1295 * then against their "label" (default language only) and finally the 1296 * CV name before moving on to the next variable if none of those match. 1297 * 1298 * Depending on the second parameter, it will return the index of the first 1299 * or last variable in our internal rowVector that matches the given string. 1300 * 1301 * @param name search string. 1302 * @param searchFromEnd If true, will start searching from the end. 1303 * @return index of the first matching variable found. 1304 */ 1305 public int findVarIndex(String name, boolean searchFromEnd) { 1306 if(searchFromEnd) { 1307 for (int i = getRowCount() - 1; i >= 0; i--) { 1308 if (name.equals(getItem(i))) { 1309 return i; 1310 } 1311 if (name.equals(getLabel(i))) { 1312 return i; 1313 } 1314 if (name.equals("CV" + getCvName(i))) { 1315 return i; 1316 } 1317 } 1318 } else { 1319 for (int i = 0; i < getRowCount(); i++) { 1320 if (name.equals(getItem(i))) { 1321 return i; 1322 } 1323 if (name.equals(getLabel(i))) { 1324 return i; 1325 } 1326 if (name.equals("CV" + getCvName(i))) { 1327 return i; 1328 } 1329 } 1330 } 1331 return -1; 1332 } 1333 1334 public void dispose() { 1335 log.debug("dispose"); 1336 1337 // remove buttons 1338 for (int i = 0; i < _writeButtons.size(); i++) { 1339 _writeButtons.elementAt(i).removeActionListener(this); 1340 } 1341 for (int i = 0; i < _readButtons.size(); i++) { 1342 _readButtons.elementAt(i).removeActionListener(this); 1343 } 1344 1345 // remove variables listeners 1346 for (int i = 0; i < rowVector.size(); i++) { 1347 VariableValue v = rowVector.elementAt(i); 1348 v.removePropertyChangeListener(this); 1349 v.dispose(); 1350 } 1351 1352 headers = null; 1353 1354 rowVector.removeAllElements(); 1355 rowVector = null; 1356 1357 _cvModel = null; 1358 1359 _writeButtons.removeAllElements(); 1360 _writeButtons = null; 1361 1362 _readButtons.removeAllElements(); 1363 _readButtons = null; 1364 1365 _status = null; 1366 } 1367 1368 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(VariableTableModel.class); 1369 1370}