001package jmri.jmrit.symbolicprog.tabbedframe; 002 003import java.awt.Color; 004import java.awt.Component; 005import java.awt.Font; 006import java.awt.GridBagConstraints; 007import java.awt.GridBagLayout; 008import java.awt.Insets; 009import java.awt.event.ItemEvent; 010import java.awt.event.ItemListener; 011import java.io.IOException; 012import java.lang.reflect.Field; 013import java.util.*; 014import javax.swing.AbstractButton; 015import javax.swing.BorderFactory; 016import javax.swing.Box; 017import javax.swing.BoxLayout; 018import javax.swing.JButton; 019import javax.swing.JComponent; 020import javax.swing.JLabel; 021import javax.swing.JPanel; 022import javax.swing.JProgressBar; 023import javax.swing.JScrollPane; 024import javax.swing.JSeparator; 025import javax.swing.JTable; 026import javax.swing.JTextField; 027import javax.swing.JToggleButton; 028import javax.swing.JWindow; 029import javax.swing.RowSorter; 030import javax.swing.SortOrder; 031import javax.swing.SwingConstants; 032import javax.swing.table.TableModel; 033import javax.swing.table.TableRowSorter; 034import jmri.jmrit.roster.RosterEntry; 035import jmri.jmrit.symbolicprog.AbstractValue; 036import jmri.jmrit.symbolicprog.CvTableModel; 037import jmri.jmrit.symbolicprog.CvValue; 038import jmri.jmrit.symbolicprog.DccAddressPanel; 039import jmri.jmrit.symbolicprog.FnMapPanel; 040import jmri.jmrit.symbolicprog.FnMapPanelESU; 041import jmri.jmrit.symbolicprog.PrintCvAction; 042import jmri.jmrit.symbolicprog.Qualifier; 043import jmri.jmrit.symbolicprog.QualifierAdder; 044import jmri.jmrit.symbolicprog.SymbolicProgBundle; 045import jmri.jmrit.symbolicprog.ValueEditor; 046import jmri.jmrit.symbolicprog.CvValueRenderer; 047import jmri.jmrit.symbolicprog.VariableTableModel; 048import jmri.jmrit.symbolicprog.VariableValue; 049import jmri.util.CvUtil; 050import jmri.util.StringUtil; 051import jmri.util.davidflanagan.HardcopyWriter; 052import jmri.util.jdom.LocaleSelector; 053import org.jdom2.Attribute; 054import org.jdom2.Element; 055import org.slf4j.Logger; 056import org.slf4j.LoggerFactory; 057 058/** 059 * Provide the individual panes for the TabbedPaneProgrammer. 060 * <p> 061 * Note that this is not only the panes carrying variables, but also the special 062 * purpose panes for the CV table, etc. 063 * <p> 064 * This class implements PropertyChangeListener so that it can be notified when 065 * a variable changes its busy status at the end of a programming read/write 066 * operation. 067 * 068 * There are four read and write operation types, all of which have to be 069 * handled carefully: 070 * <DL> 071 * <DT>Write Changes<DD>This must write changes that occur after the operation 072 * starts, because the act of writing a variable/CV may change another. For 073 * example, writing CV 1 will mark CV 29 as changed. 074 * <p> 075 * The definition of "changed" is operationally in the 076 * {@link jmri.jmrit.symbolicprog.VariableValue#isChanged} member function. 077 * 078 * <DT>Write All<DD>Like write changes, this might have to go back and re-write 079 * a variable depending on what has previously happened. It should write every 080 * variable (at least) once. 081 * <DT>Read All<DD>This should read every variable once. 082 * <img src="doc-files/PaneProgPane-ReadAllSequenceDiagram.png" alt="UML Sequence diagram"> 083 * <DT>Read Changes<DD>This should read every variable that's marked as changed. 084 * Currently, we use a common definition of changed with the write operations, 085 * and that someday might have to change. 086 * 087 * </DL> 088 * 089 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2004, 2005, 2006 090 * @author D Miller Copyright 2003 091 * @author Howard G. Penny Copyright (C) 2005 092 * @author Dave Heap Copyright (C) 2014, 2019 093 * @see jmri.jmrit.symbolicprog.VariableValue#isChanged 094 */ 095/* 096 * @startuml jmri/jmrit/symbolicprog/tabbedframe/doc-files/PaneProgPane-ReadAllSequenceDiagram.png 097 * actor User 098 * box "PaneProgPane" 099 * participant readPaneAll 100 * participant prepReadPane 101 * participant nextRead 102 * participant executeRead 103 * participant propertyChange 104 * participant replyWhileProgrammingVar 105 * participant restartProgramming 106 * end box 107 * box "VariableValue" 108 * participant readAll 109 * participant readChanges 110 * end box 111 * 112 * control Programmer 113 * User -> readPaneAll: Read All Sheets 114 * activate readPaneAll 115 * readPaneAll -> prepReadPane 116 * activate prepReadPane 117 * prepReadPane --> readPaneAll 118 * deactivate prepReadPane 119 * deactivate prepReadPane 120 * readPaneAll -> nextRead 121 * activate nextRead 122 * nextRead -> executeRead 123 * activate executeRead 124 * executeRead -> readAll 125 * activate readAll 126 * readAll -> Programmer 127 * activate Programmer 128 * readAll --> executeRead 129 * deactivate readAll 130 * executeRead --> nextRead 131 * deactivate executeRead 132 * nextRead --> readPaneAll 133 * deactivate nextRead 134 * deactivate readPaneAll 135 * == Callback after read completes == 136 * Programmer -> propertyChange 137 * activate propertyChange 138 * note over propertyChange 139 * if the first read failed, 140 * setup a second read of 141 * the same value. 142 * otherwise, setup a read of 143 * the next value. 144 * end note 145 * deactivate Programmer 146 * propertyChange -> User: CV value or error 147 * propertyChange -> replyWhileProgrammingVar 148 * activate replyWhileProgrammingVar 149 * replyWhileProgrammingVar -> restartProgramming 150 * activate restartProgramming 151 * restartProgramming -> nextRead 152 * activate nextRead 153 * nextRead -> executeRead 154 * activate executeRead 155 * executeRead -> readAll 156 * activate readAll 157 * readAll -> Programmer 158 * activate Programmer 159 * readAll --> executeRead 160 * deactivate readAll 161 * executeRead -> nextRead 162 * deactivate executeRead 163 * nextRead --> restartProgramming 164 * deactivate nextRead 165 * restartProgramming --> replyWhileProgrammingVar 166 * deactivate restartProgramming 167 * replyWhileProgrammingVar --> propertyChange 168 * deactivate replyWhileProgrammingVar 169 * deactivate propertyChange 170 * deactivate Programmer 171 * == Callback triggered repeat occurs until no more values == 172 * @enduml 173 */ 174public class PaneProgPane extends javax.swing.JPanel 175 implements java.beans.PropertyChangeListener { 176 177 static final String LAST_GRIDX = "last_gridx"; 178 static final String LAST_GRIDY = "last_gridy"; 179 180 protected CvTableModel _cvModel; 181 protected VariableTableModel _varModel; 182 protected PaneContainer container; 183 protected RosterEntry rosterEntry; 184 185 boolean _cvTable; 186 187 protected JPanel bottom; 188 189 transient ItemListener l1; 190 protected transient ItemListener l2; 191 transient ItemListener l3; 192 protected transient ItemListener l4; 193 transient ItemListener l5; 194 transient ItemListener l6; 195 196 boolean isCvTablePane = false; 197 198 /** 199 * Store name of this programmer Tab (pane) 200 */ 201 String mName = ""; 202 203 /** 204 * Construct a null object. 205 * <p> 206 * Normally only used for tests and to pre-load classes. 207 */ 208 public PaneProgPane() { 209 } 210 211 public PaneProgPane(PaneContainer parent, String name, Element pane, CvTableModel cvModel, VariableTableModel varModel, Element modelElem, RosterEntry pRosterEntry) { 212 this(parent, name, pane, cvModel, varModel, modelElem, pRosterEntry, false); 213 } 214 215 /** 216 * Construct the Pane from the XML definition element. 217 * 218 * @param parent The parent pane 219 * @param name Name to appear on tab of pane 220 * @param pane The JDOM Element for the pane definition 221 * @param cvModel Already existing TableModel containing the CV 222 * definitions 223 * @param varModel Already existing TableModel containing the variable 224 * definitions 225 * @param modelElem "model" element from the Decoder Index, used to check 226 * what decoder options are present. 227 * @param pRosterEntry The current roster entry, used to get sound labels. 228 * @param isProgPane True if the pane is a default programmer pane 229 */ 230 public PaneProgPane(PaneContainer parent, String name, Element pane, CvTableModel cvModel, VariableTableModel varModel, Element modelElem, RosterEntry pRosterEntry, boolean isProgPane) { 231 232 container = parent; 233 mName = name; 234 _cvModel = cvModel; 235 _varModel = varModel; 236 rosterEntry = pRosterEntry; 237 238 // when true a cv table with compare was loaded into pane 239 _cvTable = false; 240 241 // This is a JPanel containing a JScrollPane, containing a 242 // laid-out JPanel 243 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 244 245 // Add tooltip (if available) 246 setToolTipText(jmri.util.jdom.LocaleSelector.getAttribute(pane, "tooltip")); 247 248 // find out whether to display "label" (false) or "item" (true) 249 boolean showItem = false; 250 Attribute nameFmt = pane.getAttribute("nameFmt"); 251 if (nameFmt != null && nameFmt.getValue().equals("item")) { 252 log.debug("Pane {} will show items, not labels, from decoder file", name); 253 showItem = true; 254 } 255 // put the columns left to right in a panel 256 JPanel p = new JPanel(); 257 panelList.add(p); 258 p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); 259 260 // handle the xml definition 261 // for all "column" elements ... 262 List<Element> colList = pane.getChildren("column"); 263 for (Element element : colList) { 264 // load each column 265 p.add(newColumn(element, showItem, modelElem)); 266 } 267 // for all "row" elements ... 268 List<Element> rowList = pane.getChildren("row"); 269 for (Element element : rowList) { 270 // load each row 271 p.add(newRow(element, showItem, modelElem)); 272 } 273 // for all "grid" elements ... 274 List<Element> gridList = pane.getChildren("grid"); 275 for (Element element : gridList) { 276 // load each grid 277 p.add(newGrid(element, showItem, modelElem)); 278 } 279 // for all "group" elements ... 280 List<Element> groupList = pane.getChildren("group"); 281 for (Element element : groupList) { 282 // load each group 283 p.add(newGroup(element, showItem, modelElem)); 284 } 285 286 // explain why pane is empty 287 if (cvList.isEmpty() && varList.isEmpty() && isProgPane) { 288 JPanel pe = new JPanel(); 289 pe.setLayout(new BoxLayout(pe, BoxLayout.Y_AXIS)); 290 int line = 1; 291 while (line >= 0) { 292 try { 293 String msg = SymbolicProgBundle.getMessage("TextTabEmptyExplain" + line); 294 if (msg.isEmpty()) { 295 msg = " "; 296 } 297 JLabel l = new JLabel(msg); 298 l.setAlignmentX(Component.CENTER_ALIGNMENT); 299 pe.add(l); 300 line++; 301 } catch (java.util.MissingResourceException e) { // deliberately runs until exception 302 line = -1; 303 } 304 } 305 add(pe); 306 panelList.add(pe); 307 return; 308 } 309 310 // add glue to the right to allow resize - but this isn't working as expected? Alignment? 311 add(Box.createHorizontalGlue()); 312 313 add(new JScrollPane(p)); 314 315 // add buttons in a new panel 316 bottom = new JPanel(); 317 panelList.add(p); 318 bottom.setLayout(new BoxLayout(bottom, BoxLayout.X_AXIS)); 319 320 // enable read buttons, if possible, and 321 // set their tool tips 322 enableReadButtons(); 323 324 // add read button listeners 325 readChangesButton.addItemListener(l1 = (ItemEvent e) -> { 326 if (e.getStateChange() == ItemEvent.SELECTED) { 327 readChangesButton.setText(SymbolicProgBundle.getMessage("ButtonStopReadChangesSheet")); 328 if (!container.isBusy()) { 329 prepReadPane(true); 330 prepGlassPane(readChangesButton); 331 container.getBusyGlassPane().setVisible(true); 332 readPaneChanges(); 333 } 334 } else { 335 stopProgramming(); 336 readChangesButton.setText(SymbolicProgBundle.getMessage("ButtonReadChangesSheet")); 337 if (container.isBusy()) { 338 readChangesButton.setEnabled(false); 339 } 340 } 341 }); 342 readAllButton.addItemListener(l2 = (ItemEvent e) -> { 343 if (e.getStateChange() == ItemEvent.SELECTED) { 344 readAllButton.setText(SymbolicProgBundle.getMessage("ButtonStopReadSheet")); 345 if (!container.isBusy()) { 346 prepReadPane(false); 347 prepGlassPane(readAllButton); 348 container.getBusyGlassPane().setVisible(true); 349 readPaneAll(); 350 } 351 } else { 352 stopProgramming(); 353 readAllButton.setText(SymbolicProgBundle.getMessage("ButtonReadFullSheet")); 354 if (container.isBusy()) { 355 readAllButton.setEnabled(false); 356 } 357 } 358 }); 359 360 writeChangesButton.setToolTipText(SymbolicProgBundle.getMessage("TipWriteHighlightedSheet")); 361 writeChangesButton.addItemListener(l3 = (ItemEvent e) -> { 362 if (e.getStateChange() == ItemEvent.SELECTED) { 363 writeChangesButton.setText(SymbolicProgBundle.getMessage("ButtonStopWriteChangesSheet")); 364 if (!container.isBusy()) { 365 prepWritePane(true); 366 prepGlassPane(writeChangesButton); 367 container.getBusyGlassPane().setVisible(true); 368 writePaneChanges(); 369 } 370 } else { 371 stopProgramming(); 372 writeChangesButton.setText(SymbolicProgBundle.getMessage("ButtonWriteChangesSheet")); 373 if (container.isBusy()) { 374 writeChangesButton.setEnabled(false); 375 } 376 } 377 }); 378 379 writeAllButton.setToolTipText(SymbolicProgBundle.getMessage("TipWriteAllSheet")); 380 writeAllButton.addItemListener(l4 = (ItemEvent e) -> { 381 if (e.getStateChange() == ItemEvent.SELECTED) { 382 writeAllButton.setText(SymbolicProgBundle.getMessage("ButtonStopWriteSheet")); 383 if (!container.isBusy()) { 384 prepWritePane(false); 385 prepGlassPane(writeAllButton); 386 container.getBusyGlassPane().setVisible(true); 387 writePaneAll(); 388 } 389 } else { 390 stopProgramming(); 391 writeAllButton.setText(SymbolicProgBundle.getMessage("ButtonWriteFullSheet")); 392 if (container.isBusy()) { 393 writeAllButton.setEnabled(false); 394 } 395 } 396 }); 397 398 // enable confirm buttons, if possible, and 399 // set their tool tips 400 enableConfirmButtons(); 401 402 // add confirm button listeners 403 confirmChangesButton.addItemListener(l5 = (ItemEvent e) -> { 404 if (e.getStateChange() == ItemEvent.SELECTED) { 405 confirmChangesButton.setText(SymbolicProgBundle.getMessage("ButtonStopConfirmChangesSheet")); 406 if (!container.isBusy()) { 407 prepConfirmPane(true); 408 prepGlassPane(confirmChangesButton); 409 container.getBusyGlassPane().setVisible(true); 410 confirmPaneChanges(); 411 } 412 } else { 413 stopProgramming(); 414 confirmChangesButton.setText(SymbolicProgBundle.getMessage("ButtonConfirmChangesSheet")); 415 if (container.isBusy()) { 416 confirmChangesButton.setEnabled(false); 417 } 418 } 419 }); 420 confirmAllButton.addItemListener(l6 = (ItemEvent e) -> { 421 if (e.getStateChange() == ItemEvent.SELECTED) { 422 confirmAllButton.setText(SymbolicProgBundle.getMessage("ButtonStopConfirmSheet")); 423 if (!container.isBusy()) { 424 prepConfirmPane(false); 425 prepGlassPane(confirmAllButton); 426 container.getBusyGlassPane().setVisible(true); 427 confirmPaneAll(); 428 } 429 } else { 430 stopProgramming(); 431 confirmAllButton.setText(SymbolicProgBundle.getMessage("ButtonConfirmFullSheet")); 432 if (container.isBusy()) { 433 confirmAllButton.setEnabled(false); 434 } 435 } 436 }); 437 438// Only add change buttons to CV tables 439 bottom.add(readChangesButton); 440 bottom.add(writeChangesButton); 441 if (_cvTable) { 442 bottom.add(confirmChangesButton); 443 } 444 bottom.add(readAllButton); 445 bottom.add(writeAllButton); 446 if (_cvTable) { 447 bottom.add(confirmAllButton); 448 } 449 450 // don't show buttons if no programmer at all 451 if (_cvModel.getProgrammer() != null) { 452 add(bottom); 453 } 454 } 455 456 public void setNoDecoder() { 457 readChangesButton.setEnabled(false); 458 readAllButton.setEnabled(false); 459 writeChangesButton.setEnabled(false); 460 writeAllButton.setEnabled(false); 461 confirmChangesButton.setEnabled(false); 462 confirmAllButton.setEnabled(false); 463 } 464 465 @Override 466 public String getName() { 467 return mName; 468 } 469 470 @Override 471 public String toString() { 472 return getName(); 473 } 474 475 /** 476 * Enable the read all and read changes button if possible. This checks to 477 * make sure this is appropriate, given the attached programmer's 478 * capability. 479 */ 480 void enableReadButtons() { 481 readChangesButton.setToolTipText(SymbolicProgBundle.getMessage("TipReadChangesSheet")); 482 readAllButton.setToolTipText(SymbolicProgBundle.getMessage("TipReadAllSheet")); 483 if (_cvModel.getProgrammer() != null 484 && !_cvModel.getProgrammer().getCanRead()) { 485 // can't read, disable the buttons 486 readChangesButton.setEnabled(false); 487 readAllButton.setEnabled(false); 488 // set tooltip to explain why 489 readChangesButton.setToolTipText(SymbolicProgBundle.getMessage("TipNoRead")); 490 readAllButton.setToolTipText(SymbolicProgBundle.getMessage("TipNoRead")); 491 } else { 492 readChangesButton.setEnabled(true); 493 readAllButton.setEnabled(true); 494 } 495 } 496 497 /** 498 * Enable the compare all and compare changes button if possible. This 499 * checks to make sure this is appropriate, given the attached programmer's 500 * capability. 501 */ 502 void enableConfirmButtons() { 503 confirmChangesButton.setToolTipText(SymbolicProgBundle.getMessage("TipConfirmChangesSheet")); 504 confirmAllButton.setToolTipText(SymbolicProgBundle.getMessage("TipConfirmAllSheet")); 505 if (_cvModel.getProgrammer() != null 506 && !_cvModel.getProgrammer().getCanRead()) { 507 // can't read, disable the buttons 508 confirmChangesButton.setEnabled(false); 509 confirmAllButton.setEnabled(false); 510 // set tooltip to explain why 511 confirmChangesButton.setToolTipText(SymbolicProgBundle.getMessage("TipNoRead")); 512 confirmAllButton.setToolTipText(SymbolicProgBundle.getMessage("TipNoRead")); 513 } else { 514 confirmChangesButton.setEnabled(true); 515 confirmAllButton.setEnabled(true); 516 } 517 } 518 519 /** 520 * This remembers the variables on this pane for the Read/Write sheet 521 * operation. They are stored as a list of Integer objects, each of which is 522 * the index of the Variable in the VariableTable. 523 */ 524 List<Integer> varList = new ArrayList<>(); 525 int varListIndex; 526 /** 527 * This remembers the CVs on this pane for the Read/Write sheet operation. 528 * They are stored as a set of Integer objects, each of which is the index 529 * of the CV in the CVTable. Note that variables are handled separately, and 530 * the CVs that are represented by variables are not entered here. So far 531 * (sic), the only use of this is for the cvtable rep. 532 */ 533 protected TreeSet<Integer> cvList = new TreeSet<>(); // TreeSet is iterated in order 534 protected Iterator<Integer> cvListIterator; 535 536 protected JToggleButton readChangesButton = new JToggleButton(SymbolicProgBundle.getMessage("ButtonReadChangesSheet")); 537 protected JToggleButton readAllButton = new JToggleButton(SymbolicProgBundle.getMessage("ButtonReadFullSheet")); 538 protected JToggleButton writeChangesButton = new JToggleButton(SymbolicProgBundle.getMessage("ButtonWriteChangesSheet")); 539 protected JToggleButton writeAllButton = new JToggleButton(SymbolicProgBundle.getMessage("ButtonWriteFullSheet")); 540 JToggleButton confirmChangesButton = new JToggleButton(SymbolicProgBundle.getMessage("ButtonConfirmChangesSheet")); 541 JToggleButton confirmAllButton = new JToggleButton(SymbolicProgBundle.getMessage("ButtonConfirmFullSheet")); 542 543 /** 544 * Estimate the number of CVs that will be accessed when reading or writing 545 * the contents of this pane. 546 * 547 * @param read true if counting for read, false for write 548 * @param changes true if counting for a *Changes operation; false, if 549 * counting for a *All operation 550 * @return the total number of CV reads/writes needed for this pane 551 */ 552 public int countOpsNeeded(boolean read, boolean changes) { 553 Set<Integer> set = new HashSet<>(cvList.size() + varList.size() + 50); 554 return makeOpsNeededSet(read, changes, set).size(); 555 } 556 557 /** 558 * Produce a set of CVs that will be accessed when reading or writing the 559 * contents of this pane. 560 * 561 * @param read true if counting for read, false for write 562 * @param changes true if counting for a *Changes operation; false, if 563 * counting for a *All operation 564 * @param set The set to fill. Any CVs already in here will not be 565 * duplicated, which provides a way to aggregate a set of CVs 566 * across multiple panes. 567 * @return the same set as the parameter, for convenient chaining of 568 * operations. 569 */ 570 public Set<Integer> makeOpsNeededSet(boolean read, boolean changes, Set<Integer> set) { 571 572 // scan the variable list 573 for (int varNum : varList) { 574 575 VariableValue var = _varModel.getVariable(varNum); 576 577 // must decide whether this one should be counted 578 if (!changes || var.isChanged()) { 579 580 CvValue[] cvs = var.usesCVs(); 581 for (CvValue cv : cvs) { 582 // always of interest 583 if (!changes || VariableValue.considerChanged(cv)) { 584 set.add(Integer.valueOf(cv.number())); 585 } 586 } 587 } 588 } 589 590 return set; 591 } 592 593 private void prepGlassPane(AbstractButton activeButton) { 594 container.prepGlassPane(activeButton); 595 } 596 597 void enableButtons(boolean stat) { 598 if (stat) { 599 enableReadButtons(); 600 enableConfirmButtons(); 601 } else { 602 readChangesButton.setEnabled(stat); 603 readAllButton.setEnabled(stat); 604 confirmChangesButton.setEnabled(stat); 605 confirmAllButton.setEnabled(stat); 606 } 607 writeChangesButton.setEnabled(stat); 608 writeAllButton.setEnabled(stat); 609 } 610 611 boolean justChanges; 612 613 /** 614 * Invoked by "Read changes on sheet" button, this sets in motion a 615 * continuing sequence of "read" operations on the variables and 616 * CVs in the Pane. Only variables in states marked as "changed" will be 617 * read. 618 * 619 * @return true is a read has been started, false if the pane is complete. 620 */ 621 public boolean readPaneChanges() { 622 if (log.isDebugEnabled()) { 623 log.debug("readPane starts with {} vars, {} cvs ", varList.size(), cvList.size()); 624 } 625 prepReadPane(true); 626 return nextRead(); 627 } 628 629 /** 630 * Prepare this pane for a read operation. 631 * <p> 632 * The read mechanism only reads variables in certain states (and needs to 633 * do that to handle error processing right now), so this is implemented by 634 * first setting all variables and CVs on this pane to TOREAD via this 635 * method 636 * 637 * @param onlyChanges true if only reading changes; false if reading all 638 */ 639 public void prepReadPane(boolean onlyChanges) { 640 log.debug("start prepReadPane with onlyChanges={}", onlyChanges); 641 justChanges = onlyChanges; 642 643 if (isCvTablePane) { 644 setCvListFromTable(); // make sure list of CVs up to date if table 645 } 646 enableButtons(false); 647 if (justChanges) { 648 readChangesButton.setEnabled(true); 649 readChangesButton.setSelected(true); 650 } else { 651 readAllButton.setSelected(true); 652 readAllButton.setEnabled(true); 653 } 654 if (!container.isBusy()) { 655 container.enableButtons(false); 656 } 657 setToRead(justChanges, true); 658 varListIndex = 0; 659 cvListIterator = cvList.iterator(); 660 } 661 662 /** 663 * Invoked by "Read Full Sheet" button, this sets in motion a continuing 664 * sequence of "read" operations on the variables and CVs in the 665 * Pane. The read mechanism only reads variables in certain states (and 666 * needs to do that to handle error processing right now), so this is 667 * implemented by first setting all variables and CVs on this pane to TOREAD 668 * in prepReadPaneAll, then starting the execution. 669 * 670 * @return true is a read has been started, false if the pane is complete 671 */ 672 public boolean readPaneAll() { 673 if (log.isDebugEnabled()) { 674 log.debug("readAllPane starts with {} vars, {} cvs ", varList.size(), cvList.size()); 675 } 676 prepReadPane(false); 677 // start operation 678 return nextRead(); 679 } 680 681 /** 682 * Set the "ToRead" parameter in all variables and CVs on this pane. 683 * 684 * @param justChanges true if this is read changes, false if read all 685 * @param startProcess true if this is the start of processing, false if 686 * cleaning up at end 687 */ 688 void setToRead(boolean justChanges, boolean startProcess) { 689 if (!container.isBusy() 690 || // the frame has already setToRead 691 (!startProcess)) { // we want to setToRead false if the pane's process is being stopped 692 for (int varNum : varList) { 693 VariableValue var = _varModel.getVariable(varNum); 694 if (justChanges) { 695 if (var.isChanged()) { 696 var.setToRead(startProcess); 697 } else { 698 var.setToRead(false); 699 } 700 } else { 701 var.setToRead(startProcess); 702 } 703 } 704 705 if (isCvTablePane) { 706 setCvListFromTable(); // make sure list of CVs up to date if table 707 } 708 for (int cvNum : cvList) { 709 CvValue cv = _cvModel.getCvByRow(cvNum); 710 if (justChanges) { 711 if (VariableValue.considerChanged(cv)) { 712 cv.setToRead(startProcess); 713 } else { 714 cv.setToRead(false); 715 } 716 } else { 717 cv.setToRead(startProcess); 718 } 719 } 720 } 721 } 722 723 /** 724 * Set the "ToWrite" parameter in all variables and CVs on this pane 725 * 726 * @param justChanges true if this is read changes, false if read all 727 * @param startProcess true if this is the start of processing, false if 728 * cleaning up at end 729 */ 730 void setToWrite(boolean justChanges, boolean startProcess) { 731 log.debug("start setToWrite method with {},{}", justChanges, startProcess); 732 if (!container.isBusy() 733 || // the frame has already setToWrite 734 (!startProcess)) { // we want to setToWrite false if the pane's process is being stopped 735 log.debug("about to start setToWrite of varList"); 736 for (int varNum : varList) { 737 VariableValue var = _varModel.getVariable(varNum); 738 if (justChanges) { 739 if (var.isChanged()) { 740 var.setToWrite(startProcess); 741 } else { 742 var.setToWrite(false); 743 } 744 } else { 745 var.setToWrite(startProcess); 746 } 747 } 748 749 log.debug("about to start setToWrite of cvList"); 750 if (isCvTablePane) { 751 setCvListFromTable(); // make sure list of CVs up to date if table 752 } 753 for (int cvNum : cvList) { 754 CvValue cv = _cvModel.getCvByRow(cvNum); 755 if (justChanges) { 756 if (VariableValue.considerChanged(cv)) { 757 cv.setToWrite(startProcess); 758 } else { 759 cv.setToWrite(false); 760 } 761 } else { 762 cv.setToWrite(startProcess); 763 } 764 } 765 } 766 log.debug("end setToWrite method"); 767 } 768 769 void executeRead(VariableValue var) { 770 setBusy(true); 771 // var.setToRead(false); // variables set this themselves 772 if (_programmingVar != null) { 773 log.error("listener already set at read start"); 774 } 775 _programmingVar = var; 776 _read = true; 777 // get notified when that state changes so can repeat 778 _programmingVar.addPropertyChangeListener(this); 779 // and make the read request 780 if (justChanges) { 781 _programmingVar.readChanges(); 782 } else { 783 _programmingVar.readAll(); 784 } 785 } 786 787 void executeWrite(VariableValue var) { 788 setBusy(true); 789 // var.setToWrite(false); // variables reset themselves when done 790 if (_programmingVar != null) { 791 log.error("listener already set at write start"); 792 } 793 _programmingVar = var; 794 _read = false; 795 // get notified when that state changes so can repeat 796 _programmingVar.addPropertyChangeListener(this); 797 // and make the write request 798 if (justChanges) { 799 _programmingVar.writeChanges(); 800 } else { 801 _programmingVar.writeAll(); 802 } 803 } 804 805 /** 806 * If there are any more read operations to be done on this pane, do the 807 * next one. 808 * <p> 809 * Each invocation of this method reads one variable or CV; completion of 810 * that request will cause it to happen again, reading the next one, until 811 * there's nothing left to read. 812 * @return true is a read has been started, false if the pane is complete. 813 */ 814 boolean nextRead() { 815 // look for possible variables 816 if (log.isDebugEnabled()) { 817 log.debug("nextRead scans {} variables", varList.size()); 818 } 819 while ((varList.size() > 0) && (varListIndex < varList.size())) { 820 int varNum = varList.get(varListIndex); 821 AbstractValue.ValueState vState = _varModel.getState(varNum); 822 VariableValue var = _varModel.getVariable(varNum); 823 if (log.isDebugEnabled()) { 824 log.debug("nextRead var index {} state {} isToRead: {} label: {}", varNum, vState.getName(), var.isToRead(), var.label()); 825 } 826 varListIndex++; 827 if (var.isToRead()) { 828 if (log.isDebugEnabled()) { 829 log.debug("start read of variable {}", _varModel.getLabel(varNum)); 830 } 831 executeRead(var); 832 833 log.debug("return from starting var read"); 834 // the request may have instantaneously been satisfied... 835 return true; // only make one request at a time! 836 } 837 } 838 // found no variables needing read, try CVs 839 if (log.isDebugEnabled()) { 840 log.debug("nextRead scans {} CVs", cvList.size()); 841 } 842 while (cvListIterator != null && cvListIterator.hasNext()) { 843 int cvNum = cvListIterator.next(); 844 CvValue cv = _cvModel.getCvByRow(cvNum); 845 if (log.isDebugEnabled()) { 846 log.debug("nextRead cv index {} state {}", cvNum, cv.getState()); 847 } 848 849 if (cv.isToRead()) { // always read UNKNOWN state 850 log.debug("start read of cv {}", cvNum); 851 setBusy(true); 852 if (_programmingCV != null) { 853 log.error("listener already set at read start"); 854 } 855 _programmingCV = _cvModel.getCvByRow(cvNum); 856 _read = true; 857 // get notified when that state changes so can repeat 858 _programmingCV.addPropertyChangeListener(this); 859 // and make the read request 860 // _programmingCV.setToRead(false); // CVs set this themselves 861 _programmingCV.read(_cvModel.getStatusLabel()); 862 log.debug("return from starting CV read"); 863 // the request may have instantateously been satisfied... 864 return true; // only make one request at a time! 865 } 866 } 867 // nothing to program, end politely 868 log.debug("nextRead found nothing to do"); 869 readChangesButton.setSelected(false); 870 readAllButton.setSelected(false); // reset both, as that's final state we want 871 setBusy(false); 872 container.paneFinished(); 873 return false; 874 } 875 876 /** 877 * If there are any more compare operations to be done on this pane, do the 878 * next one. 879 * <p> 880 * Each invocation of this method compares one CV; completion of that request 881 * will cause it to happen again, reading the next one, until there's 882 * nothing left to read. 883 * 884 * @return true is a compare has been started, false if the pane is 885 * complete. 886 */ 887 boolean nextConfirm() { 888 // look for possible CVs 889 while (cvListIterator != null && cvListIterator.hasNext()) { 890 int cvNum = cvListIterator.next(); 891 CvValue cv = _cvModel.getCvByRow(cvNum); 892 if (log.isDebugEnabled()) { 893 log.debug("nextConfirm cv index {} state {}", cvNum, cv.getState()); 894 } 895 896 if (cv.isToRead()) { 897 log.debug("start confirm of cv {}", cvNum); 898 setBusy(true); 899 if (_programmingCV != null) { 900 log.error("listener already set at confirm start"); 901 } 902 _programmingCV = _cvModel.getCvByRow(cvNum); 903 _read = true; 904 // get notified when that state changes so can repeat 905 _programmingCV.addPropertyChangeListener(this); 906 // and make the compare request 907 _programmingCV.confirm(_cvModel.getStatusLabel()); 908 log.debug("return from starting CV confirm"); 909 // the request may have instantateously been satisfied... 910 return true; // only make one request at a time! 911 } 912 } 913 // nothing to program, end politely 914 log.debug("nextConfirm found nothing to do"); 915 confirmChangesButton.setSelected(false); 916 confirmAllButton.setSelected(false); // reset both, as that's final state we want 917 setBusy(false); 918 container.paneFinished(); 919 return false; 920 } 921 922 /** 923 * Invoked by "Write changes on sheet" button, this sets in motion a 924 * continuing sequence of "write" operations on the variables in the Pane. 925 * Only variables in isChanged states are written; other states don't need 926 * to be. 927 * 928 * @return true if a write has been started, false if the pane is complete 929 */ 930 public boolean writePaneChanges() { 931 log.debug("writePaneChanges starts"); 932 prepWritePane(true); 933 boolean val = nextWrite(); 934 log.debug("writePaneChanges returns {}", val); 935 return val; 936 } 937 938 /** 939 * Invoked by "Write full sheet" button to write all CVs. 940 * 941 * @return true if a write has been started, false if the pane is complete 942 */ 943 public boolean writePaneAll() { 944 prepWritePane(false); 945 return nextWrite(); 946 } 947 948 /** 949 * Prepare a "write full sheet" operation. 950 * 951 * @param onlyChanges true if only writing changes; false if writing all 952 */ 953 public void prepWritePane(boolean onlyChanges) { 954 log.debug("start prepWritePane with {}", onlyChanges); 955 justChanges = onlyChanges; 956 enableButtons(false); 957 958 if (isCvTablePane) { 959 setCvListFromTable(); // make sure list of CVs up to date if table 960 } 961 if (justChanges) { 962 writeChangesButton.setEnabled(true); 963 writeChangesButton.setSelected(true); 964 } else { 965 writeAllButton.setSelected(true); 966 writeAllButton.setEnabled(true); 967 } 968 if (!container.isBusy()) { 969 container.enableButtons(false); 970 } 971 setToWrite(justChanges, true); 972 varListIndex = 0; 973 974 cvListIterator = cvList.iterator(); 975 log.debug("end prepWritePane"); 976 } 977 978 boolean nextWrite() { 979 log.debug("start nextWrite"); 980 // look for possible variables 981 while ((varList.size() > 0) && (varListIndex < varList.size())) { 982 int varNum = varList.get(varListIndex); 983 AbstractValue.ValueState vState = _varModel.getState(varNum); 984 VariableValue var = _varModel.getVariable(varNum); 985 if (log.isDebugEnabled()) { 986 log.debug("nextWrite var index {} state {} isToWrite: {} label:{}", varNum, vState.getName(), var.isToWrite(), var.label()); 987 } 988 varListIndex++; 989 if (var.isToWrite()) { 990 log.debug("start write of variable {}", _varModel.getLabel(varNum)); 991 992 executeWrite(var); 993 994 log.debug("return from starting var write"); 995 return true; // only make one request at a time! 996 } 997 } 998 // check for CVs to handle (e.g. for CV table) 999 while (cvListIterator != null && cvListIterator.hasNext()) { 1000 int cvNum = cvListIterator.next(); 1001 CvValue cv = _cvModel.getCvByRow(cvNum); 1002 if (log.isDebugEnabled()) { 1003 log.debug("nextWrite cv index {} state {}", cvNum, cv.getState()); 1004 } 1005 1006 if (cv.isToWrite()) { 1007 log.debug("start write of cv index {}", cvNum); 1008 setBusy(true); 1009 if (_programmingCV != null) { 1010 log.error("listener already set at write start"); 1011 } 1012 _programmingCV = _cvModel.getCvByRow(cvNum); 1013 _read = false; 1014 // get notified when that state changes so can repeat 1015 _programmingCV.addPropertyChangeListener(this); 1016 // and make the write request 1017 // _programmingCV.setToWrite(false); // CVs set this themselves 1018 _programmingCV.write(_cvModel.getStatusLabel()); 1019 log.debug("return from starting cv write"); 1020 return true; // only make one request at a time! 1021 } 1022 } 1023 // nothing to program, end politely 1024 log.debug("nextWrite found nothing to do"); 1025 writeChangesButton.setSelected(false); 1026 writeAllButton.setSelected(false); 1027 setBusy(false); 1028 container.paneFinished(); 1029 log.debug("return from nextWrite with nothing to do"); 1030 return false; 1031 } 1032 1033 /** 1034 * Prepare this pane for a compare operation. 1035 * <p> 1036 * The read mechanism only reads variables in certain states (and needs to 1037 * do that to handle error processing right now), so this is implemented by 1038 * first setting all variables and CVs on this pane to TOREAD via this 1039 * method 1040 * 1041 * @param onlyChanges true if only confirming changes; false if confirming 1042 * all 1043 */ 1044 public void prepConfirmPane(boolean onlyChanges) { 1045 log.debug("start prepReadPane with onlyChanges={}", onlyChanges); 1046 justChanges = onlyChanges; 1047 enableButtons(false); 1048 1049 if (isCvTablePane) { 1050 setCvListFromTable(); // make sure list of CVs up to date if table 1051 } 1052 if (justChanges) { 1053 confirmChangesButton.setEnabled(true); 1054 confirmChangesButton.setSelected(true); 1055 } else { 1056 confirmAllButton.setSelected(true); 1057 confirmAllButton.setEnabled(true); 1058 } 1059 if (!container.isBusy()) { 1060 container.enableButtons(false); 1061 } 1062 // we can use the read prep since confirm has to read first 1063 setToRead(justChanges, true); 1064 varListIndex = 0; 1065 1066 cvListIterator = cvList.iterator(); 1067 } 1068 1069 /** 1070 * Invoked by "Compare changes on sheet" button, this sets in motion a 1071 * continuing sequence of "confirm" operations on the variables and 1072 * CVs in the Pane. Only variables in states marked as "changed" will be 1073 * checked. 1074 * 1075 * @return true is a confirm has been started, false if the pane is 1076 * complete. 1077 */ 1078 public boolean confirmPaneChanges() { 1079 if (log.isDebugEnabled()) { 1080 log.debug("confirmPane starts with {} vars, {} cvs ", varList.size(), cvList.size()); 1081 } 1082 prepConfirmPane(true); 1083 return nextConfirm(); 1084 } 1085 1086 /** 1087 * Invoked by "Compare Full Sheet" button, this sets in motion a continuing 1088 * sequence of "confirm" operations on the variables and CVs in the 1089 * Pane. The read mechanism only reads variables in certain states (and 1090 * needs to do that to handle error processing right now), so this is 1091 * implemented by first setting all variables and CVs on this pane to TOREAD 1092 * in prepReadPaneAll, then starting the execution. 1093 * 1094 * @return true is a confirm has been started, false if the pane is 1095 * complete. 1096 */ 1097 public boolean confirmPaneAll() { 1098 if (log.isDebugEnabled()) { 1099 log.debug("confirmAllPane starts with {} vars, {} cvs ", varList.size(), cvList.size()); 1100 } 1101 prepConfirmPane(false); 1102 // start operation 1103 return nextConfirm(); 1104 } 1105 1106 // reference to variable being programmed (or null if none) 1107 VariableValue _programmingVar = null; 1108 CvValue _programmingCV = null; 1109 boolean _read = true; 1110 1111 // busy during read, write operations 1112 private boolean _busy = false; 1113 1114 public boolean isBusy() { 1115 return _busy; 1116 } 1117 1118 protected void setBusy(boolean busy) { 1119 boolean oldBusy = _busy; 1120 _busy = busy; 1121 if (!busy && !container.isBusy()) { 1122 enableButtons(true); 1123 } 1124 if (oldBusy != busy) { 1125 firePropertyChange("Busy", Boolean.valueOf(oldBusy), Boolean.valueOf(busy)); 1126 } 1127 } 1128 1129 private int retry = 0; 1130 1131 /** 1132 * Get notification of a variable property change, specifically "busy" going 1133 * to false at the end of a programming operation. If we're in a programming 1134 * operation, we then continue it by reinvoking the nextRead/writePane 1135 * operation. 1136 * 1137 * @param e the event to respond to 1138 */ 1139 @Override 1140 public void propertyChange(java.beans.PropertyChangeEvent e) { 1141 // check for the right event & condition 1142 if (_programmingVar == null && _programmingCV == null ) { 1143 log.warn("unexpected propertChange: {}", e); 1144 return; 1145 } else if (log.isDebugEnabled()) { 1146 log.debug("property changed: {} new value: {}", e.getPropertyName(), e.getNewValue()); 1147 } 1148 1149 // find the right way to handle this 1150 if (e.getSource() == _programmingVar 1151 && e.getPropertyName().equals("Busy") 1152 && e.getNewValue().equals(Boolean.FALSE)) { 1153 if (_programmingVar.getState() == AbstractValue.ValueState.UNKNOWN) { 1154 if (retry == 0) { 1155 varListIndex--; 1156 retry++; 1157 if (_read) { 1158 _programmingVar.setToRead(true); // set the variable 1159 // to read again. 1160 } else { 1161 _programmingVar.setToWrite(true); // set the variable 1162 // to attempt another 1163 // write. 1164 } 1165 } else { 1166 retry = 0; 1167 } 1168 } 1169 replyWhileProgrammingVar(); 1170 } else if (e.getSource() == _programmingCV 1171 && e.getPropertyName().equals("Busy") 1172 && e.getNewValue().equals(Boolean.FALSE)) { 1173 1174 // there's no -- operator on the HashSet Iterator we're 1175 // using for the CV list, so we don't do individual retries 1176 // now. 1177 //if (_programmingCV.getState() == CvValue.UNKNOWN) { 1178 // if (retry == 0) { 1179 // cvListIndex--; 1180 // retry++; 1181 // } else { 1182 // retry = 0; 1183 // } 1184 //} 1185 replyWhileProgrammingCV(); 1186 } else { 1187 if (log.isDebugEnabled() && e.getPropertyName().equals("Busy")) { 1188 log.debug("ignoring change of Busy {} {}", e.getNewValue(), e.getNewValue().equals(Boolean.FALSE)); 1189 } 1190 } 1191 } 1192 1193 public void replyWhileProgrammingVar() { 1194 log.debug("correct event for programming variable, restart operation"); 1195 // remove existing listener 1196 _programmingVar.removePropertyChangeListener(this); 1197 _programmingVar = null; 1198 // restart the operation 1199 restartProgramming(); 1200 } 1201 1202 public void replyWhileProgrammingCV() { 1203 log.debug("correct event for programming CV, restart operation"); 1204 // remove existing listener 1205 _programmingCV.removePropertyChangeListener(this); 1206 _programmingCV = null; 1207 // restart the operation 1208 restartProgramming(); 1209 } 1210 1211 void restartProgramming() { 1212 log.debug("start restartProgramming"); 1213 if (_read && readChangesButton.isSelected()) { 1214 nextRead(); 1215 } else if (_read && readAllButton.isSelected()) { 1216 nextRead(); 1217 } else if (_read && confirmChangesButton.isSelected()) { 1218 nextConfirm(); 1219 } else if (_read && confirmAllButton.isSelected()) { 1220 nextConfirm(); 1221 } else if (writeChangesButton.isSelected()) { 1222 nextWrite(); // was writePaneChanges 1223 } else if (writeAllButton.isSelected()) { 1224 nextWrite(); 1225 } else { 1226 log.debug("No operation to restart"); 1227 if (isBusy()) { 1228 container.paneFinished(); 1229 setBusy(false); 1230 } 1231 } 1232 log.debug("end restartProgramming"); 1233 } 1234 1235 protected void stopProgramming() { 1236 log.debug("start stopProgramming"); 1237 setToRead(false, false); 1238 setToWrite(false, false); 1239 varListIndex = varList.size(); 1240 1241 cvListIterator = null; 1242 log.debug("end stopProgramming"); 1243 } 1244 1245 /** 1246 * Create a new group from the JDOM group Element 1247 * 1248 * @param element element containing group contents 1249 * @param showStdName show the name following the rules for the 1250 * <em>nameFmt</em> element 1251 * @param modelElem element containing the decoder model 1252 * @return a panel containing the group 1253 */ 1254 protected JPanel newGroup(Element element, boolean showStdName, Element modelElem) { 1255 1256 // create a panel to add as a new column or row 1257 final JPanel c = new JPanel(); 1258 panelList.add(c); 1259 GridBagLayout g = new GridBagLayout(); 1260 GridBagConstraints cs = new GridBagConstraints(); 1261 c.setLayout(g); 1262 1263 // handle include/exclude 1264 if (!PaneProgFrame.isIncludedFE(element, modelElem, rosterEntry, "", "")) { 1265 return c; 1266 } 1267 1268 // handle the xml definition 1269 // for all elements in the column or row 1270 List<Element> elemList = element.getChildren(); 1271 log.trace("newColumn starting with {} elements", elemList.size()); 1272 for (Element e : elemList) { 1273 1274 String name = e.getName(); 1275 log.trace("newGroup processing {} element", name); 1276 // decode the type 1277 if (name.equals("display")) { // its a variable 1278 // load the variable 1279 newVariable(e, c, g, cs, showStdName); 1280 } else if (name.equals("separator")) { // its a separator 1281 JSeparator j = new JSeparator(SwingConstants.HORIZONTAL); 1282 cs.fill = GridBagConstraints.BOTH; 1283 cs.gridwidth = GridBagConstraints.REMAINDER; 1284 g.setConstraints(j, cs); 1285 c.add(j); 1286 cs.gridwidth = 1; 1287 } else if (name.equals("label")) { 1288 cs.gridwidth = GridBagConstraints.REMAINDER; 1289 makeLabel(e, c, g, cs); 1290 } else if (name.equals("soundlabel")) { 1291 cs.gridwidth = GridBagConstraints.REMAINDER; 1292 makeSoundLabel(e, c, g, cs); 1293 } else if (name.equals("cvtable")) { 1294 makeCvTable(cs, g, c); 1295 } else if (name.equals("fnmapping")) { 1296 pickFnMapPanel(c, g, cs, modelElem); 1297 } else if (name.equals("dccaddress")) { 1298 JPanel l = addDccAddressPanel(e); 1299 if (l.getComponentCount() > 0) { 1300 cs.gridwidth = GridBagConstraints.REMAINDER; 1301 g.setConstraints(l, cs); 1302 c.add(l); 1303 cs.gridwidth = 1; 1304 } 1305 } else if (name.equals("column")) { 1306 // nested "column" elements ... 1307 cs.gridheight = GridBagConstraints.REMAINDER; 1308 JPanel l = newColumn(e, showStdName, modelElem); 1309 if (l.getComponentCount() > 0) { 1310 panelList.add(l); 1311 g.setConstraints(l, cs); 1312 c.add(l); 1313 cs.gridheight = 1; 1314 } 1315 } else if (name.equals("row")) { 1316 // nested "row" elements ... 1317 cs.gridwidth = GridBagConstraints.REMAINDER; 1318 JPanel l = newRow(e, showStdName, modelElem); 1319 if (l.getComponentCount() > 0) { 1320 panelList.add(l); 1321 g.setConstraints(l, cs); 1322 c.add(l); 1323 cs.gridwidth = 1; 1324 } 1325 } else if (name.equals("grid")) { 1326 // nested "grid" elements ... 1327 cs.gridwidth = GridBagConstraints.REMAINDER; 1328 JPanel l = newGrid(e, showStdName, modelElem); 1329 if (l.getComponentCount() > 0) { 1330 panelList.add(l); 1331 g.setConstraints(l, cs); 1332 c.add(l); 1333 cs.gridwidth = 1; 1334 } 1335 } else if (name.equals("group")) { 1336 // nested "group" elements ... 1337 JPanel l = newGroup(e, showStdName, modelElem); 1338 if (l.getComponentCount() > 0) { 1339 panelList.add(l); 1340 g.setConstraints(l, cs); 1341 c.add(l); 1342 } 1343 } else if (!name.equals("qualifier")) { // its a mistake 1344 log.error("No code to handle element of type {} in newColumn", e.getName()); 1345 } 1346 } 1347 // add glue to the bottom to allow resize 1348 if (c.getComponentCount() > 0) { 1349 c.add(Box.createVerticalGlue()); 1350 } 1351 1352 // handle qualification if any 1353 QualifierAdder qa = new QualifierAdder() { 1354 @Override 1355 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 1356 return new JComponentQualifier(c, var, Integer.parseInt(value), relation); 1357 } 1358 1359 @Override 1360 protected void addListener(java.beans.PropertyChangeListener qc) { 1361 c.addPropertyChangeListener(qc); 1362 } 1363 }; 1364 1365 qa.processModifierElements(element, _varModel); 1366 return c; 1367 } 1368 1369 /** 1370 * Create a new grid group from the JDOM group Element. 1371 * 1372 * @param element element containing group contents 1373 * @param c the panel to create the grid in 1374 * @param g the layout manager for the panel 1375 * @param globs properties to configure g 1376 * @param showStdName show the name following the rules for the 1377 * <em>nameFmt</em> element 1378 * @param modelElem element containing the decoder model 1379 */ 1380 protected void newGridGroup(Element element, final JPanel c, GridBagLayout g, GridGlobals globs, boolean showStdName, Element modelElem) { 1381 1382 // handle include/exclude 1383 if (!PaneProgFrame.isIncludedFE(element, modelElem, rosterEntry, "", "")) { 1384 return; 1385 } 1386 1387 // handle the xml definition 1388 // for all elements in the column or row 1389 List<Element> elemList = element.getChildren(); 1390 log.trace("newColumn starting with {} elements", elemList.size()); 1391 for (Element e : elemList) { 1392 1393 String name = e.getName(); 1394 log.trace("newGroup processing {} element", name); 1395 // decode the type 1396 if (name.equals("griditem")) { 1397 final JPanel l = newGridItem(e, showStdName, modelElem, globs); 1398 if (l.getComponentCount() > 0) { 1399 panelList.add(l); 1400 g.setConstraints(l, globs.gridConstraints); 1401 c.add(l); 1402 // globs.gridConstraints.gridwidth = 1; 1403 // handle qualification if any 1404 QualifierAdder qa = new QualifierAdder() { 1405 @Override 1406 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 1407 return new JComponentQualifier(l, var, Integer.parseInt(value), relation); 1408 } 1409 1410 @Override 1411 protected void addListener(java.beans.PropertyChangeListener qc) { 1412 l.addPropertyChangeListener(qc); 1413 } 1414 }; 1415 1416 qa.processModifierElements(e, _varModel); 1417 } 1418 } else if (name.equals("group")) { 1419 // nested "group" elements ... 1420 newGridGroup(e, c, g, globs, showStdName, modelElem); 1421 } else if (!name.equals("qualifier")) { // its a mistake 1422 log.error("No code to handle element of type {} in newColumn", e.getName()); 1423 } 1424 } 1425 // add glue to the bottom to allow resize 1426// if (c.getComponentCount() > 0) { 1427// c.add(Box.createVerticalGlue()); 1428// } 1429 1430 } 1431 1432 /** 1433 * Create a single column from the JDOM column Element. 1434 * 1435 * @param element element containing column contents 1436 * @param showStdName show the name following the rules for the 1437 * <em>nameFmt</em> element 1438 * @param modelElem element containing the decoder model 1439 * @return a panel containing the group 1440 */ 1441 public JPanel newColumn(Element element, boolean showStdName, Element modelElem) { 1442 1443 // create a panel to add as a new column or row 1444 final JPanel c = new JPanel(); 1445 panelList.add(c); 1446 GridBagLayout g = new GridBagLayout(); 1447 GridBagConstraints cs = new GridBagConstraints(); 1448 c.setLayout(g); 1449 1450 // handle the xml definition 1451 // for all elements in the column or row 1452 List<Element> elemList = element.getChildren(); 1453 log.trace("newColumn starting with {} elements", elemList.size()); 1454 for (Element value : elemList) { 1455 1456 // update the grid position 1457 cs.gridy++; 1458 cs.gridx = 0; 1459 1460 String name = value.getName(); 1461 log.trace("newColumn processing {} element", name); 1462 // decode the type 1463 if (name.equals("display")) { // it's a variable 1464 // load the variable 1465 newVariable(value, c, g, cs, showStdName); 1466 } else if (name.equals("separator")) { // its a separator 1467 JSeparator j = new JSeparator(SwingConstants.HORIZONTAL); 1468 cs.fill = GridBagConstraints.BOTH; 1469 cs.gridwidth = GridBagConstraints.REMAINDER; 1470 g.setConstraints(j, cs); 1471 c.add(j); 1472 cs.gridwidth = 1; 1473 } else if (name.equals("label")) { 1474 cs.gridwidth = GridBagConstraints.REMAINDER; 1475 makeLabel(value, c, g, cs); 1476 } else if (name.equals("soundlabel")) { 1477 cs.gridwidth = GridBagConstraints.REMAINDER; 1478 makeSoundLabel(value, c, g, cs); 1479 } else if (name.equals("cvtable")) { 1480 makeCvTable(cs, g, c); 1481 } else if (name.equals("fnmapping")) { 1482 pickFnMapPanel(c, g, cs, modelElem); 1483 } else if (name.equals("dccaddress")) { 1484 JPanel l = addDccAddressPanel(value); 1485 if (l.getComponentCount() > 0) { 1486 cs.gridwidth = GridBagConstraints.REMAINDER; 1487 g.setConstraints(l, cs); 1488 c.add(l); 1489 cs.gridwidth = 1; 1490 } 1491 } else if (name.equals("column")) { 1492 // nested "column" elements ... 1493 cs.gridheight = GridBagConstraints.REMAINDER; 1494 JPanel l = newColumn(value, showStdName, modelElem); 1495 if (l.getComponentCount() > 0) { 1496 panelList.add(l); 1497 g.setConstraints(l, cs); 1498 c.add(l); 1499 cs.gridheight = 1; 1500 } 1501 } else if (name.equals("row")) { 1502 // nested "row" elements ... 1503 cs.gridwidth = GridBagConstraints.REMAINDER; 1504 JPanel l = newRow(value, showStdName, modelElem); 1505 if (l.getComponentCount() > 0) { 1506 panelList.add(l); 1507 g.setConstraints(l, cs); 1508 c.add(l); 1509 cs.gridwidth = 1; 1510 } 1511 } else if (name.equals("grid")) { 1512 // nested "grid" elements ... 1513 cs.gridwidth = GridBagConstraints.REMAINDER; 1514 JPanel l = newGrid(value, showStdName, modelElem); 1515 if (l.getComponentCount() > 0) { 1516 panelList.add(l); 1517 g.setConstraints(l, cs); 1518 c.add(l); 1519 cs.gridwidth = 1; 1520 } 1521 } else if (name.equals("group")) { 1522 // nested "group" elements ... 1523 JPanel l = newGroup(value, showStdName, modelElem); 1524 if (l.getComponentCount() > 0) { 1525 panelList.add(l); 1526 g.setConstraints(l, cs); 1527 c.add(l); 1528 } 1529 } else if (!name.equals("qualifier")) { // its a mistake 1530 log.error("No code to handle element of type {} in newColumn", value.getName()); 1531 } 1532 } 1533 // add glue to the bottom to allow resize 1534 if (c.getComponentCount() > 0) { 1535 c.add(Box.createVerticalGlue()); 1536 } 1537 1538 // handle qualification if any 1539 QualifierAdder qa = new QualifierAdder() { 1540 @Override 1541 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 1542 return new JComponentQualifier(c, var, Integer.parseInt(value), relation); 1543 } 1544 1545 @Override 1546 protected void addListener(java.beans.PropertyChangeListener qc) { 1547 c.addPropertyChangeListener(qc); 1548 } 1549 }; 1550 1551 qa.processModifierElements(element, _varModel); 1552 return c; 1553 } 1554 1555 /** 1556 * Create a single row from the JDOM column Element 1557 * 1558 * @param element element containing row contents 1559 * @param showStdName show the name following the rules for the 1560 * <em>nameFmt</em> element 1561 * @param modelElem element containing the decoder model 1562 * @return a panel containing the group 1563 */ 1564 public JPanel newRow(Element element, boolean showStdName, Element modelElem) { 1565 1566 // create a panel to add as a new column or row 1567 final JPanel c = new JPanel(); 1568 panelList.add(c); 1569 GridBagLayout g = new GridBagLayout(); 1570 GridBagConstraints cs = new GridBagConstraints(); 1571 c.setLayout(g); 1572 1573 // handle the xml definition 1574 // for all elements in the column or row 1575 List<Element> elemList = element.getChildren(); 1576 log.trace("newRow starting with {} elements", elemList.size()); 1577 for (Element value : elemList) { 1578 1579 // update the grid position 1580 cs.gridy = 0; 1581 cs.gridx++; 1582 1583 String name = value.getName(); 1584 log.trace("newRow processing {} element", name); 1585 // decode the type 1586 if (name.equals("display")) { // its a variable 1587 // load the variable 1588 newVariable(value, c, g, cs, showStdName); 1589 } else if (name.equals("separator")) { // its a separator 1590 JSeparator j = new JSeparator(SwingConstants.VERTICAL); 1591 cs.fill = GridBagConstraints.BOTH; 1592 cs.gridheight = GridBagConstraints.REMAINDER; 1593 g.setConstraints(j, cs); 1594 c.add(j); 1595 cs.fill = GridBagConstraints.NONE; 1596 cs.gridheight = 1; 1597 } else if (name.equals("label")) { 1598 cs.gridheight = GridBagConstraints.REMAINDER; 1599 makeLabel(value, c, g, cs); 1600 } else if (name.equals("soundlabel")) { 1601 cs.gridheight = GridBagConstraints.REMAINDER; 1602 makeSoundLabel(value, c, g, cs); 1603 } else if (name.equals("cvtable")) { 1604 makeCvTable(cs, g, c); 1605 } else if (name.equals("fnmapping")) { 1606 pickFnMapPanel(c, g, cs, modelElem); 1607 } else if (name.equals("dccaddress")) { 1608 JPanel l = addDccAddressPanel(value); 1609 if (l.getComponentCount() > 0) { 1610 cs.gridheight = GridBagConstraints.REMAINDER; 1611 g.setConstraints(l, cs); 1612 c.add(l); 1613 cs.gridheight = 1; 1614 } 1615 } else if (name.equals("column")) { 1616 // nested "column" elements ... 1617 cs.gridheight = GridBagConstraints.REMAINDER; 1618 JPanel l = newColumn(value, showStdName, modelElem); 1619 if (l.getComponentCount() > 0) { 1620 panelList.add(l); 1621 g.setConstraints(l, cs); 1622 c.add(l); 1623 cs.gridheight = 1; 1624 } 1625 } else if (name.equals("row")) { 1626 // nested "row" elements ... 1627 cs.gridwidth = GridBagConstraints.REMAINDER; 1628 JPanel l = newRow(value, showStdName, modelElem); 1629 if (l.getComponentCount() > 0) { 1630 panelList.add(l); 1631 g.setConstraints(l, cs); 1632 c.add(l); 1633 cs.gridwidth = 1; 1634 } 1635 } else if (name.equals("grid")) { 1636 // nested "grid" elements ... 1637 cs.gridwidth = GridBagConstraints.REMAINDER; 1638 JPanel l = newGrid(value, showStdName, modelElem); 1639 if (l.getComponentCount() > 0) { 1640 panelList.add(l); 1641 g.setConstraints(l, cs); 1642 c.add(l); 1643 cs.gridwidth = 1; 1644 } 1645 } else if (name.equals("group")) { 1646 // nested "group" elements ... 1647 JPanel l = newGroup(value, showStdName, modelElem); 1648 if (l.getComponentCount() > 0) { 1649 panelList.add(l); 1650 g.setConstraints(l, cs); 1651 c.add(l); 1652 } 1653 } else if (!name.equals("qualifier")) { // its a mistake 1654 log.error("No code to handle element of type {} in newRow", value.getName()); 1655 } 1656 } 1657 // add glue to the bottom to allow resize 1658 if (c.getComponentCount() > 0) { 1659 c.add(Box.createVerticalGlue()); 1660 } 1661 1662 // handle qualification if any 1663 QualifierAdder qa = new QualifierAdder() { 1664 @Override 1665 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 1666 return new JComponentQualifier(c, var, Integer.parseInt(value), relation); 1667 } 1668 1669 @Override 1670 protected void addListener(java.beans.PropertyChangeListener qc) { 1671 c.addPropertyChangeListener(qc); 1672 } 1673 }; 1674 1675 qa.processModifierElements(element, _varModel); 1676 return c; 1677 } 1678 1679 /** 1680 * Create a grid from the JDOM Element. 1681 * 1682 * @param element element containing group contents 1683 * @param showStdName show the name following the rules for the 1684 * <em>nameFmt</em> element 1685 * @param modelElem element containing the decoder model 1686 * @return a panel containing the group 1687 */ 1688 public JPanel newGrid(Element element, boolean showStdName, Element modelElem) { 1689 1690 // create a panel to add as a new grid 1691 final JPanel c = new JPanel(); 1692 panelList.add(c); 1693 GridBagLayout g = new GridBagLayout(); 1694 c.setLayout(g); 1695 1696 GridGlobals globs = new GridGlobals(); 1697 1698 // handle the xml definition 1699 // for all elements in the grid 1700 List<Element> elemList = element.getChildren(); 1701 globs.gridAttList = element.getAttributes(); // get grid-level attributes 1702 log.trace("newGrid starting with {} elements", elemList.size()); 1703 for (Element value : elemList) { 1704 globs.gridConstraints = new GridBagConstraints(); 1705 String name = value.getName(); 1706 log.trace("newGrid processing {} element", name); 1707 // decode the type 1708 if (name.equals("griditem")) { 1709 JPanel l = newGridItem(value, showStdName, modelElem, globs); 1710 if (l.getComponentCount() > 0) { 1711 panelList.add(l); 1712 g.setConstraints(l, globs.gridConstraints); 1713 c.add(l); 1714 // globs.gridConstraints.gridwidth = 1; 1715 } 1716 } else if (name.equals("group")) { 1717 // nested "group" elements ... 1718 newGridGroup(value, c, g, globs, showStdName, modelElem); 1719 } else if (!name.equals("qualifier")) { // its a mistake 1720 log.error("No code to handle element of type {} in newGrid", value.getName()); 1721 } 1722 } 1723 1724 // add glue to the bottom to allow resize 1725 if (c.getComponentCount() > 0) { 1726 c.add(Box.createVerticalGlue()); 1727 } 1728 1729 // handle qualification if any 1730 QualifierAdder qa = new QualifierAdder() { 1731 @Override 1732 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 1733 return new JComponentQualifier(c, var, Integer.parseInt(value), relation); 1734 } 1735 1736 @Override 1737 protected void addListener(java.beans.PropertyChangeListener qc) { 1738 c.addPropertyChangeListener(qc); 1739 } 1740 }; 1741 1742 qa.processModifierElements(element, _varModel); 1743 return c; 1744 } 1745 1746 protected static class GridGlobals { 1747 1748 public int gridxCurrent = -1; 1749 public int gridyCurrent = -1; 1750 public List<Attribute> gridAttList; 1751 public GridBagConstraints gridConstraints; 1752 } 1753 1754 /** 1755 * Create a grid item from the JDOM Element 1756 * 1757 * @param element element containing grid item contents 1758 * @param showStdName show the name following the rules for the 1759 * <em>nameFmt</em> element 1760 * @param modelElem element containing the decoder model 1761 * @param globs properties to configure the layout 1762 * @return a panel containing the group 1763 */ 1764 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("DP_DO_INSIDE_DO_PRIVILEGED") // setAccessible() 1765 public JPanel newGridItem(Element element, boolean showStdName, Element modelElem, GridGlobals globs) { 1766 1767 List<Attribute> itemAttList = element.getAttributes(); // get item-level attributes 1768 List<Attribute> attList = new ArrayList<>(globs.gridAttList); 1769 attList.addAll(itemAttList); // merge grid and item-level attributes 1770// log.info("New gridtiem -----------------------------------------------"); 1771// log.info("Attribute list:"+attList); 1772 attList.add(new Attribute(LAST_GRIDX, "")); 1773 attList.add(new Attribute(LAST_GRIDY, "")); 1774// log.info("Updated Attribute list:"+attList); 1775// Attribute ax = attList.get(attList.size()-2); 1776// Attribute ay = attList.get(attList.size()-1); 1777// log.info("ax="+ax+";ay="+ay); 1778// log.info("Previous gridxCurrent="+globs.gridxCurrent+";gridyCurrent="+globs.gridyCurrent); 1779 for (int j = 0; j < attList.size(); j++) { 1780 Attribute attrib = attList.get(j); 1781 String attribName = attrib.getName(); 1782 String attribRawValue = attrib.getValue(); 1783 Field constraint; 1784 String constraintType; 1785 // make sure we only process the last gridx or gridy attribute in the list 1786 if (attribName.equals("gridx")) { 1787 Attribute a = new Attribute(LAST_GRIDX, attribRawValue); 1788 attList.set(attList.size() - 2, a); 1789// log.info("Moved & Updated Attribute list:"+attList); 1790 continue; //. don't process now 1791 } 1792 if (attribName.equals("gridy")) { 1793 Attribute a = new Attribute(LAST_GRIDY, attribRawValue); 1794 attList.set(attList.size() - 1, a); 1795// log.info("Moved & Updated Attribute list:"+attList); 1796 continue; //. don't process now 1797 } 1798 if (attribName.equals(LAST_GRIDX)) { // we must be at end of original list, restore last gridx 1799 attribName = "gridx"; 1800 if (attribRawValue.equals("")) { // don't process blank (unused) 1801 continue; 1802 } 1803 } 1804 if (attribName.equals(LAST_GRIDY)) { // we must be at end of original list, restore last gridy 1805 attribName = "gridy"; 1806 if (attribRawValue.equals("")) { // don't process blank (unused) 1807 continue; 1808 } 1809 } 1810 if ((attribName.equals("gridx") || attribName.equals("gridy")) && attribRawValue.equals("RELATIVE")) { 1811 attribRawValue = "NEXT"; // NEXT is a synonym for RELATIVE 1812 } 1813 if (attribName.equals("gridx") && attribRawValue.equals("CURRENT")) { 1814 attribRawValue = String.valueOf(Math.max(0, globs.gridxCurrent)); 1815 } 1816 if (attribName.equals("gridy") && attribRawValue.equals("CURRENT")) { 1817 attribRawValue = String.valueOf(Math.max(0, globs.gridyCurrent)); 1818 } 1819 if (attribName.equals("gridx") && attribRawValue.equals("NEXT")) { 1820 attribRawValue = String.valueOf(++globs.gridxCurrent); 1821 } 1822 if (attribName.equals("gridy") && attribRawValue.equals("NEXT")) { 1823 attribRawValue = String.valueOf(++globs.gridyCurrent); 1824 } 1825// log.info("attribName="+attribName+";attribRawValue="+attribRawValue); 1826 try { 1827 constraint = globs.gridConstraints.getClass().getDeclaredField(attribName); 1828 constraintType = constraint.getType().toString(); 1829 constraint.setAccessible(true); 1830 } catch (NoSuchFieldException ex) { 1831 log.error("Unrecognised attribute \"{}\", skipping", attribName); 1832 continue; 1833 } 1834 switch (constraintType) { 1835 case "int": { 1836 int attribValue; 1837 try { 1838 attribValue = Integer.parseInt(attribRawValue); 1839 constraint.set(globs.gridConstraints, attribValue); 1840 } catch (IllegalAccessException ey) { 1841 log.error("Unable to set constraint \"{}. IllegalAccessException error thrown.", attribName); 1842 } catch (NumberFormatException ex) { 1843 try { 1844 Field constant = globs.gridConstraints.getClass().getDeclaredField(attribRawValue); 1845 constant.setAccessible(true); 1846 attribValue = (Integer) GridBagConstraints.class.getField(attribRawValue).get(constant); 1847 constraint.set(globs.gridConstraints, attribValue); 1848 } catch (NoSuchFieldException ey) { 1849 log.error("Invalid value \"{}\" for attribute \"{}\"", attribRawValue, attribName); 1850 } catch (IllegalAccessException ey) { 1851 log.error("Unable to set constraint \"{}. IllegalAccessException error thrown.", attribName); 1852 } 1853 } 1854 break; 1855 } 1856 case "double": { 1857 double attribValue; 1858 try { 1859 attribValue = Double.parseDouble(attribRawValue); 1860 constraint.set(globs.gridConstraints, attribValue); 1861 } catch (IllegalAccessException ey) { 1862 log.error("Unable to set constraint \"{}. IllegalAccessException error thrown.", attribName); 1863 } catch (NumberFormatException ex) { 1864 log.error("Invalid value \"{}\" for attribute \"{}\"", attribRawValue, attribName); 1865 } 1866 break; 1867 } 1868 case "class java.awt.Insets": 1869 try { 1870 String[] insetStrings = attribRawValue.split(","); 1871 if (insetStrings.length == 4) { 1872 Insets attribValue = new Insets(Integer.parseInt(insetStrings[0]), Integer.parseInt(insetStrings[1]), Integer.parseInt(insetStrings[2]), Integer.parseInt(insetStrings[3])); 1873 constraint.set(globs.gridConstraints, attribValue); 1874 } else { 1875 log.error("Invalid value \"{}\" for attribute \"{}\"", attribRawValue, attribName); 1876 log.error("Value should be four integers of the form \"top,left,bottom,right\""); 1877 } 1878 } catch (IllegalAccessException ey) { 1879 log.error("Unable to set constraint \"{}. IllegalAccessException error thrown.", attribName); 1880 } catch (NumberFormatException ex) { 1881 log.error("Invalid value \"{}\" for attribute \"{}\"", attribRawValue, attribName); 1882 log.error("Value should be four integers of the form \"top,left,bottom,right\""); 1883 } 1884 break; 1885 default: 1886 log.error("Required \"{}\" handler for attribute \"{}\" not defined in JMRI code", constraintType, attribName); 1887 log.error("Please file a JMRI bug report at https://sourceforge.net/p/jmri/bugs/new/"); 1888 break; 1889 } 1890 } 1891// log.info("Updated globs.GridBagConstraints.gridx="+globs.gridConstraints.gridx+";globs.GridBagConstraints.gridy="+globs.gridConstraints.gridy); 1892 1893 // create a panel to add as a new grid item 1894 final JPanel c = new JPanel(); 1895 panelList.add(c); 1896 GridBagLayout g = new GridBagLayout(); 1897 GridBagConstraints cs = new GridBagConstraints(); 1898 c.setLayout(g); 1899 1900 // handle the xml definition 1901 // for all elements in the grid item 1902 List<Element> elemList = element.getChildren(); 1903 log.trace("newGridItem starting with {} elements", elemList.size()); 1904 for (Element value : elemList) { 1905 1906 // update the grid position 1907 cs.gridy = 0; 1908 cs.gridx++; 1909 1910 String name = value.getName(); 1911 log.trace("newGridItem processing {} element", name); 1912 // decode the type 1913 if (name.equals("display")) { // its a variable 1914 // load the variable 1915 newVariable(value, c, g, cs, showStdName); 1916 } else if (name.equals("separator")) { // its a separator 1917 JSeparator j = new JSeparator(SwingConstants.VERTICAL); 1918 cs.fill = GridBagConstraints.BOTH; 1919 cs.gridheight = GridBagConstraints.REMAINDER; 1920 g.setConstraints(j, cs); 1921 c.add(j); 1922 cs.fill = GridBagConstraints.NONE; 1923 cs.gridheight = 1; 1924 } else if (name.equals("label")) { 1925 cs.gridheight = GridBagConstraints.REMAINDER; 1926 makeLabel(value, c, g, cs); 1927 } else if (name.equals("soundlabel")) { 1928 cs.gridheight = GridBagConstraints.REMAINDER; 1929 makeSoundLabel(value, c, g, cs); 1930 } else if (name.equals("cvtable")) { 1931 makeCvTable(cs, g, c); 1932 } else if (name.equals("fnmapping")) { 1933 pickFnMapPanel(c, g, cs, modelElem); 1934 } else if (name.equals("dccaddress")) { 1935 JPanel l = addDccAddressPanel(value); 1936 if (l.getComponentCount() > 0) { 1937 cs.gridheight = GridBagConstraints.REMAINDER; 1938 g.setConstraints(l, cs); 1939 c.add(l); 1940 cs.gridheight = 1; 1941 } 1942 } else if (name.equals("column")) { 1943 // nested "column" elements ... 1944 cs.gridheight = GridBagConstraints.REMAINDER; 1945 JPanel l = newColumn(value, showStdName, modelElem); 1946 if (l.getComponentCount() > 0) { 1947 panelList.add(l); 1948 g.setConstraints(l, cs); 1949 c.add(l); 1950 cs.gridheight = 1; 1951 } 1952 } else if (name.equals("row")) { 1953 // nested "row" elements ... 1954 cs.gridwidth = GridBagConstraints.REMAINDER; 1955 JPanel l = newRow(value, showStdName, modelElem); 1956 if (l.getComponentCount() > 0) { 1957 panelList.add(l); 1958 g.setConstraints(l, cs); 1959 c.add(l); 1960 cs.gridwidth = 1; 1961 } 1962 } else if (name.equals("grid")) { 1963 // nested "grid" elements ... 1964 cs.gridwidth = GridBagConstraints.REMAINDER; 1965 JPanel l = newGrid(value, showStdName, modelElem); 1966 if (l.getComponentCount() > 0) { 1967 panelList.add(l); 1968 g.setConstraints(l, cs); 1969 c.add(l); 1970 cs.gridwidth = 1; 1971 } 1972 } else if (name.equals("group")) { 1973 // nested "group" elements ... 1974 JPanel l = newGroup(value, showStdName, modelElem); 1975 if (l.getComponentCount() > 0) { 1976 panelList.add(l); 1977 g.setConstraints(l, cs); 1978 c.add(l); 1979 } 1980 } else if (!name.equals("qualifier")) { // its a mistake 1981 log.error("No code to handle element of type {} in newGridItem", value.getName()); 1982 } 1983 } 1984 1985 globs.gridxCurrent = globs.gridConstraints.gridx; 1986 globs.gridyCurrent = globs.gridConstraints.gridy; 1987// log.info("Updated gridxCurrent="+globs.gridxCurrent+";gridyCurrent="+globs.gridyCurrent); 1988 1989 // add glue to the bottom to allow resize 1990 if (c.getComponentCount() > 0) { 1991 c.add(Box.createVerticalGlue()); 1992 } 1993 1994 // handle qualification if any 1995 QualifierAdder qa = new QualifierAdder() { 1996 @Override 1997 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 1998 return new JComponentQualifier(c, var, Integer.parseInt(value), relation); 1999 } 2000 2001 @Override 2002 protected void addListener(java.beans.PropertyChangeListener qc) { 2003 c.addPropertyChangeListener(qc); 2004 } 2005 }; 2006 2007 qa.processModifierElements(element, _varModel); 2008 return c; 2009 } 2010 2011 /** 2012 * Create label from Element. 2013 * 2014 * @param e element containing label contents 2015 * @param c panel to insert label into 2016 * @param g panel layout manager 2017 * @param cs constraints on layout manager 2018 */ 2019 protected void makeLabel(Element e, JPanel c, GridBagLayout g, GridBagConstraints cs) { 2020 String text = LocaleSelector.getAttribute(e, "text"); 2021 if (text == null || text.equals("")) { 2022 text = LocaleSelector.getAttribute(e, "label"); // label subelement not since 3.7.5 2023 } 2024 final JLabel l = new JLabel(text); 2025 l.setAlignmentX(1.0f); 2026 cs.fill = GridBagConstraints.BOTH; 2027 log.trace("Add label: {} cs: {} fill: {} x: {} y: {}", 2028 l.getText(), cs.gridwidth, cs.fill, cs.gridx, cs.gridy); 2029 g.setConstraints(l, cs); 2030 c.add(l); 2031 cs.fill = GridBagConstraints.NONE; 2032 cs.gridwidth = 1; 2033 cs.gridheight = 1; 2034 2035 // handle qualification if any 2036 QualifierAdder qa = new QualifierAdder() { 2037 @Override 2038 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 2039 return new JComponentQualifier(l, var, Integer.parseInt(value), relation); 2040 } 2041 2042 @Override 2043 protected void addListener(java.beans.PropertyChangeListener qc) { 2044 l.addPropertyChangeListener(qc); 2045 } 2046 }; 2047 2048 qa.processModifierElements(e, _varModel); 2049 } 2050 2051 /** 2052 * Create sound label from Element. 2053 * 2054 * @param e element containing label contents 2055 * @param c panel to insert label into 2056 * @param g panel layout manager 2057 * @param cs constraints on layout manager 2058 */ 2059 protected void makeSoundLabel(Element e, JPanel c, GridBagLayout g, GridBagConstraints cs) { 2060 String labelText = rosterEntry.getSoundLabel(Integer.parseInt(Objects.requireNonNull(LocaleSelector.getAttribute(e, "num")))); 2061 final JLabel l = new JLabel(labelText); 2062 l.setAlignmentX(1.0f); 2063 cs.fill = GridBagConstraints.BOTH; 2064 if (log.isDebugEnabled()) { 2065 log.debug("Add soundlabel: {} cs: {} {} {} {}", l.getText(), cs.gridwidth, cs.fill, cs.gridx, cs.gridy); 2066 } 2067 g.setConstraints(l, cs); 2068 c.add(l); 2069 cs.fill = GridBagConstraints.NONE; 2070 cs.gridwidth = 1; 2071 cs.gridheight = 1; 2072 2073 // handle qualification if any 2074 QualifierAdder qa = new QualifierAdder() { 2075 @Override 2076 protected Qualifier createQualifier(VariableValue var, String relation, String value) { 2077 return new JComponentQualifier(l, var, Integer.parseInt(value), relation); 2078 } 2079 2080 @Override 2081 protected void addListener(java.beans.PropertyChangeListener qc) { 2082 l.addPropertyChangeListener(qc); 2083 } 2084 }; 2085 2086 qa.processModifierElements(e, _varModel); 2087 } 2088 2089 void makeCvTable(GridBagConstraints cs, GridBagLayout g, JPanel c) { 2090 log.debug("starting to build CvTable pane"); 2091 2092 TableRowSorter<TableModel> sorter = new TableRowSorter<>(_cvModel); 2093 2094 JTable cvTable = new JTable(_cvModel); 2095 2096 sorter.setComparator(CvTableModel.NUMCOLUMN, new jmri.jmrit.symbolicprog.CVNameComparator()); 2097 2098 List<RowSorter.SortKey> sortKeys = new ArrayList<>(); 2099 sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING)); 2100 sorter.setSortKeys(sortKeys); 2101 2102 cvTable.setRowSorter(sorter); 2103 2104 cvTable.setDefaultRenderer(JTextField.class, new CvValueRenderer()); 2105 cvTable.setDefaultRenderer(JButton.class, new CvValueRenderer()); 2106 cvTable.setDefaultRenderer(String.class, new CvValueRenderer()); 2107 cvTable.setDefaultRenderer(Integer.class, new CvValueRenderer()); 2108 cvTable.setDefaultEditor(JTextField.class, new ValueEditor()); 2109 cvTable.setDefaultEditor(JButton.class, new ValueEditor()); 2110 cvTable.setRowHeight(new JButton("X").getPreferredSize().height); 2111 // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541) 2112 // instead of forcing the columns to fill the frame (and only fill) 2113 //cvTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 2114 JScrollPane cvScroll = new JScrollPane(cvTable); 2115 cvScroll.setColumnHeaderView(cvTable.getTableHeader()); 2116 2117 cs.fill = GridBagConstraints.BOTH; 2118 cs.weighty = 2.0; 2119 cs.weightx = 0.75; 2120 g.setConstraints(cvScroll, cs); 2121 c.add(cvScroll); 2122 2123 // remember which CVs to read/write 2124 isCvTablePane = true; 2125 setCvListFromTable(); 2126 2127 _cvTable = true; 2128 log.debug("end of building CvTable pane"); 2129 } 2130 2131 void setCvListFromTable() { 2132 // remember which CVs to read/write 2133 for (int j = 0; j < _cvModel.getRowCount(); j++) { 2134 cvList.add(j); 2135 } 2136 _varModel.setButtonModeFromProgrammer(); 2137 } 2138 2139 /** 2140 * Pick an appropriate function map panel depending on model attribute. 2141 * <dl> 2142 * <dt>If attribute extFnsESU="yes":</dt> 2143 * <dd>Invoke 2144 * {@code FnMapPanelESU(VariableTableModel v, List<Integer> varsUsed, Element model)}</dd> 2145 * <dt>Otherwise:</dt> 2146 * <dd>Invoke 2147 * {@code FnMapPanel(VariableTableModel v, List<Integer> varsUsed, Element model)}</dd> 2148 * </dl> 2149 * 2150 * @param modelElem element containing model attributes 2151 * @param c panel to add function map panel to 2152 * @param g panel layout manager 2153 * @param cs constraints on layout manager 2154 */ 2155 // why does this use a different parameter order than all similar methods? 2156 void pickFnMapPanel(JPanel c, GridBagLayout g, GridBagConstraints cs, Element modelElem) { 2157 boolean extFnsESU = false; 2158 Attribute a = modelElem.getAttribute("extFnsESU"); 2159 try { 2160 if (a != null) { 2161 extFnsESU = !(a.getValue()).equalsIgnoreCase("no"); 2162 } 2163 } catch (Exception ex) { 2164 log.error("error handling decoder's extFnsESU value"); 2165 } 2166 if (extFnsESU) { 2167 FnMapPanelESU l = new FnMapPanelESU(_varModel, varList, modelElem, rosterEntry, _cvModel); 2168 fnMapListESU.add(l); // remember for deletion 2169 cs.gridwidth = GridBagConstraints.REMAINDER; 2170 g.setConstraints(l, cs); 2171 c.add(l); 2172 } else { 2173 FnMapPanel l = new FnMapPanel(_varModel, varList, modelElem); 2174 fnMapList.add(l); // remember for deletion 2175 cs.gridwidth = GridBagConstraints.REMAINDER; 2176 g.setConstraints(l, cs); 2177 c.add(l); 2178 } 2179 cs.gridwidth = 1; 2180 } 2181 2182 /** 2183 * Add the representation of a single variable. The variable is defined by a 2184 * JDOM variable Element from the XML file. 2185 * 2186 * @param var element containing variable 2187 * @param col column to insert label into 2188 * @param g panel layout manager 2189 * @param cs constraints on layout manager 2190 * @param showStdName show the name following the rules for the 2191 * <em>nameFmt</em> element 2192 */ 2193 public void newVariable(Element var, JComponent col, 2194 GridBagLayout g, GridBagConstraints cs, boolean showStdName) { 2195 2196 // get the name 2197 String name = var.getAttribute("item").getValue(); 2198 2199 // if it doesn't exist, do nothing 2200 int i = _varModel.findVarIndex(name); 2201 if (i < 0) { 2202 log.trace("Variable \"{}\" not found, omitted", name); 2203 return; 2204 } 2205// Leave here for now. Need to track pre-existing corner-case issue 2206// log.info("Entry item="+name+";cs.gridx="+cs.gridx+";cs.gridy="+cs.gridy+";cs.anchor="+cs.anchor+";cs.ipadx="+cs.ipadx); 2207 2208 // check label orientation 2209 Attribute attr; 2210 String layout = "left"; // this default is also set in the DTD 2211 if ((attr = var.getAttribute("layout")) != null && attr.getValue() != null) { 2212 layout = attr.getValue(); 2213 } 2214 2215 // load label if specified, else use name 2216 String label = name; 2217 if (!showStdName) { 2218 // get name attribute from variable, as that's the mfg name 2219 label = _varModel.getLabel(i); 2220 } 2221 String temp = LocaleSelector.getAttribute(var, "label"); 2222 if (temp != null) { 2223 label = temp; 2224 } 2225 2226 // get representation; store into the list to be programmed 2227 JComponent rep = getRepresentation(name, var); 2228 varList.add(i); 2229 2230 // create the paired label 2231 JLabel l = new WatchingLabel(label, rep); 2232 2233 int spaceWidth = getFontMetrics(l.getFont()).stringWidth(" "); 2234 2235 // now handle the four orientations 2236 // assemble v from label, rep 2237 switch (layout) { 2238 case "left": 2239 cs.anchor = GridBagConstraints.EAST; 2240 cs.ipadx = spaceWidth; 2241 g.setConstraints(l, cs); 2242 col.add(l); 2243 cs.ipadx = 0; 2244 cs.gridx++; 2245 cs.anchor = GridBagConstraints.WEST; 2246 g.setConstraints(rep, cs); 2247 col.add(rep); 2248 break; 2249// log.info("Exit item="+name+";cs.gridx="+cs.gridx+";cs.gridy="+cs.gridy+";cs.anchor="+cs.anchor+";cs.ipadx="+cs.ipadx); 2250 case "right": 2251 cs.anchor = GridBagConstraints.EAST; 2252 g.setConstraints(rep, cs); 2253 col.add(rep); 2254 cs.gridx++; 2255 cs.anchor = GridBagConstraints.WEST; 2256 cs.ipadx = spaceWidth; 2257 g.setConstraints(l, cs); 2258 col.add(l); 2259 cs.ipadx = 0; 2260 break; 2261 case "below": 2262 // variable in center of upper line 2263 cs.anchor = GridBagConstraints.CENTER; 2264 g.setConstraints(rep, cs); 2265 col.add(rep); 2266 // label aligned like others 2267 cs.gridy++; 2268 cs.anchor = GridBagConstraints.WEST; 2269 cs.ipadx = spaceWidth; 2270 g.setConstraints(l, cs); 2271 col.add(l); 2272 cs.ipadx = 0; 2273 break; 2274 case "above": 2275 // label aligned like others 2276 cs.anchor = GridBagConstraints.WEST; 2277 cs.ipadx = spaceWidth; 2278 g.setConstraints(l, cs); 2279 col.add(l); 2280 cs.ipadx = 0; 2281 // variable in center of lower line 2282 cs.gridy++; 2283 cs.anchor = GridBagConstraints.CENTER; 2284 g.setConstraints(rep, cs); 2285 col.add(rep); 2286 break; 2287 default: 2288 log.error("layout internally inconsistent: {}", layout); 2289 } 2290 } 2291 2292 /** 2293 * Get a GUI representation of a particular variable for display. 2294 * 2295 * @param name Name used to look up the Variable object 2296 * @param var XML Element which might contain a "format" attribute to be 2297 * used in the {@link VariableValue#getNewRep} call from the 2298 * Variable object; "tooltip" elements are also processed here. 2299 * @return JComponent representing this variable 2300 */ 2301 public JComponent getRepresentation(String name, Element var) { 2302 int i = _varModel.findVarIndex(name); 2303 VariableValue variable = _varModel.getVariable(i); 2304 JComponent rep = null; 2305 String format = "default"; 2306 Attribute attr; 2307 if ((attr = var.getAttribute("format")) != null && attr.getValue() != null) { 2308 format = attr.getValue(); 2309 } 2310 2311 boolean viewOnly = (var.getAttribute("viewOnly") != null && 2312 var.getAttribute("viewOnly").getValue().equals("yes")); 2313 2314 if (i >= 0) { 2315 rep = getRep(i, format); 2316 rep.setMaximumSize(rep.getPreferredSize()); 2317 // set tooltip if specified here & not overridden by defn in Variable 2318 String tip = LocaleSelector.getAttribute(var, "tooltip"); 2319 if (rep.getToolTipText() != null) { 2320 tip = rep.getToolTipText(); 2321 } 2322 rep.setToolTipText(modifyToolTipText(tip, variable)); 2323 if (viewOnly) { 2324 rep.setEnabled(false); 2325 } 2326 } 2327 return rep; 2328 } 2329 2330 /** 2331 * Takes default tool tip text, e.g. from the decoder element, and modifies 2332 * it as needed. 2333 * <p> 2334 * Intended to handle e.g. adding CV numbers to variables. 2335 * 2336 * @param start existing tool tip text 2337 * @param variable the CV 2338 * @return new tool tip text 2339 */ 2340 String modifyToolTipText(String start, VariableValue variable) { 2341 log.trace("modifyToolTipText: {}", variable.label()); 2342 // this is the place to invoke VariableValue methods to (conditionally) 2343 // add information about CVs, etc in the ToolTip text 2344 2345 // Optionally add CV numbers based on Roster Preferences setting 2346 start = CvUtil.addCvDescription(start, variable.getCvDescription(), variable.getMask()); 2347 2348 // Indicate what the command station can do 2349 // need to update this with e.g. the specific CV numbers 2350 if (_cvModel.getProgrammer() != null 2351 && !_cvModel.getProgrammer().getCanRead()) { 2352 start = StringUtil.concatTextHtmlAware(start, SymbolicProgBundle.getMessage("TipHardwareCannotRead")); 2353 } 2354 if (_cvModel.getProgrammer() != null 2355 && !_cvModel.getProgrammer().getCanWrite()) { 2356 start = StringUtil.concatTextHtmlAware(start, SymbolicProgBundle.getMessage("TipHardwareCannotWrite")); 2357 } 2358 2359 // indicate other reasons for read/write constraints 2360 if (variable.getReadOnly()) { 2361 start = StringUtil.concatTextHtmlAware(start, SymbolicProgBundle.getMessage("TipDefinedToBeReadOnly")); 2362 } 2363 if (variable.getWriteOnly()) { 2364 start = StringUtil.concatTextHtmlAware(start, SymbolicProgBundle.getMessage("TipDefinedToBeWriteOnly")); 2365 } 2366 2367 return start; 2368 } 2369 2370 JComponent getRep(int i, String format) { 2371 return (JComponent) (_varModel.getRep(i, format)); 2372 } 2373 2374 /** 2375 * list of fnMapping objects to dispose 2376 */ 2377 ArrayList<FnMapPanel> fnMapList = new ArrayList<>(); 2378 ArrayList<FnMapPanelESU> fnMapListESU = new ArrayList<>(); 2379 /** 2380 * list of JPanel objects to removeAll 2381 */ 2382 ArrayList<JPanel> panelList = new ArrayList<>(); 2383 2384 public void dispose() { 2385 log.debug("dispose"); 2386 2387 // remove components 2388 removeAll(); 2389 2390 readChangesButton.removeItemListener(l1); 2391 readAllButton.removeItemListener(l2); 2392 writeChangesButton.removeItemListener(l3); 2393 writeAllButton.removeItemListener(l4); 2394 confirmChangesButton.removeItemListener(l5); 2395 confirmAllButton.removeItemListener(l6); 2396 l1 = l2 = l3 = l4 = l5 = l6 = null; 2397 2398 if (_programmingVar != null) { 2399 _programmingVar.removePropertyChangeListener(this); 2400 } 2401 if (_programmingCV != null) { 2402 _programmingCV.removePropertyChangeListener(this); 2403 } 2404 2405 _programmingVar = null; 2406 _programmingCV = null; 2407 2408 varList.clear(); 2409 varList = null; 2410 cvList.clear(); 2411 cvList = null; 2412 2413 // dispose of any panels 2414 for (JPanel jPanel : panelList) { 2415 jPanel.removeAll(); 2416 } 2417 panelList.clear(); 2418 panelList = null; 2419 2420 // dispose of any fnMaps 2421 for (FnMapPanel fnMapPanel : fnMapList) { 2422 fnMapPanel.dispose(); 2423 } 2424 fnMapList.clear(); 2425 fnMapList = null; 2426 2427 // dispose of any fnMaps 2428 for (FnMapPanelESU fnMapPanelESU : fnMapListESU) { 2429 fnMapPanelESU.dispose(); 2430 } 2431 fnMapListESU.clear(); 2432 fnMapListESU = null; 2433 2434 readChangesButton = null; 2435 writeChangesButton = null; 2436 2437 // these are disposed elsewhere 2438 _cvModel = null; 2439 _varModel = null; 2440 } 2441 2442 /** 2443 * Check if varList and cvList, and thus the tab, is empty. 2444 * 2445 * @return true if empty 2446 */ 2447 public boolean isEmpty() { 2448 return (varList.isEmpty() && cvList.isEmpty()); 2449 } 2450 2451 public boolean includeInPrint() { 2452 return print; 2453 } 2454 2455 public void includeInPrint(boolean inc) { 2456 print = inc; 2457 } 2458 boolean print = false; 2459 2460 public void printPane(HardcopyWriter w) { 2461 // if pane is empty, don't print anything 2462 if (isEmpty()) { 2463 return; 2464 } 2465 2466 // Define column widths for name and value output. 2467 // Make col 2 slightly larger than col 1 and reduce both to allow for 2468 // extra spaces that will be added during concatenation 2469 int col1Width = w.getCharactersPerLine() / 2 - 3 - 5; 2470 int col2Width = w.getCharactersPerLine() / 2 - 3 + 5; 2471 2472 try { 2473 //Create a string of spaces the width of the first column 2474 StringBuilder spaces = new StringBuilder(); 2475 spaces.append(" ".repeat(Math.max(0, col1Width))); 2476 // start with pane name in bold 2477 String heading1 = SymbolicProgBundle.getMessage("PrintHeadingField"); 2478 String heading2 = SymbolicProgBundle.getMessage("PrintHeadingSetting"); 2479 String s; 2480 int interval = spaces.length() - heading1.length(); 2481 w.setFontStyle(Font.BOLD); 2482 // write the section name and dividing line 2483 s = mName; 2484 w.write(s, 0, s.length()); 2485 w.writeBorders(); 2486 //Draw horizontal dividing line for each Pane section 2487 w.write(w.getCurrentLineNumber(), 0, w.getCurrentLineNumber(), 2488 w.getCharactersPerLine() + 1); 2489 s = "\n"; 2490 w.write(s, 0, s.length()); 2491 // if this isn't the raw CV section, write the column headings 2492 if (cvList.isEmpty()) { 2493 w.setFontStyle(Font.BOLD + Font.ITALIC); 2494 s = " " + heading1 + spaces.substring(0, interval) + " " + heading2; 2495 w.write(s, 0, s.length()); 2496 w.writeBorders(); 2497 s = "\n"; 2498 w.write(s, 0, s.length()); 2499 } 2500 w.setFontStyle(Font.PLAIN); 2501 // Define a vector to store the names of variables that have been printed 2502 // already. If they have been printed, they will be skipped. 2503 // Using a vector here since we don't know how many variables will 2504 // be printed and it allows expansion as necessary 2505 ArrayList<String> printedVariables = new ArrayList<>(10); 2506 // index over variables 2507 for (int varNum : varList) { 2508 VariableValue var = _varModel.getVariable(varNum); 2509 String name = var.label(); 2510 if (name == null) { 2511 name = var.item(); 2512 } 2513 // Check if variable has been printed. If not store it and print 2514 boolean alreadyPrinted = false; 2515 for (String printedVariable : printedVariables) { 2516 if (name.equals(printedVariable)) { 2517 alreadyPrinted = true; 2518 break; 2519 } 2520 } 2521 // If already printed, skip it. If not, store it and print 2522 if (alreadyPrinted) { 2523 continue; 2524 } 2525 printedVariables.add(name); 2526 2527 String value = var.getTextValue(); 2528 String originalName = name; 2529 String originalValue = value; 2530 name = name + " (CV" + var.getCvNum() + ")"; // NO I18N 2531 2532 // define index values for name and value substrings 2533 int nameLeftIndex = 0; 2534 int nameRightIndex = name.length(); 2535 int valueLeftIndex = 0; 2536 int valueRightIndex = value.length(); 2537 String trimmedName; 2538 String trimmedValue; 2539 2540 // Check the name length to see if it is wider than the column. 2541 // If so, split it and do the same checks for the Value 2542 // Then concatenate the name and value (or the split versions thereof) 2543 // before writing - if split, repeat until all pieces have been output 2544 while ((valueLeftIndex < value.length()) || (nameLeftIndex < name.length())) { 2545 // name split code 2546 if (name.substring(nameLeftIndex).length() > col1Width) { 2547 for (int j = 0; j < col1Width; j++) { 2548 String delimiter = name.substring(nameLeftIndex + col1Width - j - 1, nameLeftIndex + col1Width - j); 2549 if (delimiter.equals(" ") || delimiter.equals(";") || delimiter.equals(",")) { 2550 nameRightIndex = nameLeftIndex + col1Width - j; 2551 break; 2552 } 2553 } 2554 trimmedName = name.substring(nameLeftIndex, nameRightIndex); 2555 nameLeftIndex = nameRightIndex; 2556 int space = spaces.length() - trimmedName.length(); 2557 s = " " + trimmedName + spaces.substring(0, space); 2558 } else { 2559 trimmedName = name.substring(nameLeftIndex); 2560 int space = spaces.length() - trimmedName.length(); 2561 s = " " + trimmedName + spaces.substring(0, space); 2562 name = ""; 2563 nameLeftIndex = 0; 2564 } 2565 // value split code 2566 if (value.substring(valueLeftIndex).length() > col2Width) { 2567 for (int j = 0; j < col2Width; j++) { 2568 String delimiter = value.substring(valueLeftIndex + col2Width - j - 1, valueLeftIndex + col2Width - j); 2569 if (delimiter.equals(" ") || delimiter.equals(";") || delimiter.equals(",")) { 2570 valueRightIndex = valueLeftIndex + col2Width - j; 2571 break; 2572 } 2573 } 2574 trimmedValue = value.substring(valueLeftIndex, valueRightIndex); 2575 valueLeftIndex = valueRightIndex; 2576 s = s + " " + trimmedValue; 2577 } else { 2578 trimmedValue = value.substring(valueLeftIndex); 2579 s = s + " " + trimmedValue; 2580 valueLeftIndex = 0; 2581 value = ""; 2582 } 2583 w.write(s, 0, s.length()); 2584 w.writeBorders(); 2585 s = "\n"; 2586 w.write(s, 0, s.length()); 2587 } 2588 // Check for a Speed Table output and create a graphic display. 2589 // Java 1.5 has a known bug, #6328248, that prevents printing of progress 2590 // bars using old style printing classes. It results in blank bars on Windows, 2591 // but hangs Macs. The version check is a workaround. 2592 float v = Float.parseFloat(System.getProperty("java.version").substring(0, 3)); 2593 if (originalName.equals("Speed Table") && v < 1.5) { 2594 // set the height of the speed table graph in lines 2595 int speedFrameLineHeight = 11; 2596 s = "\n"; 2597 2598 // check that there is enough room on the page; if not, 2599 // space down the rest of the page. 2600 // don't use page break because we want the table borders to be written 2601 // to the bottom of the page 2602 int pageSize = w.getLinesPerPage(); 2603 int here = w.getCurrentLineNumber(); 2604 if (pageSize - here < speedFrameLineHeight) { 2605 for (int j = 0; j < (pageSize - here); j++) { 2606 w.writeBorders(); 2607 w.write(s, 0, s.length()); 2608 } 2609 } 2610 2611 // Now that there is page space, create the window to hold the graphic speed table 2612 JWindow speedWindow = new JWindow(); 2613 // Window size as wide as possible to allow for largest type size 2614 speedWindow.setSize(512, 165); 2615 speedWindow.getContentPane().setBackground(Color.white); 2616 speedWindow.getContentPane().setLayout(null); 2617 // in preparation for display, extract the speed table values into an array 2618 StringTokenizer valueTokens = new StringTokenizer(originalValue, ",", false); 2619 int[] speedVals = new int[28]; 2620 int k = 0; 2621 while (valueTokens.hasMoreTokens()) { 2622 speedVals[k] = Integer.parseInt(valueTokens.nextToken()); 2623 k++; 2624 } 2625 2626 // Now create a set of vertical progress bar whose length is based 2627 // on the speed table value (half height) and add them to the window 2628 for (int j = 0; j < 28; j++) { 2629 JProgressBar printerBar = new JProgressBar(JProgressBar.VERTICAL, 0, 127); 2630 printerBar.setBounds(52 + j * 15, 19, 10, 127); 2631 printerBar.setValue(speedVals[j] / 2); 2632 printerBar.setBackground(Color.white); 2633 printerBar.setForeground(Color.darkGray); 2634 printerBar.setBorder(BorderFactory.createLineBorder(Color.black)); 2635 speedWindow.getContentPane().add(printerBar); 2636 // create a set of value labels at the top containing the speed table values 2637 JLabel barValLabel = new JLabel(Integer.toString(speedVals[j]), SwingConstants.CENTER); 2638 barValLabel.setBounds(50 + j * 15, 4, 15, 15); 2639 barValLabel.setFont(new Font("Monospaced", Font.PLAIN, 7)); 2640 speedWindow.getContentPane().add(barValLabel); 2641 //Create a set of labels at the bottom with the CV numbers in them 2642 JLabel barCvLabel = new JLabel(Integer.toString(67 + j), SwingConstants.CENTER); 2643 barCvLabel.setBounds(50 + j * 15, 150, 15, 15); 2644 barCvLabel.setFont(new Font("Monospaced", Font.PLAIN, 7)); 2645 speedWindow.getContentPane().add(barCvLabel); 2646 } 2647 JLabel cvLabel = new JLabel(Bundle.getMessage("Value")); 2648 cvLabel.setFont(new Font("Monospaced", Font.PLAIN, 7)); 2649 cvLabel.setBounds(25, 4, 26, 15); 2650 speedWindow.getContentPane().add(cvLabel); 2651 JLabel valueLabel = new JLabel("CV"); // I18N seems undesirable for support 2652 valueLabel.setFont(new Font("Monospaced", Font.PLAIN, 7)); 2653 valueLabel.setBounds(37, 150, 13, 15); 2654 speedWindow.getContentPane().add(valueLabel); 2655 // pass the complete window to the printing class 2656 w.write(speedWindow); 2657 // Now need to write the borders on sides of table 2658 for (int j = 0; j < speedFrameLineHeight; j++) { 2659 w.writeBorders(); 2660 w.write(s, 0, s.length()); 2661 } 2662 } 2663 } 2664 2665 final int TABLE_COLS = 3; 2666 2667 // index over CVs 2668 if (cvList.size() > 0) { 2669// Check how many Cvs there are to print 2670 int cvCount = cvList.size(); 2671 w.setFontStyle(Font.BOLD); //set font to Bold 2672 // print a simple heading with I18N 2673 s = String.format("%1$21s", Bundle.getMessage("Value")) + String.format("%1$28s", Bundle.getMessage("Value")) + 2674 String.format("%1$28s", Bundle.getMessage("Value")); 2675 w.write(s, 0, s.length()); 2676 w.writeBorders(); 2677 s = "\n"; 2678 w.write(s, 0, s.length()); 2679 // NO I18N 2680 s = " CV Dec Hex CV Dec Hex CV Dec Hex"; 2681 w.write(s, 0, s.length()); 2682 w.writeBorders(); 2683 s = "\n"; 2684 w.write(s, 0, s.length()); 2685 w.setFontStyle(0); //set font back to Normal 2686 // } 2687 /*create an array to hold CV/Value strings to allow reformatting and sorting 2688 Same size as the table drawn above (TABLE_COLS columns*tableHeight; heading rows 2689 not included). Use the count of how many CVs there are to determine the number 2690 of table rows required. Add one more row if the divison into TABLE_COLS columns 2691 isn't even. 2692 */ 2693 int tableHeight = cvCount / TABLE_COLS; 2694 if (cvCount % TABLE_COLS > 0) { 2695 tableHeight++; 2696 } 2697 String[] cvStrings = new String[TABLE_COLS * tableHeight]; 2698 2699 //blank the array 2700 Arrays.fill(cvStrings, ""); 2701 2702 // get each CV and value 2703 int i = 0; 2704 for (int cvNum : cvList) { 2705 CvValue cv = _cvModel.getCvByRow(cvNum); 2706 2707 int value = cv.getValue(); 2708 2709 //convert and pad numbers as needed 2710 String numString = String.format("%12s", cv.number()); 2711 StringBuilder valueString = new StringBuilder(Integer.toString(value)); 2712 String valueStringHex = Integer.toHexString(value).toUpperCase(); 2713 if (value < 16) { 2714 valueStringHex = "0" + valueStringHex; 2715 } 2716 for (int j = 1; j < 3; j++) { 2717 if (valueString.length() < 3) { 2718 valueString.insert(0, " "); 2719 } 2720 } 2721 //Create composite string of CV and its decimal and hex values 2722 s = " " + numString + " " + valueString + " " + valueStringHex 2723 + " "; 2724 2725 //populate printing array - still treated as a single column 2726 cvStrings[i] = s; 2727 i++; 2728 } 2729 2730 //sort the array in CV order (just the members with values) 2731 String temp; 2732 boolean swap; 2733 do { 2734 swap = false; 2735 for (i = 0; i < _cvModel.getRowCount() - 1; i++) { 2736 if (PrintCvAction.cvSortOrderVal(cvStrings[i + 1].substring(0, 15).trim()) < PrintCvAction.cvSortOrderVal(cvStrings[i].substring(0, 15).trim())) { 2737 temp = cvStrings[i + 1]; 2738 cvStrings[i + 1] = cvStrings[i]; 2739 cvStrings[i] = temp; 2740 swap = true; 2741 } 2742 } 2743 } while (swap); 2744 2745 //Print the array in four columns 2746 for (i = 0; i < tableHeight; i++) { 2747 s = cvStrings[i] + " " + cvStrings[i + tableHeight] + " " + cvStrings[i 2748 + tableHeight * 2]; 2749 w.write(s, 0, s.length()); 2750 w.writeBorders(); 2751 s = "\n"; 2752 w.write(s, 0, s.length()); 2753 } 2754 } 2755 s = "\n"; 2756 w.writeBorders(); 2757 w.write(s, 0, s.length()); 2758 w.writeBorders(); 2759 w.write(s, 0, s.length()); 2760 2761 // handle special cases 2762 } catch (IOException e) { 2763 log.warn("error during printing", e); 2764 } 2765 2766 } 2767 2768 private JPanel addDccAddressPanel(Element e) { 2769 JPanel l = new DccAddressPanel(_varModel); 2770 panelList.add(l); 2771 // make sure this will get read/written, even if real vars not on pane 2772 int iVar; 2773 2774 // note we want Short Address first, as it might change others 2775 iVar = _varModel.findVarIndex("Short Address"); 2776 if (iVar >= 0) { 2777 varList.add(iVar); 2778 } else { 2779 log.debug("addDccAddressPanel did not find Short Address"); 2780 } 2781 2782 iVar = _varModel.findVarIndex("Address Format"); 2783 if (iVar >= 0) { 2784 varList.add(iVar); 2785 } else { 2786 log.debug("addDccAddressPanel did not find Address Format"); 2787 } 2788 2789 iVar = _varModel.findVarIndex("Long Address"); 2790 if (iVar >= 0) { 2791 varList.add(iVar); 2792 } else { 2793 log.debug("addDccAddressPanel did not find Long Address"); 2794 } 2795 2796 // included here because CV1 can modify it, even if it doesn't show on pane; 2797 iVar = _varModel.findVarIndex("Consist Address"); 2798 if (iVar >= 0) { 2799 varList.add(iVar); 2800 } else { 2801 log.debug("addDccAddressPanel did not find CV19 Consist Address"); 2802 } 2803 2804 return l; 2805 } 2806 2807 private final static Logger log = LoggerFactory.getLogger(PaneProgPane.class); 2808 2809}