001package jmri.jmrit.roster.swing; 002 003import java.awt.BorderLayout; 004import java.awt.Graphics; 005import java.awt.Graphics2D; 006import java.awt.GridLayout; 007import java.awt.Insets; 008import java.awt.Rectangle; 009import java.awt.datatransfer.DataFlavor; 010import java.awt.datatransfer.Transferable; 011import java.awt.event.ActionEvent; 012import java.awt.event.ActionListener; 013import java.awt.event.ItemEvent; 014import java.awt.event.ItemListener; 015import java.awt.event.MouseEvent; 016import java.util.ArrayList; 017import java.util.ResourceBundle; 018 019import javax.swing.DropMode; 020import javax.swing.ImageIcon; 021import javax.swing.JComponent; 022import javax.swing.JMenuItem; 023import javax.swing.JPanel; 024import javax.swing.JPopupMenu; 025import javax.swing.JScrollPane; 026import javax.swing.JSeparator; 027import javax.swing.JToggleButton; 028import javax.swing.JToolBar; 029import javax.swing.JTree; 030import javax.swing.ScrollPaneConstants; 031import javax.swing.SwingUtilities; 032import javax.swing.UIManager; 033import javax.swing.event.PopupMenuEvent; 034import javax.swing.event.PopupMenuListener; 035import javax.swing.event.TreeExpansionEvent; 036import javax.swing.event.TreeSelectionEvent; 037import javax.swing.plaf.basic.BasicTreeUI; 038import javax.swing.tree.DefaultMutableTreeNode; 039import javax.swing.tree.DefaultTreeCellRenderer; 040import javax.swing.tree.DefaultTreeModel; 041import javax.swing.tree.DefaultTreeSelectionModel; 042import javax.swing.tree.ExpandVetoException; 043import javax.swing.tree.TreeNode; 044import javax.swing.tree.TreePath; 045import javax.swing.tree.TreeSelectionModel; 046 047import jmri.jmrit.roster.FullBackupExportAction; 048import jmri.jmrit.roster.FullBackupImportAction; 049import jmri.jmrit.roster.Roster; 050import jmri.jmrit.roster.RosterEntry; 051import jmri.jmrit.roster.rostergroup.RosterGroupSelector; 052import jmri.util.FileUtil; 053import jmri.util.IterableEnumeration; 054import jmri.util.datatransfer.RosterEntrySelection; 055import jmri.util.swing.JmriAbstractAction; 056import jmri.util.swing.JmriJOptionPane; 057import jmri.util.swing.WindowInterface; 058 059/** 060 * A JPanel that lists Roster Groups 061 * <p> 062 * This panel contains a fairly self-contained display of Roster Groups that 063 * allows roster groups to be fully manipulated through context menus. 064 * 065 * @author Randall Wood Copyright (C) 2011 066 * @see jmri.jmrit.roster.Roster 067 */ 068public class RosterGroupsPanel extends JPanel implements RosterGroupSelector { 069 070 private static int GROUPS_MENU = 1; 071 private static int ALL_ENTRIES_MENU = 2; 072 private JScrollPane scrollPane; 073 private JTree _tree; 074 private DefaultTreeModel _model; 075 private DefaultMutableTreeNode _root; 076 private DefaultMutableTreeNode _groups; 077 //private DefaultMutableTreeNode _consists; 078 private TreeSelectionListener _TSL; 079 private String selectedRosterGroup = ""; 080 private JPopupMenu groupsMenu; 081 private JPopupMenu allEntriesMenu; 082 private JmriAbstractAction newWindowMenuItemAction = null; 083 084 /** 085 * Create a RosterGroupsPanel with default settings 086 */ 087 public RosterGroupsPanel() { 088 this(null); 089 } 090 091 /** 092 * Create a RosterGroupTreePane with the defaultRosterGroup selected. 093 * 094 * @param defaultRosterGroup the name of the default selection 095 */ 096 public RosterGroupsPanel(String defaultRosterGroup) { 097 this.scrollPane = new JScrollPane(getTree()); 098 this.scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); 099 setGroupsMenu(defaultMenu(GROUPS_MENU)); 100 setAllEntriesMenu(defaultMenu(ALL_ENTRIES_MENU)); 101 setLayout(new BorderLayout()); 102 add(scrollPane, BorderLayout.CENTER); 103 add(getButtons(), BorderLayout.SOUTH); 104 setSelectedRosterGroup(defaultRosterGroup); 105 } 106 107 /** 108 * Get the selected roster group. 109 * 110 * @return The selected roster group 111 */ 112 @Override 113 public String getSelectedRosterGroup() { 114 return selectedRosterGroup; 115 } 116 117 /** 118 * Set the selected roster group. 119 * <p> 120 * If the group is <code>null</code>, the selected roster group is set to 121 * "All Entries". 122 * 123 * @param group The name of the group to set the selection to. 124 */ 125 public final void setSelectedRosterGroup(String group) { 126 if (group == null ? selectedRosterGroup != null : !group.equals(selectedRosterGroup)) { 127 String oldGroup = selectedRosterGroup; 128 selectedRosterGroup = group; 129 setSelectionToGroup(group); 130 firePropertyChange(SELECTED_ROSTER_GROUP, oldGroup, group); 131 } 132 } 133 134 /** 135 * Is the selected roster group user or system defined. 136 * 137 * @return flag indicating current selection is a user defined roster group. 138 */ 139 public boolean isSelectionUserDefinedRosterGroup() { 140 return (selectedRosterGroup != null && !selectedRosterGroup.equals(Roster.ALLENTRIES)); 141 } 142 143 /** 144 * Set the context menu for Roster Groups 145 * 146 * @param menu The new menu for Roster Groups. 147 */ 148 public final void setGroupsMenu(JPopupMenu menu) { 149 this.groupsMenu = menu; 150 } 151 152 /** 153 * Get the context menu for Roster Groups 154 * 155 * @return The current groups menu. 156 */ 157 public JPopupMenu getGroupsMenu() { 158 return this.groupsMenu; 159 } 160 161 /** 162 * Set the context menu for the "All Entries" roster group 163 * 164 * @param menu The new menu for All Entries. 165 */ 166 public final void setAllEntriesMenu(JPopupMenu menu) { 167 this.allEntriesMenu = menu; 168 } 169 170 /** 171 * Get the context menu for "All Entries" 172 * 173 * @return The menu for All Entries. 174 */ 175 public JPopupMenu getAllEntriesMenu() { 176 return this.allEntriesMenu; 177 } 178 179 /** 180 * Set an action that the menu item "Open in New Window" will trigger. 181 * <p> 182 * Set a {@link JmriAbstractAction} that the "Open in New Window" menu item 183 * will trigger. <code>null</code> will remove the "Open in New Window" menu 184 * item from context menus. The "Open in New Window" menu item will be added 185 * or removed from the menu as appropriate. 186 * <p> 187 * If the action you pass has access to the RosterGroupPanel, it may call 188 * RosterGroupPanel.getSelectedRosterGroup to determine which group to open 189 * in the new window, otherwise it must accept a String defining the group 190 * in JmriAbstractAction.setParameter(String, String). 191 * 192 * @param action An action that can work on the current selection 193 */ 194 public void setNewWindowMenuAction(JmriAbstractAction action) { 195 if (action != null) { 196 if (newWindowMenuItemAction == null) { 197 MenuActionListener ml = new MenuActionListener(); 198 JMenuItem mi = new JMenuItem(ResourceBundle.getBundle("jmri.jmrit.Bundle").getString("MenuOpenInNewWindow")); 199 mi.addActionListener(ml); 200 mi.setActionCommand("newWindow"); 201 groupsMenu.insert(mi, 0); 202 groupsMenu.insert(new JSeparator(), 1); 203 // create the menu item twice because a menu item can only 204 // be attached to a single menu 205 mi = new JMenuItem(ResourceBundle.getBundle("jmri.jmrit.Bundle").getString("MenuOpenInNewWindow")); 206 mi.addActionListener(ml); 207 mi.setActionCommand("newWindow"); 208 allEntriesMenu.insert(mi, 0); 209 allEntriesMenu.insert(new JSeparator(), 1); 210 } 211 newWindowMenuItemAction = action; 212 } else if (newWindowMenuItemAction != null) { 213 groupsMenu.remove(0); 214 groupsMenu.remove(0); 215 allEntriesMenu.remove(0); 216 allEntriesMenu.remove(0); 217 newWindowMenuItemAction = null; 218 } 219 groupsMenu.revalidate(); 220 allEntriesMenu.revalidate(); 221 } 222 223 /** 224 * The action triggered by the "Open in New Window" menu item. 225 * 226 * @return A JmriAbstractAction or null 227 */ 228 public JmriAbstractAction getNewWindowMenuAction() { 229 return newWindowMenuItemAction; 230 } 231 232 private void setSelectionToGroup(String group) { 233 _tree.removeTreeSelectionListener(_TSL); 234 if (group == null || group.equals(Roster.ALLENTRIES) || group.equals("")) { 235 _tree.setSelectionPath(new TreePath(_model.getPathToRoot(_groups.getFirstChild()))); 236 } else { 237 for (TreeNode n : new IterableEnumeration<TreeNode>(_groups.children())) { 238 if (n.toString().equals(group)) { 239 _tree.setSelectionPath(new TreePath(_model.getPathToRoot(n))); 240 } 241 } 242 } 243 _tree.addTreeSelectionListener(_TSL); 244 } 245 246 private JToolBar getButtons() { 247 JToolBar controls = new JToolBar(); 248 controls.setLayout(new GridLayout(1, 0, 0, 0)); 249 controls.setFloatable(false); 250 final JToggleButton addGroupBtn = new JToggleButton(new ImageIcon(FileUtil.findURL("resources/icons/misc/gui3/Add.png")), false); 251 final JToggleButton actGroupBtn = new JToggleButton(new ImageIcon(FileUtil.findURL("resources/icons/misc/gui3/Action.png")), false); 252 addGroupBtn.addActionListener(new ActionListener() { 253 254 @Override 255 public void actionPerformed(ActionEvent e) { 256 new CreateRosterGroupAction("", scrollPane.getTopLevelAncestor()).actionPerformed(e); 257 addGroupBtn.setSelected(false); 258 } 259 }); 260 actGroupBtn.addItemListener(new ItemListener() { 261 262 @Override 263 public void itemStateChanged(ItemEvent ie) { 264 if (ie.getStateChange() == ItemEvent.SELECTED) { 265 TreePath g = new TreePath(_model.getPathToRoot(_groups)); 266 if (_tree.getSelectionPath() != null) { 267 if (_tree.getSelectionPath().getLastPathComponent().toString().equals(Roster.ALLENTRIES)) { 268 allEntriesMenu.show((JComponent) ie.getSource(), actGroupBtn.getX() - actGroupBtn.getWidth(), actGroupBtn.getY() - allEntriesMenu.getPreferredSize().height); 269 } else if (g.isDescendant(_tree.getSelectionPath()) && !_tree.getSelectionPath().isDescendant(g)) { 270 groupsMenu.show((JComponent) ie.getSource(), actGroupBtn.getX() - actGroupBtn.getWidth(), actGroupBtn.getY() - groupsMenu.getPreferredSize().height); 271 } 272 } 273 } 274 } 275 }); 276 PopupMenuListener PML = new PopupMenuListener() { 277 278 @Override 279 public void popupMenuWillBecomeVisible(PopupMenuEvent pme) { 280 // do nothing 281 } 282 283 @Override 284 public void popupMenuWillBecomeInvisible(PopupMenuEvent pme) { 285 actGroupBtn.setSelected(false); 286 } 287 288 @Override 289 public void popupMenuCanceled(PopupMenuEvent pme) { 290 actGroupBtn.setSelected(false); 291 } 292 }; 293 allEntriesMenu.addPopupMenuListener(PML); 294 groupsMenu.addPopupMenuListener(PML); 295 controls.add(addGroupBtn); 296 controls.add(actGroupBtn); 297 return controls; 298 } 299 300 /** 301 * Get a JScrollPane containing the JTree that does not display horizontal 302 * scrollbars. 303 * 304 * @return The internal JScrollPane 305 */ 306 public JScrollPane getScrollPane() { 307 return this.scrollPane; 308 } 309 310 /** 311 * Get the JTree containing the roster groups. 312 * 313 * @return The internal JTree 314 */ 315 public final JTree getTree() { 316 if (_tree == null) { 317 TreeSelectionModel sm = new DefaultTreeSelectionModel(); 318 DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer(); 319 sm.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); 320 renderer.setLeafIcon(null); 321 renderer.setClosedIcon(null); 322 renderer.setOpenIcon(null); 323 _tree = new JTree(getModel()); 324 _tree.setUI(new TreeUI()); 325 _tree.putClientProperty("JTree.lineStyle", "None"); 326 _tree.setRootVisible(false); 327 _tree.expandRow(0); 328 _tree.setSelectionModel(sm); 329 _tree.setCellRenderer(renderer); 330 _tree.addTreeWillExpandListener(new TreeWillExpandListener()); 331 _TSL = new TreeSelectionListener(); 332 _tree.addTreeSelectionListener(_TSL); 333 _tree.setDragEnabled(true); 334 _tree.setDropMode(DropMode.ON); 335 _tree.setTransferHandler(new TransferHandler()); 336 _tree.addMouseListener(new MouseAdapter()); 337 setSelectionToGroup(selectedRosterGroup); 338 Roster.getDefault().addPropertyChangeListener(new PropertyChangeListener()); 339 } 340 return _tree; 341 } 342 343 private DefaultTreeModel getModel() { 344 if (_model == null) { 345 _model = new DefaultTreeModel(getRoot()); 346 } 347 return _model; 348 } 349 350 private DefaultMutableTreeNode getRoot() { 351 if (_root == null) { 352 _root = new DefaultMutableTreeNode(); 353 _groups = new DefaultMutableTreeNode(Bundle.getMessage("MenuRosterGroups")); // "Roster Groups" 354 _root.add(_groups); 355 setRosterGroups(_groups); 356 // once consists can be displayed in the DP3 table, add them here 357 //_consists = new DefaultMutableTreeNode("Consists"); 358 //setConsists(_consists); 359 //_root.add(_consists); 360 } 361 return _root; 362 } 363 364 private JPopupMenu defaultMenu(int menu) { 365 JPopupMenu pm = new JPopupMenu(); 366 MenuActionListener ml = new MenuActionListener(); 367 JMenuItem mi = new JMenuItem(Bundle.getMessage("Exportddd")); 368 mi.addActionListener(ml); 369 mi.setActionCommand("export"); 370 pm.add(mi); 371 mi = new JMenuItem(Bundle.getMessage("Importddd")); 372 mi.addActionListener(ml); 373 mi.setActionCommand("import"); 374 pm.add(mi); 375 if (menu == GROUPS_MENU) { 376 pm.addSeparator(); 377 mi = new JMenuItem(Bundle.getMessage("Renameddd")); // key is in jmri.NamedBeanBundle 378 mi.addActionListener(ml); 379 mi.setActionCommand("rename"); 380 pm.add(mi); 381 mi = new JMenuItem(Bundle.getMessage("Duplicateddd")); 382 mi.addActionListener(ml); 383 mi.setActionCommand("duplicate"); 384 pm.add(mi); 385 mi = new JMenuItem(Bundle.getMessage("ButtonDelete")); 386 mi.addActionListener(ml); 387 mi.setActionCommand("delete"); 388 pm.add(mi); 389 } 390 return pm; 391 } 392 393 private void setRosterGroups(DefaultMutableTreeNode root) { 394 root.removeAllChildren(); 395 root.add(new DefaultMutableTreeNode(Roster.ALLENTRIES)); 396 for (String g : Roster.getDefault().getRosterGroupList()) { 397 root.add(new DefaultMutableTreeNode(g)); 398 } 399 root.add(new DefaultMutableTreeNode(Roster.NOGROUP)); 400 } 401 402 // allow private classes to fire property change events as the RGP 403 protected void firePropertyChangeAsRGP(String propertyName, Object oldValue, Object newValue) { 404 if (propertyName.equals(SELECTED_ROSTER_GROUP)) { 405 selectedRosterGroup = (String) newValue; 406 } 407 this.firePropertyChange(propertyName, oldValue, newValue); 408 } 409 410 class MenuActionListener implements ActionListener { 411 412 @Override 413 public void actionPerformed(ActionEvent e) { 414 TreePath g = new TreePath(_model.getPathToRoot(_groups)); 415 WindowInterface wi = (WindowInterface) scrollPane.getTopLevelAncestor(); 416 if (g.isDescendant(_tree.getSelectionPath())) { 417 if (e.getActionCommand().equals("export")) { 418 new FullBackupExportAction(ResourceBundle.getBundle("jmri.jmrit.roster.JmritRosterBundle").getString("Exportddd"), wi).actionPerformed(e); 419 } else if (e.getActionCommand().equals("import")) { 420 new FullBackupImportAction(ResourceBundle.getBundle("jmri.jmrit.roster.JmritRosterBundle").getString("Importddd"), wi).actionPerformed(e); 421 } else if (e.getActionCommand().equals("rename")) { 422 new RenameRosterGroupAction("Rename", wi).actionPerformed(e); 423 } else if (e.getActionCommand().equals("duplicate")) { 424 new CopyRosterGroupAction("Duplicate", wi).actionPerformed(e); 425 } else if (e.getActionCommand().equals("delete")) { 426 new DeleteRosterGroupAction("Delete", wi).actionPerformed(e); 427 } else if (e.getActionCommand().equals("newWindow") && newWindowMenuItemAction != null) { 428 newWindowMenuItemAction.actionPerformed(e); 429 } else { 430 JmriJOptionPane.showMessageDialog((JComponent) e.getSource(), 431 ResourceBundle.getBundle("jmri.jmrit.roster.JmritRosterBundle").getString("NotImplemented"), 432 ResourceBundle.getBundle("jmri.jmrit.roster.JmritRosterBundle").getString("NotImplemented"), 433 JmriJOptionPane.ERROR_MESSAGE); 434 } 435 } 436 } 437 } 438 439 class MouseAdapter extends java.awt.event.MouseAdapter { 440 441 @Override 442 public void mousePressed(MouseEvent e) { 443 if (SwingUtilities.isLeftMouseButton(e)) { 444 JTree t = (JTree) e.getSource(); 445 int closestRow = t.getClosestRowForLocation(e.getX(), e.getY()); 446 Rectangle closestRowBounds = t.getRowBounds(closestRow); 447 if (e.getY() >= closestRowBounds.getY() 448 && e.getY() < closestRowBounds.getY() + closestRowBounds.getHeight()) { 449 // test the click is after start of row renderer 450 //if (e.getX() > closestRowBounds.getX() 451 // && closestRow < t.getRowCount()) { 452 t.setSelectionRow(closestRow); 453 // setting selection to -1 removes the selection 454 //} else { 455 // t.setSelectionRow(-1); 456 } 457 } else if (e.isPopupTrigger()) { 458 showMenu(e); 459 } 460 } 461 462 @Override 463 public void mouseReleased(MouseEvent e) { 464 if (e.isPopupTrigger()) { 465 showMenu(e); 466 } 467 } 468 469 public void showMenu(MouseEvent e) { 470 JTree t = (JTree) e.getSource(); 471 int closestRow = t.getClosestRowForLocation(e.getX(), e.getY()); 472 Rectangle closestRowBounds = t.getRowBounds(closestRow); 473 if (e.getY() >= closestRowBounds.getY() 474 && e.getY() < closestRowBounds.getY() + closestRowBounds.getHeight()) { 475 t.setSelectionRow(closestRow); 476 TreePath g = new TreePath(_model.getPathToRoot(_groups)); 477 if (t.getSelectionPath().getLastPathComponent().toString().equals(Roster.ALLENTRIES)) { 478 allEntriesMenu.show((JComponent) e.getSource(), e.getX(), e.getY()); 479 } else if (g.isDescendant(t.getSelectionPath()) && !t.getSelectionPath().isDescendant(g)) { 480 groupsMenu.show((JComponent) e.getSource(), e.getX(), e.getY()); 481 } 482 } 483 } 484 } 485 486 class PropertyChangeListener implements java.beans.PropertyChangeListener { 487 488 @Override 489 public void propertyChange(java.beans.PropertyChangeEvent e) { 490 //log.debug(e.getPropertyName()); // seems a bit too much to keep active! 491 if ((e.getPropertyName().equals("RosterGroupRemoved")) 492 || (e.getPropertyName().equals("RosterGroupAdded")) 493 || (e.getPropertyName().equals("RosterGroupRenamed"))) { 494 setRosterGroups(_groups); 495 _model.reload(_groups); 496 setSelectionToGroup(selectedRosterGroup); 497 log.debug("Refreshed Roster Groups pane"); // test for panel redraw after duplication 498 } 499 } 500 } 501 502 class TransferHandler extends javax.swing.TransferHandler { 503 504 @Override 505 public boolean canImport(JComponent c, DataFlavor[] transferFlavors) { 506 for (DataFlavor flavor : transferFlavors) { 507 if (RosterEntrySelection.rosterEntryFlavor.equals(flavor)) { 508 return true; 509 } 510 } 511 return false; 512 } 513 514 @Override 515 public boolean importData(JComponent c, Transferable t) { 516 if (canImport(c, t.getTransferDataFlavors()) && (c instanceof JTree)) { 517 // getDropLocation is null unless dropping on an existing path 518 return importData(c, t, ((JTree) c).getDropLocation().getPath()); 519 } 520 return false; 521 } 522 523 public boolean importData(JComponent c, Transferable t, TreePath p) { 524 if (p != null) { 525 TreePath g = new TreePath(_model.getPathToRoot(_groups)); 526 // drag onto existing user defined group, but not onto current selection 527 if (g.isDescendant(p) && !p.isDescendant(g) 528 && ( (c instanceof JTree) && !p.isDescendant(((JTree)c).getSelectionPath())) 529 ) { 530 try { 531 ArrayList<RosterEntry> REs = RosterEntrySelection.getRosterEntries(t); 532 for (RosterEntry re : REs) { 533 re.putAttribute(Roster.getRosterGroupProperty(p.getLastPathComponent().toString()), "yes"); 534 re.updateFile(); 535 } 536 Roster.getDefault().writeRoster(); 537 setSelectedRosterGroup(p.getLastPathComponent().toString()); 538 } catch (java.awt.datatransfer.UnsupportedFlavorException | java.io.IOException | RuntimeException e) { 539 log.warn("Exception dragging RosterEntries onto RosterGroups", e); 540 } 541 } 542 } else { 543 try { 544 JmriAbstractAction a = new CreateRosterGroupAction("Create From Selection", scrollPane.getTopLevelAncestor()); 545 a.setParameter("RosterEntries", RosterEntrySelection.getRosterEntries(t)); 546 a.actionPerformed(null); 547 } catch (java.awt.datatransfer.UnsupportedFlavorException | java.io.IOException | RuntimeException e) { 548 log.warn("Exception creating RosterGroups from selection", e); 549 } 550 } 551 return false; 552 } 553 } 554 555 static public class TreeCellRenderer extends DefaultTreeCellRenderer { 556 557 } 558 559 public class TreeSelectionListener implements javax.swing.event.TreeSelectionListener { 560 561 @Override 562 public void valueChanged(TreeSelectionEvent e) { 563 TreePath g = new TreePath(_model.getPathToRoot(_groups)); 564 String oldGroup = selectedRosterGroup; 565 if (e.getNewLeadSelectionPath() == null) { 566 // if there are no Roster Groups set selection to "All Entries" 567 if (Roster.getDefault().getRosterGroupList().isEmpty()) { 568 _tree.setSelectionPath(new TreePath(_model.getPathToRoot(_groups.getFirstChild()))); 569 } 570 } else if (e.getNewLeadSelectionPath().isDescendant(g)) { 571 // reject user attempts to select the "Roster Groups" header 572 _tree.setSelectionPath(e.getOldLeadSelectionPath()); 573 } else if (g.isDescendant(e.getNewLeadSelectionPath())) { 574 selectedRosterGroup = _tree.getSelectionPath().getLastPathComponent().toString(); 575 if (Roster.ALLENTRIES.equals(selectedRosterGroup)) { 576 selectedRosterGroup = null; 577 } 578 } else { 579 selectedRosterGroup = null; 580 } 581 firePropertyChangeAsRGP(SELECTED_ROSTER_GROUP, oldGroup, selectedRosterGroup); 582 } 583 } 584 585 public class TreeWillExpandListener implements javax.swing.event.TreeWillExpandListener { 586 587 @Override 588 public void treeWillExpand(TreeExpansionEvent e) throws ExpandVetoException { 589 log.debug("Selected rows {}", _tree.getSelectionRows()); 590 } 591 592 @Override 593 public void treeWillCollapse(TreeExpansionEvent e) throws ExpandVetoException { 594 if (e.getPath().getLastPathComponent().toString().equals("Roster Groups")) { 595 throw new ExpandVetoException(e); 596 } 597 } 598 } 599 600 static public class TreeUI extends BasicTreeUI { 601 602 @Override 603 public void paint(Graphics g, JComponent c) { 604 // TODO use c.getVisibleRect to trim painting to minimum rectangle. 605 // paint the background for the tree. 606 g.setColor(UIManager.getColor("Tree.textBackground")); 607 g.fillRect(0, 0, c.getWidth(), c.getHeight()); 608 609 // TODO use c.getVisibleRect to trim painting to minimum rectangle. 610 // paint the background for the selected entry, if there is one. 611 int selectedRow = getSelectionModel().getLeadSelectionRow(); 612 if (selectedRow >= 0 && tree.isVisible(tree.getPathForRow(selectedRow))) { 613 614 Rectangle bounds = tree.getRowBounds(selectedRow); 615 616 Graphics2D selectionBackgroundGraphics = (Graphics2D) g.create(); 617 selectionBackgroundGraphics.translate(0, bounds.y); 618 selectionBackgroundGraphics.setColor(UIManager.getColor("Tree.selectionBackground")); 619 selectionBackgroundGraphics.fillRect(0, 0, c.getWidth(), bounds.height); 620 selectionBackgroundGraphics.dispose(); 621 } 622 623 super.paint(g, c); 624 } 625 626 @Override 627 protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left, int right) { 628 // do nothing - don't paint horizontal lines. 629 } 630 631 @Override 632 protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, 633 TreePath path) { 634 // do nothing - don't paint vertical lines. 635 } 636 } 637 638 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RosterGroupsPanel.class); 639}