001package jmri.jmrit.symbolicprog; 002 003import java.awt.BorderLayout; 004import java.awt.event.ActionEvent; 005import java.awt.event.MouseAdapter; 006import java.awt.event.MouseEvent; 007import java.util.ArrayList; 008import java.util.Enumeration; 009import java.util.HashMap; 010import java.util.List; 011import javax.swing.ButtonGroup; 012import javax.swing.JLabel; 013import javax.swing.JPanel; 014import javax.swing.JRadioButton; 015import javax.swing.JScrollPane; 016import javax.swing.JTree; 017import javax.swing.event.TreeSelectionEvent; 018import javax.swing.event.TreeSelectionListener; 019import javax.swing.tree.DefaultMutableTreeNode; 020import javax.swing.tree.DefaultTreeModel; 021import javax.swing.tree.DefaultTreeSelectionModel; 022import javax.swing.tree.TreeNode; 023import javax.swing.tree.TreePath; 024import jmri.GlobalProgrammerManager; 025import jmri.InstanceManager; 026import jmri.jmrit.decoderdefn.DecoderFile; 027import jmri.jmrit.decoderdefn.DecoderIndexFile; 028import jmri.jmrit.progsupport.ProgModeSelector; 029import jmri.jmrit.roster.Roster; 030import jmri.jmrit.roster.RosterEntry; 031import jmri.util.StringUtil; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035/** 036 * Provide GUI controls to select a known loco and/or new decoder. 037 * <p> 038 * This is an extension of the CombinedLocoSelPane class to use a JTree instead 039 * of a JComboBox for the decoder selection. The loco selection (Roster 040 * manipulation) parts are unchanged. 041 * <p> 042 * The JComboBox implementation always had to have selected entries, so we added 043 * dummy "select from .." items at the top and used those to indicate 044 * that there was no selection in that box. Here, the lack of a selection 045 * indicates there's no selection. 046 * <p> 047 * Internally, the "filter" is used to only show identified models (leaf nodes). 048 * This is implemented in internal InvisibleTreeModel and DecoderTreeNode 049 * classes. 050 * <p> 051 * The decoder definition {@link jmri.jmrit.decoderdefn.DecoderFile.Showable} 052 * attribute also interacts with those. 053 * 054 * @author Bob Jacobsen Copyright (C) 2001, 2002, 2013 055 */ 056public class CombinedLocoSelTreePane extends CombinedLocoSelPane { 057 058 /** 059 * The decoder selection tree. 060 */ 061 protected JTree dTree; 062 063 /** 064 * A panel immediately below the decoder selection tree. 065 * <br><br> 066 * Used for tree action buttons. 067 */ 068 protected JPanel viewButtons; 069 070 /** 071 * The listener for the decoder selection tree. 072 */ 073 protected transient volatile TreeSelectionListener dListener; 074 InvisibleTreeModel dModel; 075 DecoderTreeNode dRoot; 076 JRadioButton showAll; 077 JRadioButton showMatched; 078 ArrayList<TreePath> selectedPath = new ArrayList<>(); 079 080 /** 081 * Provide GUI controls to select a known loco and/or new decoder. 082 * 083 * @param s Reference to a JLabel that should be updated with status 084 * information as identification happens. 085 * 086 * @param selector Reference to a 087 * {@link jmri.jmrit.progsupport.ProgModeSelector} panel 088 * that configures the programming mode. 089 */ 090 public CombinedLocoSelTreePane(JLabel s, ProgModeSelector selector) { 091 super(s, selector); 092 } 093 094 /** 095 * Create the panel used to select the decoder. 096 * 097 * @return a JPanel for handling the decoder-selection GUI 098 */ 099 @Override 100 protected JPanel layoutDecoderSelection() { 101 JPanel pane1a = new JPanel(new BorderLayout()); 102 pane1a.add(new JLabel(Bundle.getMessage("LabelDecoderInstalled")), BorderLayout.NORTH); 103 // create the list of manufacturers; get the list of decoders, and add elements 104 dRoot = new DecoderTreeNode("Root"); 105 dModel = new InvisibleTreeModel(dRoot); 106 dTree = new JTree(dModel) { 107 108 @Override 109 public String getToolTipText(MouseEvent evt) { 110 if (getRowForLocation(evt.getX(), evt.getY()) == -1) { 111 return null; 112 } 113 TreePath curPath = getPathForLocation(evt.getX(), evt.getY()); 114 return ((DecoderTreeNode) curPath.getLastPathComponent()).getToolTipText(); 115 } 116 }; 117 dTree.setToolTipText(""); 118 119 createDecoderTypeContents(); 120 121 // build the tree GUI 122 pane1a.add(new JScrollPane(dTree), BorderLayout.CENTER); 123 dTree.expandPath(new TreePath(dRoot)); 124 dTree.setRootVisible(false); 125 dTree.setShowsRootHandles(true); 126 dTree.setScrollsOnExpand(true); 127 dTree.setExpandsSelectedPaths(true); 128 129 dTree.getSelectionModel().setSelectionMode(DefaultTreeSelectionModel.SINGLE_TREE_SELECTION); 130 // tree listener 131 dTree.addTreeSelectionListener(dListener = (TreeSelectionEvent e) -> { 132 log.debug("selection changed {} {}", dTree.isSelectionEmpty(), dTree.getSelectionPath()); 133 if (!dTree.isSelectionEmpty() && dTree.getSelectionPath() != null 134 && // can't be just a mfg, has to be at least a family 135 dTree.getSelectionPath().getPathCount() > 2 136 && // can't be a multiple decoder selection 137 dTree.getSelectionCount() < 2) { 138 // decoder selected - reset and disable loco selection 139 log.debug("Selection event with {}", dTree.getSelectionPath()); 140 if (locoBox != null) { 141 locoBox.setSelectedIndex(0); 142 } 143 go2.setEnabled(true); 144 go2.setRequestFocusEnabled(true); 145 go2.requestFocus(); 146 go2.setToolTipText(Bundle.getMessage("TipClickToOpen")); 147 } else { 148 // decoder not selected - require one 149 go2.setEnabled(false); 150 go2.setToolTipText(Bundle.getMessage("TipSelectLoco")); 151 } 152 }); 153 154// Mouselistener for doubleclick activation of programmer 155 dTree.addMouseListener(new MouseAdapter() { 156 157 @Override 158 public void mouseClicked(MouseEvent me) { 159 // Clear any status messages and ensure the tree is in single path select mode 160 if (_statusLabel != null) { 161 _statusLabel.setText(Bundle.getMessage("StateIdle")); 162 } 163 dTree.getSelectionModel().setSelectionMode(DefaultTreeSelectionModel.SINGLE_TREE_SELECTION); 164 165 if (me.getClickCount() == 2) { 166 if (go2.isEnabled() && ((TreeNode) dTree.getSelectionPath().getLastPathComponent()).isLeaf()) { 167 go2.doClick(); 168 } 169 } 170 } 171 }); 172 173 viewButtons = new JPanel(); 174 iddecoder = addDecoderIdentButton(); 175 if (iddecoder != null) { 176 viewButtons.add(iddecoder); 177 } 178 showAll = new JRadioButton(Bundle.getMessage("LabelAll")); 179 showAll.getAccessibleContext().setAccessibleName(Bundle.getMessage("LabelAll")); 180 showAll.setSelected(true); 181 showMatched = new JRadioButton(Bundle.getMessage("LabelMatched")); 182 showMatched.getAccessibleContext().setAccessibleName(Bundle.getMessage("LabelMatched")); 183 184 if (InstanceManager.getNullableDefault(GlobalProgrammerManager.class) != null 185 && InstanceManager.getDefault(GlobalProgrammerManager.class).isGlobalProgrammerAvailable()) { 186 ButtonGroup group = new ButtonGroup(); 187 group.add(showAll); 188 group.add(showMatched); 189 viewButtons.add(showAll); 190 viewButtons.add(showMatched); 191 192 pane1a.add(viewButtons, BorderLayout.SOUTH); 193 showAll.addActionListener((ActionEvent e) -> { 194 setShowMatchedOnly(false); 195 }); 196 showMatched.addActionListener((ActionEvent e) -> { 197 setShowMatchedOnly(true); 198 }); 199 } 200 201 return pane1a; 202 } 203 204 /** 205 * Sets the Loco Selection Pane to "Matched Only" {@code (true)} or "Show 206 * All" {@code (false)}. 207 * <br><br> 208 * Changes the Decoder Tree Display and the Radio Buttons. 209 * 210 * @param state the desired state 211 */ 212 public void setShowMatchedOnly(boolean state) { 213 showMatched.setSelected(state); 214 showAll.setSelected(!state); 215 dModel.activateFilter(state); 216 dModel.reload(); 217 for (TreePath path : selectedPath) { 218 log.debug("action selects path: {}", path); 219 dTree.expandPath(path); 220 dTree.addSelectionPath(path); 221 dTree.scrollPathToVisible(path); 222 } 223 } 224 225 /** 226 * Reads the available decoders and loads them into the dModel tree model. 227 */ 228 void createDecoderTypeContents() { 229 List<DecoderFile> decoders = InstanceManager.getDefault(DecoderIndexFile.class).matchingDecoderList(null, null, null, null, null, null); 230 int len = decoders.size(); 231 DecoderTreeNode mfgElement = null; 232 DecoderTreeNode familyElement = null; 233 HashMap<String, DecoderTreeNode> familyNameNode = new HashMap<>(); 234 for (int i = 0; i < len; i++) { 235 DecoderFile decoder = decoders.get(i); 236 String mfg = decoder.getMfg(); 237 String family = decoder.getFamily(); 238 String model = decoder.getModel(); 239 log.debug(" process {}/{}/{} on nodes {}/{}", mfg, family, model, mfgElement == null ? "<null>" : mfgElement.toString() + "(" + mfgElement.getChildCount() + ")", familyElement == null ? "<null>" : familyElement.toString() + "(" + familyElement.getChildCount() + ")"); 240 241 // build elements 242 if (mfgElement == null || !mfg.equals(mfgElement.toString())) { 243 // need new mfg node 244 mfgElement = new DecoderTreeNode(mfg, 245 "CV8 = " + InstanceManager.getDefault(DecoderIndexFile.class).mfgIdFromName(mfg), ""); 246 dModel.insertNodeInto(mfgElement, dRoot, dRoot.getChildCount()); 247 familyNameNode = new HashMap<>(); 248 familyElement = null; 249 } 250 String famComment = decoders.get(i).getFamilyComment(); 251 String verString = decoders.get(i).getVersionsAsString(); 252 if (familyElement == null || (!family.equals(familyElement.toString()) && !familyNameNode.containsKey(family))) { 253 // need new family node - is there only one model? Expect the 254 // family element, plus the model element, so check i+2 255 // to see if its the same, or if a single-decoder family 256 // appears to have decoder names separate from the family name 257 if ((i + 2 >= len) 258 || decoders.get(i + 2).getFamily().equals(family) 259 || !decoders.get(i + 1).getModel().equals(family)) { 260 // normal here; insert the new family element & exit 261 log.debug("normal family update case: {}", family); 262 familyElement = new DecoderTreeNode(family, 263 getHoverText(verString, famComment), 264 decoders.get(i).titleString()); 265 dModel.insertNodeInto(familyElement, mfgElement, mfgElement.getChildCount()); 266 familyNameNode.put(family, familyElement); 267 continue; 268 } else { 269 // this is short case; insert decoder entry (next) here 270 log.debug("short case, i={} family={} next {}", i, family, decoders.get(i + 1).getModel()); 271 if (i + 1 > len) { 272 log.error("Unexpected single entry for family: {}", family); 273 } 274 family = decoders.get(i + 1).getModel(); 275 familyElement = new DecoderTreeNode(family, 276 getHoverText(verString, famComment), 277 decoders.get(i).titleString()); 278 dModel.insertNodeInto(familyElement, mfgElement, mfgElement.getChildCount()); 279 familyNameNode.put(family, familyElement); 280 i = i + 1; 281 continue; 282 } 283 } 284 // insert at the decoder level, except if family name is the same 285 if (!family.equals(model)) { 286 if (familyNameNode.containsKey(family)) { 287 familyElement = familyNameNode.get(family); 288 } 289 String modelComment = decoders.get(i).getModelComment(); 290 DecoderTreeNode decoderNameNode = new DecoderTreeNode(model, 291 getHoverText(verString, modelComment), 292 decoders.get(i).titleString()); 293 decoderNameNode.setShowable(decoder.getShowable()); 294 dModel.insertNodeInto(decoderNameNode, familyElement, familyElement.getChildCount()); 295 } 296 } // end of loop over decoders 297 } 298 299 /** 300 * Provide tooltip text: Decoder comment, with CV version info, formatted as 301 * best we can. 302 * 303 * @param verString version string, typically from 304 * {@link jmri.jmrit.decoderdefn.DecoderFile#getVersionsAsString DecoderFile.getVersionsAsString()} 305 * @param comment version string, typically from 306 * {@link jmri.jmrit.decoderdefn.DecoderFile#getModelComment DecoderFile.getModelComment()} 307 * or 308 * {@link jmri.jmrit.decoderdefn.DecoderFile#getFamilyComment DecoderFile.getFamilyComment()} 309 * @return the combined formatted string. 310 */ 311 String getHoverText(String verString, String comment) { 312 if (comment == null || comment.equals("")) { 313 if (!verString.equals("")) { 314 return "CV7=" + verString; 315 } else { 316 return ""; 317 } 318 } else { 319 if (verString.equals("")) { 320 return comment; 321 } else { 322 return StringUtil.concatTextHtmlAware(comment, " (CV7=" + verString + ")"); 323 } 324 } 325 } 326 327 /** 328 * Identify loco button pressed, start the identify operation. This defines 329 * what happens when the identify is done. 330 * <br><br> 331 * This {@code @Override} method invokes 332 * {@link #resetSelections resetSelections} before starting. 333 */ 334 @Override 335 protected synchronized void startIdentifyDecoder() { 336 // start identifying a decoder 337 resetSelections(); 338 super.startIdentifyDecoder(); 339 } 340 341 /** 342 * Resets the Decoder Tree Display selections and sets the state to "Show 343 * All". 344 */ 345 public void resetSelections() { 346 Enumeration<TreeNode> e = dRoot.breadthFirstEnumeration(); 347 while (e.hasMoreElements()) { 348 ((DecoderTreeNode) e.nextElement()).setIdentified(false); 349 } 350 setShowMatchedOnly(false); 351 selectedPath = new ArrayList<>(); 352 dTree.expandPath(new TreePath(dRoot)); 353 dTree.setExpandsSelectedPaths(true); 354 int row = dTree.getRowCount() - 1; 355 while (row >= 0) { 356 dTree.collapseRow(row); 357 row--; 358 } 359 } 360 361 /** 362 * Decoder identify has matched one or more specific types. 363 * 364 * @param pList a list of decoders 365 */ 366 @Override 367 public void updateForDecoderTypeID(List<DecoderFile> pList) { 368 // find and select the first item 369 if (log.isDebugEnabled()) { 370 StringBuilder buf = new StringBuilder(); 371 for (int i = 0; i < pList.size(); i++) { 372 buf.append(pList.get(i).getModel()).append(":"); 373 } 374 log.debug("Identified {} matches: {}", pList.size() , buf ); 375 } 376 if (pList.size() <= 0) { 377 log.error("Found empty list in updateForDecoderTypeID, should not happen"); 378 return; 379 } 380 dTree.clearSelection(); 381 // If there are multiple matches change tree to allow multiple selections by the program 382 // and issue a warning instruction in the status bar 383 if (pList.size() > 1) { 384 dTree.getSelectionModel().setSelectionMode(DefaultTreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); 385 _statusLabel.setText(Bundle.getMessage("StateMultipleMatch")); 386 } else { 387 dTree.getSelectionModel().setSelectionMode(DefaultTreeSelectionModel.SINGLE_TREE_SELECTION); 388 _statusLabel.setText(Bundle.getMessage("StateIdle")); 389 } 390 391 // set everybody not identified 392 Enumeration<TreeNode> e = dRoot.breadthFirstEnumeration(); 393 while (e.hasMoreElements()) { // loop over the tree 394 DecoderTreeNode node = ((DecoderTreeNode) e.nextElement()); 395 node.setIdentified(false); 396 } 397 398 selectedPath = new ArrayList<>(); 399 400 // Find decoder nodes in tree and set selected 401 for (int i = 0; i < pList.size(); i++) { // loop over selected decoders 402 e = dRoot.breadthFirstEnumeration(); 403 404 DecoderFile f = pList.get(i); 405 String findMfg = f.getMfg(); 406 String findFamily = f.getFamily(); 407 String findModel = f.getModel(); 408 409 while (e.hasMoreElements()) { // loop over the tree & find node 410 DecoderTreeNode node = ((DecoderTreeNode) e.nextElement()); 411 // never match show=NO nodes 412 if (node.getShowable() == DecoderFile.Showable.NO) { 413 continue; 414 } 415 // convert path to comparison string 416 TreeNode[] list = node.getPath(); 417 if (list.length == 3) { 418 // check for match to mfg, model (as family) 419 if (list[1].toString().equals(findMfg) 420 && list[2].toString().equals(findModel)) { 421 log.debug("match length 3"); 422 node.setIdentified(true); 423 dModel.reload(); 424 ((DecoderTreeNode) list[1]).setIdentified(true); 425 ((DecoderTreeNode) list[2]).setIdentified(true); 426 TreePath path = new TreePath(node.getPath()); 427 selectedPath.add(path); 428 break; 429 } 430 } else if (list.length == 4) { 431 // check for match to mfg, family, model 432 if (list[1].toString().equals(findMfg) 433 && list[2].toString().equals(findFamily) 434 && list[3].toString().equals(findModel)) { 435 log.debug("match length 4"); 436 node.setIdentified(true); 437 dModel.reload(); 438 ((DecoderTreeNode) list[1]).setIdentified(true); 439 ((DecoderTreeNode) list[2]).setIdentified(true); 440 ((DecoderTreeNode) list[3]).setIdentified(true); 441 TreePath path = new TreePath(node.getPath()); 442 selectedPath.add(path); 443 break; 444 } 445 } 446 } 447 } 448 // now select and show paths in tree 449 for (TreePath path : selectedPath) { 450 dTree.addSelectionPath(path); 451 dTree.expandPath(path); 452 dTree.scrollPathToVisible(path); 453 } 454 } 455 456 /** 457 * Decoder identify has not matched specific types, but did find 458 * manufacturer match. 459 * 460 * @param pMfg Manufacturer name. This is passed to save time, as it has 461 * already been determined once. 462 * @param pMfgID Manufacturer ID number (CV8) 463 * @param pModelID Model ID number (CV7) 464 */ 465 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST", 466 justification="String also built for display in _statusLabel") 467 @Override 468 void updateForDecoderMfgID(String pMfg, int pMfgID, int pModelID) { 469 String msg = "Found mfg " + pMfgID + " (" + pMfg + ") version " + pModelID + "; no such decoder defined"; 470 log.warn(msg); 471 _statusLabel.setText(msg); 472 // find this mfg to select it 473 dTree.clearSelection(); 474 475 Enumeration<TreeNode> e = dRoot.breadthFirstEnumeration(); 476 477 ArrayList<DecoderTreeNode> selected = new ArrayList<>(); 478 selectedPath = new ArrayList<>(); 479 while (e.hasMoreElements()) { 480 DecoderTreeNode node = (DecoderTreeNode) e.nextElement(); 481 if (node.getParent() != null && node.getParent().toString().equals("Root")) { 482 if (node.toString().equals(pMfg)) { 483 TreePath path = new TreePath(node.getPath()); 484 dTree.expandPath(path); 485 dTree.addSelectionPath(path); 486 dTree.scrollPathToVisible(path); 487 selectedPath.add(path); 488 node.setIdentified(true); 489 selected.add(node); 490 } 491 } else { 492 node.setIdentified(false); 493 } 494 } 495 for (DecoderTreeNode node : selected) { 496 node.setIdentified(true); 497 498 Enumeration<TreeNode> es = dRoot.breadthFirstEnumeration(); 499 500 while (es.hasMoreElements()) { 501 ((DecoderTreeNode) es.nextElement()).setIdentified(true); 502 } 503 } 504 if (showMatched.isSelected()) { 505 dModel.activateFilter(true); 506 dModel.reload(); 507 } 508 } 509 510 /** 511 * Decoder identify did not match anything, warn and clear selection. 512 * 513 * @param pMfgID Manufacturer ID number (CV8) 514 * @param pModelID Model ID number (CV7) 515 */ 516 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST", 517 justification="String also built for display in _statusLabel") 518 @Override 519 void updateForDecoderNotID(int pMfgID, int pModelID) { 520 String msg = "Found mfg " + pMfgID + " version " + pModelID + "; no such manufacturer defined"; 521 log.warn(msg); 522 _statusLabel.setText(msg); 523 dTree.clearSelection(); 524 } 525 526 /** 527 * Set the decoder selection to a specific decoder from a selected Loco. 528 * <p> 529 * This must not trigger an update event from the Tree selection, so we 530 * remove and replace the listener. 531 * 532 * @param loco the loco name 533 */ 534 @Override 535 void setDecoderSelectionFromLoco(String loco) { 536 // if there's a valid loco entry... 537 RosterEntry locoEntry = Roster.getDefault().entryFromTitle(loco); 538 if (locoEntry == null) { 539 return; 540 } 541 dTree.removeTreeSelectionListener(dListener); 542 dTree.clearSelection(); 543 // get the decoder type, it has to be there (assumption!), 544 String modelString = locoEntry.getDecoderModel(); 545 String familyString = locoEntry.getDecoderFamily(); 546 547 // close the entire GUI (not currently done, users want left open) 548 //collapseAll(); 549 // find this one to select it 550 Enumeration<TreeNode> e = dRoot.breadthFirstEnumeration(); 551 552 while (e.hasMoreElements()) { 553 DecoderTreeNode node = (DecoderTreeNode) e.nextElement(); 554 DecoderTreeNode parentNode = (DecoderTreeNode) node.getParent(); 555 if (node.toString().equals(modelString) 556 && parentNode.toString().equals(familyString)) { 557 TreePath path = new TreePath(node.getPath()); 558 node.setIdentified(true); 559 parentNode.setIdentified(true); 560 dModel.reload(); 561 dTree.addSelectionPath(path); 562 dTree.scrollPathToVisible(path); 563 break; 564 } 565 } 566 // and restore the listener 567 dTree.addTreeSelectionListener(dListener); 568 } 569 570 /** 571 * Convert the decoder selection UI result into a name. 572 * 573 * @return The selected decoder type name, or null if none selected. 574 */ 575 @Override 576 protected String selectedDecoderType() { 577 if (!isDecoderSelected()) { 578 return null; 579 } else { 580 return ((DecoderTreeNode) dTree.getLastSelectedPathComponent()).getTitle(); 581 } 582 } 583 584 /** 585 * Has the user selected a decoder type, either manually or via a successful 586 * event? 587 * 588 * @return true if a decoder type is selected 589 */ 590 @Override 591 boolean isDecoderSelected() { 592 return !dTree.isSelectionEmpty(); 593 } 594 private final static Logger log = LoggerFactory.getLogger(CombinedLocoSelTreePane.class); 595 596 /** 597 * The following has been taken from an example given in.. 598 * http://www.java2s.com/Code/Java/Swing-Components/DecoderTreeNodeTreeExample.htm 599 * with extracts from http://www.codeguru.com/java/articles/143.shtml 600 */ 601 static class InvisibleTreeModel extends DefaultTreeModel { 602 603 protected boolean filterIsActive; 604 605 public InvisibleTreeModel(TreeNode root) { 606 this(root, false); 607 } 608 609 public InvisibleTreeModel(TreeNode root, boolean asksAllowsChildren) { 610 this(root, false, false); 611 } 612 613 public InvisibleTreeModel(TreeNode root, boolean asksAllowsChildren, 614 boolean filterIsActive) { 615 super(root, asksAllowsChildren); 616 this.filterIsActive = filterIsActive; 617 } 618 619 public void activateFilter(boolean newValue) { 620 filterIsActive = newValue; 621 } 622 623 public boolean isActivatedFilter() { 624 return filterIsActive; 625 } 626 627 @Override 628 public Object getChild(Object parent, int index) { 629 if (parent instanceof DecoderTreeNode) { 630 return ((DecoderTreeNode) parent).getChildAt(index, 631 filterIsActive); 632 } 633 return ((TreeNode) parent).getChildAt(index); 634 } 635 636 @Override 637 public int getChildCount(Object parent) { 638 if (parent instanceof DecoderTreeNode) { 639 return ((DecoderTreeNode) parent).getChildCount(filterIsActive); 640 } 641 return ((TreeNode) parent).getChildCount(); 642 } 643 } 644 645 static class DecoderTreeNode extends DefaultMutableTreeNode { 646 647 protected boolean isIdentified; 648 private String toolTipText; 649 private String title; 650 DecoderFile.Showable showable = DecoderFile.Showable.YES; // default 651 652 public DecoderTreeNode(String str, String toolTipText, String title) { 653 this(str); 654 this.toolTipText = toolTipText; 655 this.title = title; 656 } 657 658 @Override 659 public Enumeration<TreeNode> breadthFirstEnumeration() { // JDK 9 typing 660 return super.breadthFirstEnumeration(); 661 } 662 663 public String getTitle() { 664 return title; 665 } 666 667 public String getToolTipText() { 668 return toolTipText; 669 } 670 671 public DecoderTreeNode(Object userObject) { 672 this(userObject, true, false, DecoderFile.Showable.YES); 673 } 674 675 public DecoderTreeNode(Object userObject, boolean allowsChildren, 676 boolean isIdentified, DecoderFile.Showable showable) { 677 super(userObject, allowsChildren); 678 this.isIdentified = isIdentified; 679 this.showable = showable; 680 } 681 682 public TreeNode getChildAt(int index, boolean filterIsActive) { 683 if (children == null) { 684 throw new ArrayIndexOutOfBoundsException("node has no children"); 685 } 686 687 int realIndex = -1; 688 int visibleIndex = -1; 689 Enumeration<?> e = children.elements(); 690 while (e.hasMoreElements()) { 691 DecoderTreeNode node = (DecoderTreeNode) e.nextElement(); 692 if (node.isVisible(filterIsActive)) { 693 visibleIndex++; 694 } 695 realIndex++; 696 if (visibleIndex == index) { 697 return children.elementAt(realIndex); 698 } 699 } 700 701 throw new ArrayIndexOutOfBoundsException("index unmatched"); 702 //return (TreeNode)children.elementAt(index); 703 } 704 705 public int getChildCount(boolean filterIsActive) { 706 if (children == null) { 707 return 0; 708 } 709 710 int count = 0; 711 Enumeration<?> e = children.elements(); 712 while (e.hasMoreElements()) { 713 DecoderTreeNode node = (DecoderTreeNode) e.nextElement(); 714 if (node.isVisible(filterIsActive)) { 715 count++; 716 } 717 } 718 719 return count; 720 } 721 722 public void setIdentified(boolean isIdentified) { 723 this.isIdentified = isIdentified; 724 } 725 726 public void setShowable(DecoderFile.Showable showable) { 727 this.showable = showable; 728 } 729 730 public DecoderFile.Showable getShowable() { 731 return this.showable; 732 } 733 734 private boolean isVisible(boolean filterIsActive) { 735 // if there are children, are any visible? 736 if (children != null) { 737 Enumeration<?> e = children.elements(); 738 while (e.hasMoreElements()) { 739 DecoderTreeNode node = (DecoderTreeNode) e.nextElement(); 740 if (node.isVisible(filterIsActive)) { 741 return true; 742 } 743 } 744 return false; 745 } 746 // no children 747 return isIdentified || (!filterIsActive && (showable == DecoderFile.Showable.YES)); 748 } 749 } 750}