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 } 400 401 // allow private classes to fire property change events as the RGP 402 protected void firePropertyChangeAsRGP(String propertyName, Object oldValue, Object newValue) { 403 if (propertyName.equals(SELECTED_ROSTER_GROUP)) { 404 selectedRosterGroup = (String) newValue; 405 } 406 this.firePropertyChange(propertyName, oldValue, newValue); 407 } 408 409 class MenuActionListener implements ActionListener { 410 411 @Override 412 public void actionPerformed(ActionEvent e) { 413 TreePath g = new TreePath(_model.getPathToRoot(_groups)); 414 WindowInterface wi = (WindowInterface) scrollPane.getTopLevelAncestor(); 415 if (g.isDescendant(_tree.getSelectionPath())) { 416 if (e.getActionCommand().equals("export")) { 417 new FullBackupExportAction(ResourceBundle.getBundle("jmri.jmrit.roster.JmritRosterBundle").getString("Exportddd"), wi).actionPerformed(e); 418 } else if (e.getActionCommand().equals("import")) { 419 new FullBackupImportAction(ResourceBundle.getBundle("jmri.jmrit.roster.JmritRosterBundle").getString("Importddd"), wi).actionPerformed(e); 420 } else if (e.getActionCommand().equals("rename")) { 421 new RenameRosterGroupAction("Rename", wi).actionPerformed(e); 422 } else if (e.getActionCommand().equals("duplicate")) { 423 new CopyRosterGroupAction("Duplicate", wi).actionPerformed(e); 424 } else if (e.getActionCommand().equals("delete")) { 425 new DeleteRosterGroupAction("Delete", wi).actionPerformed(e); 426 } else if (e.getActionCommand().equals("newWindow") && newWindowMenuItemAction != null) { 427 newWindowMenuItemAction.actionPerformed(e); 428 } else { 429 JmriJOptionPane.showMessageDialog((JComponent) e.getSource(), 430 ResourceBundle.getBundle("jmri.jmrit.roster.JmritRosterBundle").getString("NotImplemented"), 431 ResourceBundle.getBundle("jmri.jmrit.roster.JmritRosterBundle").getString("NotImplemented"), 432 JmriJOptionPane.ERROR_MESSAGE); 433 } 434 } 435 } 436 } 437 438 class MouseAdapter extends java.awt.event.MouseAdapter { 439 440 @Override 441 public void mousePressed(MouseEvent e) { 442 if (SwingUtilities.isLeftMouseButton(e)) { 443 JTree t = (JTree) e.getSource(); 444 int closestRow = t.getClosestRowForLocation(e.getX(), e.getY()); 445 Rectangle closestRowBounds = t.getRowBounds(closestRow); 446 if (e.getY() >= closestRowBounds.getY() 447 && e.getY() < closestRowBounds.getY() + closestRowBounds.getHeight()) { 448 // test the click is after start of row renderer 449 //if (e.getX() > closestRowBounds.getX() 450 // && closestRow < t.getRowCount()) { 451 t.setSelectionRow(closestRow); 452 // setting selection to -1 removes the selection 453 //} else { 454 // t.setSelectionRow(-1); 455 } 456 } else if (e.isPopupTrigger()) { 457 showMenu(e); 458 } 459 } 460 461 @Override 462 public void mouseReleased(MouseEvent e) { 463 if (e.isPopupTrigger()) { 464 showMenu(e); 465 } 466 } 467 468 public void showMenu(MouseEvent e) { 469 JTree t = (JTree) e.getSource(); 470 int closestRow = t.getClosestRowForLocation(e.getX(), e.getY()); 471 Rectangle closestRowBounds = t.getRowBounds(closestRow); 472 if (e.getY() >= closestRowBounds.getY() 473 && e.getY() < closestRowBounds.getY() + closestRowBounds.getHeight()) { 474 t.setSelectionRow(closestRow); 475 TreePath g = new TreePath(_model.getPathToRoot(_groups)); 476 if (t.getSelectionPath().getLastPathComponent().toString().equals(Roster.ALLENTRIES)) { 477 allEntriesMenu.show((JComponent) e.getSource(), e.getX(), e.getY()); 478 } else if (g.isDescendant(t.getSelectionPath()) && !t.getSelectionPath().isDescendant(g)) { 479 groupsMenu.show((JComponent) e.getSource(), e.getX(), e.getY()); 480 } 481 } 482 } 483 } 484 485 class PropertyChangeListener implements java.beans.PropertyChangeListener { 486 487 @Override 488 public void propertyChange(java.beans.PropertyChangeEvent e) { 489 //log.debug(e.getPropertyName()); // seems a bit too much to keep active! 490 if ((e.getPropertyName().equals("RosterGroupRemoved")) 491 || (e.getPropertyName().equals("RosterGroupAdded")) 492 || (e.getPropertyName().equals("RosterGroupRenamed"))) { 493 setRosterGroups(_groups); 494 _model.reload(_groups); 495 setSelectionToGroup(selectedRosterGroup); 496 log.debug("Refreshed Roster Groups pane"); // test for panel redraw after duplication 497 } 498 } 499 } 500 501 class TransferHandler extends javax.swing.TransferHandler { 502 503 @Override 504 public boolean canImport(JComponent c, DataFlavor[] transferFlavors) { 505 for (DataFlavor flavor : transferFlavors) { 506 if (RosterEntrySelection.rosterEntryFlavor.equals(flavor)) { 507 return true; 508 } 509 } 510 return false; 511 } 512 513 @Override 514 public boolean importData(JComponent c, Transferable t) { 515 if (canImport(c, t.getTransferDataFlavors()) && (c instanceof JTree)) { 516 // getDropLocation is null unless dropping on an existing path 517 return importData(c, t, ((JTree) c).getDropLocation().getPath()); 518 } 519 return false; 520 } 521 522 public boolean importData(JComponent c, Transferable t, TreePath p) { 523 if (p != null) { 524 TreePath g = new TreePath(_model.getPathToRoot(_groups)); 525 // drag onto existing user defined group, but not onto current selection 526 if (g.isDescendant(p) && !p.isDescendant(g) 527 && ( (c instanceof JTree) && !p.isDescendant(((JTree)c).getSelectionPath())) 528 ) { 529 try { 530 ArrayList<RosterEntry> REs = RosterEntrySelection.getRosterEntries(t); 531 for (RosterEntry re : REs) { 532 re.putAttribute(Roster.getRosterGroupProperty(p.getLastPathComponent().toString()), "yes"); 533 re.updateFile(); 534 } 535 Roster.getDefault().writeRoster(); 536 setSelectedRosterGroup(p.getLastPathComponent().toString()); 537 } catch (java.awt.datatransfer.UnsupportedFlavorException | java.io.IOException | RuntimeException e) { 538 log.warn("Exception dragging RosterEntries onto RosterGroups", e); 539 } 540 } 541 } else { 542 try { 543 JmriAbstractAction a = new CreateRosterGroupAction("Create From Selection", scrollPane.getTopLevelAncestor()); 544 a.setParameter("RosterEntries", RosterEntrySelection.getRosterEntries(t)); 545 a.actionPerformed(null); 546 } catch (java.awt.datatransfer.UnsupportedFlavorException | java.io.IOException | RuntimeException e) { 547 log.warn("Exception creating RosterGroups from selection", e); 548 } 549 } 550 return false; 551 } 552 } 553 554 static public class TreeCellRenderer extends DefaultTreeCellRenderer { 555 556 } 557 558 public class TreeSelectionListener implements javax.swing.event.TreeSelectionListener { 559 560 @Override 561 public void valueChanged(TreeSelectionEvent e) { 562 TreePath g = new TreePath(_model.getPathToRoot(_groups)); 563 String oldGroup = selectedRosterGroup; 564 if (e.getNewLeadSelectionPath() == null) { 565 // if there are no Roster Groups set selection to "All Entries" 566 if (Roster.getDefault().getRosterGroupList().isEmpty()) { 567 _tree.setSelectionPath(new TreePath(_model.getPathToRoot(_groups.getFirstChild()))); 568 } 569 } else if (e.getNewLeadSelectionPath().isDescendant(g)) { 570 // reject user attempts to select the "Roster Groups" header 571 _tree.setSelectionPath(e.getOldLeadSelectionPath()); 572 } else if (g.isDescendant(e.getNewLeadSelectionPath())) { 573 selectedRosterGroup = _tree.getSelectionPath().getLastPathComponent().toString(); 574 if (Roster.ALLENTRIES.equals(selectedRosterGroup)) { 575 selectedRosterGroup = null; 576 } 577 } else { 578 selectedRosterGroup = null; 579 } 580 firePropertyChangeAsRGP(SELECTED_ROSTER_GROUP, oldGroup, selectedRosterGroup); 581 } 582 } 583 584 public class TreeWillExpandListener implements javax.swing.event.TreeWillExpandListener { 585 586 @Override 587 public void treeWillExpand(TreeExpansionEvent e) throws ExpandVetoException { 588 log.debug("Selected rows {}", _tree.getSelectionRows()); 589 } 590 591 @Override 592 public void treeWillCollapse(TreeExpansionEvent e) throws ExpandVetoException { 593 if (e.getPath().getLastPathComponent().toString().equals("Roster Groups")) { 594 throw new ExpandVetoException(e); 595 } 596 } 597 } 598 599 static public class TreeUI extends BasicTreeUI { 600 601 @Override 602 public void paint(Graphics g, JComponent c) { 603 // TODO use c.getVisibleRect to trim painting to minimum rectangle. 604 // paint the background for the tree. 605 g.setColor(UIManager.getColor("Tree.textBackground")); 606 g.fillRect(0, 0, c.getWidth(), c.getHeight()); 607 608 // TODO use c.getVisibleRect to trim painting to minimum rectangle. 609 // paint the background for the selected entry, if there is one. 610 int selectedRow = getSelectionModel().getLeadSelectionRow(); 611 if (selectedRow >= 0 && tree.isVisible(tree.getPathForRow(selectedRow))) { 612 613 Rectangle bounds = tree.getRowBounds(selectedRow); 614 615 Graphics2D selectionBackgroundGraphics = (Graphics2D) g.create(); 616 selectionBackgroundGraphics.translate(0, bounds.y); 617 selectionBackgroundGraphics.setColor(UIManager.getColor("Tree.selectionBackground")); 618 selectionBackgroundGraphics.fillRect(0, 0, c.getWidth(), bounds.height); 619 selectionBackgroundGraphics.dispose(); 620 } 621 622 super.paint(g, c); 623 } 624 625 @Override 626 protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left, int right) { 627 // do nothing - don't paint horizontal lines. 628 } 629 630 @Override 631 protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, 632 TreePath path) { 633 // do nothing - don't paint vertical lines. 634 } 635 } 636 637 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RosterGroupsPanel.class); 638}