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