/* * @(#)SynthTreeUI.java 1.23 04/05/27 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package javax.swing.plaf.synth; import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import java.awt.datatransfer.*; import java.awt.dnd.*; import java.beans.*; import java.io.*; import java.util.*; import javax.swing.plaf.*; import javax.swing.plaf.basic.*; import javax.swing.tree.*; import javax.swing.text.Position; import sun.swing.plaf.synth.*; /** * Skinnable TreeUI. * * @version 1.23, 05/27/04 * @author Scott Violet */ class SynthTreeUI extends BasicTreeUI implements PropertyChangeListener, SynthUI { private SynthStyle style; private SynthStyle cellStyle; private SynthContext paintContext; private boolean drawHorizontalLines; private boolean drawVerticalLines; private int leadRow; private int padding; private boolean useTreeColors; private Icon expandedIconWrapper; public static ComponentUI createUI(JComponent x) { return new SynthTreeUI(); } SynthTreeUI() { expandedIconWrapper = new ExpandedIconWrapper(); } public Icon getExpandedIcon() { return expandedIconWrapper; } protected void installDefaults() { updateStyle(tree); } private void updateStyle(JTree tree) { SynthContext context = getContext(tree, ENABLED); SynthStyle oldStyle = style; style = SynthLookAndFeel.updateStyle(context, this); if (style != oldStyle) { Object value; setExpandedIcon(style.getIcon(context, "Tree.expandedIcon")); setCollapsedIcon(style.getIcon(context, "Tree.collapsedIcon")); setLeftChildIndent(style.getInt(context, "Tree.leftChildIndent", 0)); setRightChildIndent(style.getInt(context, "Tree.rightChildIndent", 0)); drawHorizontalLines = style.getBoolean( context, "Tree.drawHorizontalLines",true); drawVerticalLines = style.getBoolean( context, "Tree.drawVerticalLines", true); value = style.get(context, "Tree.rowHeight"); if (value != null) { LookAndFeel.installProperty(tree, "rowHeight", value); } value = style.get(context, "Tree.scrollsOnExpand"); LookAndFeel.installProperty(tree, "scrollsOnExpand", value != null? value : Boolean.TRUE); padding = style.getInt(context, "Tree.padding", 0); largeModel = (tree.isLargeModel() && tree.getRowHeight() > 0); useTreeColors = style.getBoolean(context, "Tree.rendererUseTreeColors", true); Boolean showsRootHandles = style.getBoolean( context, "Tree.showsRootHandles", Boolean.TRUE); LookAndFeel.installProperty( tree, JTree.SHOWS_ROOT_HANDLES_PROPERTY, showsRootHandles); if (oldStyle != null) { uninstallKeyboardActions(); installKeyboardActions(); } } context.dispose(); context = getContext(tree, Region.TREE_CELL, ENABLED); cellStyle = SynthLookAndFeel.updateStyle(context, this); context.dispose(); } protected void installListeners() { super.installListeners(); tree.addPropertyChangeListener(this); } public SynthContext getContext(JComponent c) { return getContext(c, getComponentState(c)); } private SynthContext getContext(JComponent c, int state) { return SynthContext.getContext(SynthContext.class, c, SynthLookAndFeel.getRegion(c), style, state); } private Region getRegion(JTree c) { return SynthLookAndFeel.getRegion(c); } private int getComponentState(JComponent c) { return SynthLookAndFeel.getComponentState(c); } private SynthContext getContext(JComponent c, Region region) { return getContext(c, region, getComponentState(c, region)); } private SynthContext getContext(JComponent c, Region region, int state) { return SynthContext.getContext(SynthContext.class, c, region, cellStyle, state); } private int getComponentState(JComponent c, Region region) { // Always treat the cell as selected, will be adjusted appropriately // when painted. return ENABLED | SELECTED; } protected TreeCellEditor createDefaultCellEditor() { TreeCellRenderer renderer = tree.getCellRenderer(); DefaultTreeCellEditor editor; if(renderer != null && (renderer instanceof DefaultTreeCellRenderer)) { editor = new SynthTreeCellEditor(tree, (DefaultTreeCellRenderer) renderer); } else { editor = new SynthTreeCellEditor(tree, null); } return editor; } protected TreeCellRenderer createDefaultCellRenderer() { return new SynthTreeCellRenderer(); } protected void uninstallDefaults() { SynthContext context = getContext(tree, ENABLED); style.uninstallDefaults(context); context.dispose(); style = null; context = getContext(tree, Region.TREE_CELL, ENABLED); cellStyle.uninstallDefaults(context); context.dispose(); cellStyle = null; if (tree.getTransferHandler() instanceof UIResource) { tree.setTransferHandler(null); } } protected void uninstallListeners() { super.uninstallListeners(); tree.removePropertyChangeListener(this); } public void update(Graphics g, JComponent c) { SynthContext context = getContext(c); SynthLookAndFeel.update(context, g); context.getPainter().paintTreeBackground(context, g, 0, 0, c.getWidth(), c.getHeight()); paint(context, g); context.dispose(); } public void paintBorder(SynthContext context, Graphics g, int x, int y, int w, int h) { context.getPainter().paintTreeBorder(context, g, x, y, w, h); } public void paint(Graphics g, JComponent c) { SynthContext context = getContext(c); paint(context, g); context.dispose(); } private void adjustCellBounds(JTree tree, Rectangle bounds, Insets i){ if (bounds != null) { if (i == null) { i = SynthLookAndFeel.EMPTY_UIRESOURCE_INSETS; } bounds.x += i.left; bounds.y += i.top; } } private void updateLeadRow() { leadRow = getRowForPath(tree, tree.getLeadSelectionPath()); } protected void paint(SynthContext context, Graphics g) { paintContext = context; updateLeadRow(); Rectangle paintBounds = g.getClipBounds(); Insets insets = tree.getInsets(); TreePath initialPath = getClosestPathForLocation(tree, 0, paintBounds.y); Enumeration paintingEnumerator = treeState.getVisiblePathsFrom (initialPath); int row = treeState.getRowForPath(initialPath); int endY = paintBounds.y + paintBounds.height; TreeModel treeModel = tree.getModel(); SynthContext cellContext = getContext(tree, Region.TREE_CELL); drawingCache.clear(); setHashColor(context.getStyle().getColor(context, ColorType.FOREGROUND)); if (paintingEnumerator != null) { // First pass, draw the rows boolean done = false; boolean isExpanded; boolean hasBeenExpanded; boolean isLeaf; Rectangle boundsBuffer = new Rectangle(); Rectangle rowBounds = new Rectangle(0, 0, tree.getWidth(),0); Rectangle bounds; TreePath path; TreeCellRenderer renderer = tree.getCellRenderer(); DefaultTreeCellRenderer dtcr = (renderer instanceof DefaultTreeCellRenderer) ? (DefaultTreeCellRenderer) renderer : null; configureRenderer(cellContext); while (!done && paintingEnumerator.hasMoreElements()) { path = (TreePath)paintingEnumerator.nextElement(); if (path != null) { isLeaf = treeModel.isLeaf(path.getLastPathComponent()); if (isLeaf) { isExpanded = hasBeenExpanded = false; } else { isExpanded = treeState.getExpandedState(path); hasBeenExpanded = tree.hasBeenExpanded(path); } bounds = treeState.getBounds(path, boundsBuffer); adjustCellBounds(tree, bounds, insets); rowBounds.y = bounds.y; rowBounds.height = bounds.height; paintRow(renderer, dtcr, context, cellContext, g, paintBounds, insets, bounds, rowBounds, path, row, isExpanded, hasBeenExpanded, isLeaf); if ((bounds.y + bounds.height) >= endY) { done = true; } } else { done = true; } row++; } // Draw the connecting lines and controls. // Find each parent and have them draw a line to their last child boolean rootVisible = tree.isRootVisible(); TreePath parentPath = initialPath; parentPath = parentPath.getParentPath(); while (parentPath != null) { paintVerticalPartOfLeg(g, paintBounds, insets, parentPath); drawingCache.put(parentPath, Boolean.TRUE); parentPath = parentPath.getParentPath(); } done = false; paintingEnumerator = treeState.getVisiblePathsFrom(initialPath); while (!done && paintingEnumerator.hasMoreElements()) { path = (TreePath)paintingEnumerator.nextElement(); if (path != null) { isLeaf = treeModel.isLeaf(path.getLastPathComponent()); if (isLeaf) { isExpanded = hasBeenExpanded = false; } else { isExpanded = treeState.getExpandedState(path); hasBeenExpanded = tree.hasBeenExpanded(path); } bounds = treeState.getBounds(path, boundsBuffer); adjustCellBounds(tree, bounds, insets); // See if the vertical line to the parent has been drawn. parentPath = path.getParentPath(); if (parentPath != null) { if (drawingCache.get(parentPath) == null) { paintVerticalPartOfLeg(g, paintBounds, insets, parentPath); drawingCache.put(parentPath, Boolean.TRUE); } paintHorizontalPartOfLeg(g, paintBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf); } else if (rootVisible && row == 0) { paintHorizontalPartOfLeg(g, paintBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf); } if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf)) { paintExpandControl(g, paintBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded,isLeaf); } if ((bounds.y + bounds.height) >= endY) { done = true; } } else { done = true; } row++; } } cellContext.dispose(); // Empty out the renderer pane, allowing renderers to be gc'ed. rendererPane.removeAll(); } private void configureRenderer(SynthContext context) { TreeCellRenderer renderer = tree.getCellRenderer(); if (renderer instanceof DefaultTreeCellRenderer) { DefaultTreeCellRenderer r = (DefaultTreeCellRenderer)renderer; SynthStyle style = context.getStyle(); context.setComponentState(ENABLED | SELECTED); Color color = r.getTextSelectionColor(); if (color == null || (color instanceof UIResource)) { r.setTextSelectionColor(style.getColor( context, ColorType.TEXT_FOREGROUND)); } color = r.getBackgroundSelectionColor(); if (color == null || (color instanceof UIResource)) { r.setBackgroundSelectionColor(style.getColor( context, ColorType.TEXT_BACKGROUND)); } context.setComponentState(ENABLED); color = r.getTextNonSelectionColor(); if (color == null || color instanceof UIResource) { r.setTextNonSelectionColor(style.getColor( context, ColorType.TEXT_FOREGROUND)); } color = r.getBackgroundNonSelectionColor(); if (color instanceof UIResource) { r.setBackgroundNonSelectionColor(style.getColor( context, ColorType.TEXT_BACKGROUND)); } } } protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) { if (drawHorizontalLines) { super.paintHorizontalPartOfLeg(g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf); } } protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left, int right) { paintContext.getStyle().getGraphicsUtils(paintContext).drawLine( paintContext, "Tree.horizontalLine", g, left, y, right, y); } protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, TreePath path) { if (drawVerticalLines) { super.paintVerticalPartOfLeg(g, clipBounds, insets, path); } } protected void paintVerticalLine(Graphics g, JComponent c, int x, int top, int bottom) { paintContext.getStyle().getGraphicsUtils(paintContext).drawLine( paintContext, "Tree.verticalLine", g, x, top, x, bottom); } protected void paintRow(TreeCellRenderer renderer, DefaultTreeCellRenderer dtcr, SynthContext treeContext, SynthContext cellContext, Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, Rectangle rowBounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) { // Don't paint the renderer if editing this row. boolean selected = tree.isRowSelected(row); if (selected) { cellContext.setComponentState(ENABLED | SELECTED); } else { cellContext.setComponentState(ENABLED); } if (dtcr != null && (dtcr.getBorderSelectionColor() instanceof UIResource)) { dtcr.setBorderSelectionColor(style.getColor( cellContext, ColorType.FOCUS)); } SynthLookAndFeel.updateSubregion(cellContext, g, rowBounds); cellContext.getPainter().paintTreeCellBackground(cellContext, g, rowBounds.x, rowBounds.y, rowBounds.width, rowBounds.height); cellContext.getPainter().paintTreeCellBorder(cellContext, g, rowBounds.x, rowBounds.y, rowBounds.width, rowBounds.height); if (editingComponent != null && editingRow == row) { return; } int leadIndex; if (tree.hasFocus()) { leadIndex = leadRow; } else { leadIndex = -1; } Component component = renderer.getTreeCellRendererComponent( tree, path.getLastPathComponent(), selected, isExpanded, isLeaf, row, (leadIndex == row)); rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y, bounds.width, bounds.height, true); } protected void drawCentered(Component c, Graphics graphics, Icon icon, int x, int y) { int w = SynthIcon.getIconWidth(icon, paintContext); int h = SynthIcon.getIconHeight(icon, paintContext); SynthIcon.paintIcon(icon, paintContext, graphics, x - w/2, y - h/2, w, h); } public void propertyChange(PropertyChangeEvent event) { if (SynthLookAndFeel.shouldUpdateStyle(event)) { updateStyle((JTree)event.getSource()); } } protected int getRowX(int row, int depth) { return super.getRowX(row, depth) + padding; } private class SynthTreeCellRenderer extends DefaultTreeCellRenderer implements UIResource { SynthTreeCellRenderer() { } public String getName() { return "Tree.cellRenderer"; } public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { if (!useTreeColors && (sel || hasFocus)) { SynthLookAndFeel.setSelectedUI((SynthLabelUI)SynthLookAndFeel. getUIOfType(getUI(), SynthLabelUI.class), sel, hasFocus, tree.isEnabled()); } else { SynthLookAndFeel.resetSelectedUI(); } return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); } public void paint(Graphics g) { paintComponent(g); if (hasFocus) { SynthContext context = getContext(tree, Region.TREE_CELL); if (context.getStyle() == null) { assert false: "SynthTreeCellRenderer is being used " + "outside of UI that created it"; return; } int imageOffset = 0; Icon currentI = getIcon(); if(currentI != null && getText() != null) { imageOffset = currentI.getIconWidth() + Math.max(0, getIconTextGap() - 1); } if (selected) { context.setComponentState(ENABLED | SELECTED); } else { context.setComponentState(ENABLED); } if(getComponentOrientation().isLeftToRight()) { context.getPainter().paintTreeCellFocus(context, g, imageOffset, 0, getWidth() - imageOffset, getHeight()); } else { context.getPainter().paintTreeCellFocus(context, g, 0, 0, getWidth() - imageOffset, getHeight()); } context.dispose(); } SynthLookAndFeel.resetSelectedUI(); } } private static class SynthTreeCellEditor extends DefaultTreeCellEditor { public SynthTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer) { super(tree, renderer); setBorderSelectionColor(null); } protected TreeCellEditor createTreeCellEditor() { JTextField tf = new JTextField() { public String getName() { return "Tree.cellEditor"; } }; DefaultCellEditor editor = new DefaultCellEditor(tf); // One click to edit. editor.setClickCountToStart(1); return editor; } } // // BasicTreeUI directly uses expandIcon outside of the Synth methods. // To get the correct context we return an instance of this that fetches // the SynthContext as needed. // private class ExpandedIconWrapper extends SynthIcon { public void paintIcon(SynthContext context, Graphics g, int x, int y, int w, int h) { if (context == null) { context = getContext(tree); SynthIcon.paintIcon(expandedIcon, context, g, x, y, w, h); context.dispose(); } else { SynthIcon.paintIcon(expandedIcon, context, g, x, y, w, h); } } public int getIconWidth(SynthContext context) { int width; if (context == null) { context = getContext(tree); width = SynthIcon.getIconWidth(expandedIcon, context); context.dispose(); } else { width = SynthIcon.getIconWidth(expandedIcon, context); } return width; } public int getIconHeight(SynthContext context) { int height; if (context == null) { context = getContext(tree); height = SynthIcon.getIconHeight(expandedIcon, context); context.dispose(); } else { height = SynthIcon.getIconHeight(expandedIcon, context); } return height; } } }