Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.pushingpixels.substance.internal.ui.SubstanceTreeUI Maven / Gradle / Ivy
/*
* Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of Substance Kirill Grouchnikov nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pushingpixels.substance.internal.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.swing.ButtonModel;
import javax.swing.DefaultButtonModel;
import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.IconUIResource;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import org.pushingpixels.lafwidget.LafWidgetUtilities;
import org.pushingpixels.lafwidget.utils.LookUtils;
import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
import org.pushingpixels.substance.api.ComponentState;
import org.pushingpixels.substance.api.ComponentStateFacet;
import org.pushingpixels.substance.api.SubstanceColorScheme;
import org.pushingpixels.substance.api.SubstanceLookAndFeel;
import org.pushingpixels.substance.api.renderers.SubstanceDefaultTreeCellRenderer;
import org.pushingpixels.substance.internal.animation.StateTransitionMultiTracker;
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
import org.pushingpixels.substance.internal.animation.StateTransitionTracker.RepaintCallback;
import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
import org.pushingpixels.substance.internal.utils.SubstanceStripingUtils;
import org.pushingpixels.substance.internal.utils.icon.SubstanceIconFactory;
import org.pushingpixels.trident.Timeline.TimelineState;
import org.pushingpixels.trident.callback.TimelineCallback;
import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
/**
* UI for lists in Substance look and feel.
*
* @author Kirill Grouchnikov
*/
public class SubstanceTreeUI extends BasicTreeUI {
/**
* Holds the list of currently selected paths.
*/
protected Map selectedPaths;
/**
* Holds the currently rolled-over path or null
if none such.
*/
protected TreePathId currRolloverPathId;
/**
* Listener that listens to changes on tree properties.
*/
protected PropertyChangeListener substancePropertyChangeListener;
/**
* Listener for selection animations.
*/
protected TreeSelectionListener substanceSelectionFadeListener;
/**
* Listener for transition animations on tree rollovers.
*/
protected RolloverFadeListener substanceFadeRolloverListener;
/**
* Listener for selection of an entire row.
*/
protected MouseListener substanceRowSelectionListener;
private StateTransitionMultiTracker stateTransitionMultiTracker;
/**
* The current default color scheme. Is computed in
* {@link #update(Graphics, JComponent)} and reused in
* {@link SubstanceDefaultTreeCellRenderer#getTreeCellRendererComponent(JTree, Object, boolean, boolean, boolean, int, boolean)}
* for performance optimizations.
*/
private SubstanceColorScheme currDefaultColorScheme;
/**
* Cell renderer insets. Is computed in {@link #installDefaults()} and
* reused in
* {@link SubstanceDefaultTreeCellRenderer#getTreeCellRendererComponent(JTree, Object, boolean, boolean, boolean, int, boolean)}
* for performance optimizations.
*/
private Insets cellRendererInsets;
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
*/
public static ComponentUI createUI(JComponent comp) {
SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
return new SubstanceTreeUI();
}
/**
* Creates a UI delegate for tree.
*/
public SubstanceTreeUI() {
super();
this.selectedPaths = new HashMap();
this.stateTransitionMultiTracker = new StateTransitionMultiTracker();
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicTreeUI#installDefaults()
*/
@Override
protected void installDefaults() {
super.installDefaults();
if (SubstanceCoreUtilities.toDrawWatermark(this.tree))
this.tree.setOpaque(false);
if (this.tree.getSelectionPaths() != null) {
for (TreePath selectionPath : this.tree.getSelectionPaths()) {
TreePathId pathId = new TreePathId(selectionPath);
selectedPaths.put(pathId, selectionPath.getLastPathComponent());
}
}
setExpandedIcon(new IconUIResource(SubstanceIconFactory.getTreeIcon(
this.tree, false)));
setCollapsedIcon(new IconUIResource(SubstanceIconFactory.getTreeIcon(
this.tree, true)));
// instead of computing the cell renderer insets on
// every cell rendering, compute it once and expose to the
// SubstanceDefaultTreeCellRenderer
this.cellRendererInsets = SubstanceSizeUtils
.getTreeCellRendererInsets(SubstanceSizeUtils
.getComponentFontSize(tree));
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicTreeUI#uninstallDefaults()
*/
@Override
protected void uninstallDefaults() {
this.selectedPaths.clear();
super.uninstallDefaults();
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicTreeUI#paintRow(java.awt.Graphics,
* java.awt.Rectangle, java.awt.Insets, java.awt.Rectangle,
* javax.swing.tree.TreePath, int, boolean, boolean, boolean)
*/
@Override
protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets,
Rectangle bounds, TreePath path, int row, boolean isExpanded,
boolean hasBeenExpanded, boolean isLeaf) {
// Don't paint the renderer if editing this row.
if ((this.editingComponent != null) && (this.editingRow == row)) {
// fix for issue 446 - paint the expand control
// on the editing row
if (shouldPaintExpandControl(path, row, isExpanded,
hasBeenExpanded, isLeaf)) {
if (!this.tree.getComponentOrientation().isLeftToRight()
&& LookUtils.IS_JAVA_5) {
bounds.x -= 4;
}
paintExpandControlEnforce(g, clipBounds, insets, bounds, path,
row, isExpanded, hasBeenExpanded, isLeaf);
}
}
int leadIndex;
if (this.tree.hasFocus()) {
TreePath leadPath = this.tree.getLeadSelectionPath();
leadIndex = this.getRowForPath(this.tree, leadPath);
} else {
leadIndex = -1;
}
Component renderer = this.currentCellRenderer
.getTreeCellRendererComponent(this.tree, path
.getLastPathComponent(), this.tree.isRowSelected(row),
isExpanded, isLeaf, row, (leadIndex == row));
if (!(renderer instanceof SubstanceDefaultTreeCellRenderer)) {
// if it's not Substance renderer - ask the Basic delegate to paint
// it.
super.paintRow(g, clipBounds, insets, bounds, path, row,
isExpanded, hasBeenExpanded, isLeaf);
if (shouldPaintExpandControl(path, row, isExpanded,
hasBeenExpanded, isLeaf)) {
paintExpandControlEnforce(g, clipBounds, insets, bounds, path,
row, isExpanded, hasBeenExpanded, isLeaf);
}
return;
}
TreePathId pathId = new TreePathId(path);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(tree, g));
// Color background = renderer.getBackground();
// if (background == null)
// background = tree.getBackground();
StateTransitionTracker.ModelStateInfo modelStateInfo = getModelStateInfo(pathId);
Map activeStates = ((modelStateInfo == null) ? null
: modelStateInfo.getStateContributionMap());
ComponentState currState = ((modelStateInfo == null) ? getPathState(pathId)
: modelStateInfo.getCurrModelState());
// Compute the alpha values for the animation.
boolean hasHighlights = false;
if (renderer.isEnabled()) {
if (activeStates != null) {
for (Map.Entry stateEntry : activeStates
.entrySet()) {
hasHighlights = (SubstanceColorSchemeUtilities
.getHighlightAlpha(this.tree, stateEntry.getKey())
* stateEntry.getValue().getContribution() > 0.0f);
if (hasHighlights)
break;
}
} else {
hasHighlights = (SubstanceColorSchemeUtilities
.getHighlightAlpha(this.tree, currState) > 0.0f);
}
}
// System.out.println(row + ":" + prevTheme.getDisplayName() + "["
// + alphaForPrevBackground + "]:" + currTheme.getDisplayName()
// + "[" + alphaForCurrBackground + "]");
// At this point the renderer is an instance of
// SubstanceDefaultTreeCellRenderer
JTree.DropLocation dropLocation = tree.getDropLocation();
Rectangle rowRectangle = new Rectangle(this.tree.getInsets().left,
bounds.y, this.tree.getWidth() - this.tree.getInsets().right
- this.tree.getInsets().left, bounds.height);
if (dropLocation != null && dropLocation.getChildIndex() == -1
&& tree.getRowForPath(dropLocation.getPath()) == row) {
// mark drop location
SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
.getColorScheme(tree,
ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
currState);
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
.getColorScheme(tree, ColorSchemeAssociationKind.BORDER,
currState);
HighlightPainterUtils.paintHighlight(g2d, this.rendererPane,
renderer, rowRectangle, 0.8f, null, scheme, borderScheme);
} else {
if (hasHighlights) {
if (activeStates == null) {
float alpha = SubstanceColorSchemeUtilities
.getHighlightAlpha(this.tree, currState);
if (alpha > 0.0f) {
SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
.getColorScheme(this.tree,
ColorSchemeAssociationKind.HIGHLIGHT,
currState);
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
.getColorScheme(this.tree,
ColorSchemeAssociationKind.HIGHLIGHT,
currState);
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
this.tree, alpha, g));
// Fix for defect 180 - painting the
// highlight beneath the entire row
HighlightPainterUtils.paintHighlight(g2d,
this.rendererPane, renderer, rowRectangle,
0.8f, null, fillScheme, borderScheme);
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
this.tree, g));
}
} else {
for (Map.Entry stateEntry : activeStates
.entrySet()) {
ComponentState activeState = stateEntry.getKey();
float alpha = SubstanceColorSchemeUtilities
.getHighlightAlpha(this.tree, activeState)
* stateEntry.getValue().getContribution();
if (alpha == 0.0f)
continue;
SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
.getColorScheme(this.tree,
ColorSchemeAssociationKind.HIGHLIGHT,
activeState);
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
.getColorScheme(this.tree,
ColorSchemeAssociationKind.HIGHLIGHT,
activeState);
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
this.tree, alpha, g));
// Fix for defect 180 - painting the
// highlight beneath the entire row
HighlightPainterUtils.paintHighlight(g2d,
this.rendererPane, renderer, rowRectangle,
0.8f, null, fillScheme, borderScheme);
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
this.tree, g));
}
}
}
}
// System.out.println("Painting row " + row);
// Play with opacity to make our own gradient background
// on selected elements to show - safe to cast and set opacity
// since at this point the renderer can only by the
// SubstanceDefaultTreeCellRenderer
JComponent jRenderer = (JComponent) renderer;
boolean newOpaque = !this.tree.isRowSelected(row);
if (SubstanceCoreUtilities.toDrawWatermark(this.tree))
newOpaque = false;
Map opacity = new HashMap();
if (!newOpaque)
SubstanceCoreUtilities.makeNonOpaque(jRenderer, opacity);
this.rendererPane.paintComponent(g2d, renderer, this.tree, bounds.x,
bounds.y, Math.max(this.tree.getWidth()
- this.tree.getInsets().right
- this.tree.getInsets().left - bounds.x, bounds.width),
bounds.height, true);
if (!newOpaque)
SubstanceCoreUtilities.restoreOpaque(jRenderer, opacity);
// Paint the expand control now after the row background has been
// overlayed by the highlight background on selected and rolled over
// rows. See comments on paintExpandControl().
if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded,
isLeaf)) {
paintExpandControlEnforce(g2d, clipBounds, insets, bounds, path,
row, isExpanded, hasBeenExpanded, isLeaf);
}
g2d.dispose();
}
/*
* (non-Javadoc)
*
* @see
* javax.swing.plaf.basic.BasicTreeUI#paintExpandControl(java.awt.Graphics,
* java.awt.Rectangle, java.awt.Insets, java.awt.Rectangle,
* javax.swing.tree.TreePath, int, boolean, boolean, boolean)
*/
@Override
protected void paintExpandControl(Graphics g, Rectangle clipBounds,
Insets insets, Rectangle bounds, TreePath path, int row,
boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) {
// This does nothing. The base implementation of paint() paints
// the tree lines and tree expand controls *before* painting the
// renderer. In Substance, the highlights are painted in the
// paintRow, and thus would overlay the expand controls. This results
// in expand controls being much less visible under most of the skins.
// So, Substance paints the expand controls *after* painting the
// highlights (and the renderer which doesn't overlap with the expand
// controls in any case). This is done in paintRow() by calling
// the paintExpandControlEnforce() instead (that eventually calls the
// super implementation of paintExpandControl().
}
/**
* Paints the expand control of the specified row.
*
* @param g
* Graphics context.
* @param clipBounds
* Clip bounds.
* @param insets
* Insets.
* @param bounds
* Row bounds.
* @param path
* Tree path.
* @param row
* Tree row.
* @param isExpanded
* Expand indication.
* @param hasBeenExpanded
* Indication whether this row has ever been expanded.
* @param isLeaf
* Indication whether this row is a leaf.
*/
protected void paintExpandControlEnforce(Graphics g, Rectangle clipBounds,
Insets insets, Rectangle bounds, TreePath path, int row,
boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) {
// boolean toPaint = (!this.tree.isEnabled()) || this.isInside;
float alpha = SubstanceColorSchemeUtilities.getAlpha(this.tree,
this.tree.isEnabled() ? ComponentState.ENABLED
: ComponentState.DISABLED_UNSELECTED);
Graphics2D graphics = (Graphics2D) g.create();
// if (toPaint) {
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(this.tree,
alpha, g));
super.paintExpandControl(graphics, clipBounds, insets, bounds, path,
row, isExpanded, hasBeenExpanded, isLeaf);
// }
graphics.dispose();
}
/*
* (non-Javadoc)
*
* @see
* javax.swing.plaf.basic.BasicTreeUI#paintHorizontalPartOfLeg(java.awt.
* Graphics, java.awt.Rectangle, java.awt.Insets, java.awt.Rectangle,
* javax.swing.tree.TreePath, int, boolean, boolean, boolean)
*/
@Override
protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
Insets insets, Rectangle bounds, TreePath path, int row,
boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) {
}
/*
* (non-Javadoc)
*
* @see
* javax.swing.plaf.basic.BasicTreeUI#paintVerticalPartOfLeg(java.awt.Graphics
* , java.awt.Rectangle, java.awt.Insets, javax.swing.tree.TreePath)
*/
@Override
protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
Insets insets, TreePath path) {
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicTreeUI#createDefaultCellRenderer()
*/
@Override
protected TreeCellRenderer createDefaultCellRenderer() {
return new SubstanceDefaultTreeCellRenderer();
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicTreeUI#installListeners()
*/
@Override
protected void installListeners() {
super.installListeners();
this.substancePropertyChangeListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (SubstanceLookAndFeel.WATERMARK_VISIBLE.equals(evt
.getPropertyName())) {
tree.setOpaque(!SubstanceCoreUtilities
.toDrawWatermark(tree));
}
if ("font".equals(evt.getPropertyName())) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
tree.updateUI();
}
});
}
if ("dropLocation".equals(evt.getPropertyName())) {
JTree.DropLocation oldValue = (JTree.DropLocation) evt
.getOldValue();
if (oldValue != null) {
TreePath oldDrop = oldValue.getPath();
Rectangle oldBounds = getPathBounds(tree, oldDrop);
tree.repaint(0, oldBounds.y, tree.getWidth(),
oldBounds.height);
}
JTree.DropLocation currLocation = tree.getDropLocation();
if (currLocation != null) {
TreePath newDrop = currLocation.getPath();
if (newDrop != null) {
Rectangle newBounds = getPathBounds(tree, newDrop);
tree.repaint(0, newBounds.y, tree.getWidth(),
newBounds.height);
}
}
}
}
};
this.tree
.addPropertyChangeListener(this.substancePropertyChangeListener);
this.substanceSelectionFadeListener = new MyTreeSelectionListener();
this.tree.getSelectionModel().addTreeSelectionListener(
this.substanceSelectionFadeListener);
this.substanceRowSelectionListener = new RowSelectionListener();
this.tree.addMouseListener(this.substanceRowSelectionListener);
// Add listener for the fade animation
this.substanceFadeRolloverListener = new RolloverFadeListener();
this.tree.addMouseMotionListener(this.substanceFadeRolloverListener);
this.tree.addMouseListener(this.substanceFadeRolloverListener);
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicTreeUI#uninstallListeners()
*/
@Override
protected void uninstallListeners() {
this.tree.removeMouseListener(this.substanceRowSelectionListener);
this.substanceRowSelectionListener = null;
this.tree.getSelectionModel().removeTreeSelectionListener(
this.substanceSelectionFadeListener);
this.substanceSelectionFadeListener = null;
this.tree
.removePropertyChangeListener(this.substancePropertyChangeListener);
this.substancePropertyChangeListener = null;
// Remove listener for the fade animation
this.tree.removeMouseMotionListener(this.substanceFadeRolloverListener);
this.tree.removeMouseListener(this.substanceFadeRolloverListener);
this.substanceFadeRolloverListener = null;
super.uninstallListeners();
}
/**
* ID of a single tree path.
*
* @author Kirill Grouchnikov
*/
@SuppressWarnings("unchecked")
public static class TreePathId implements Comparable {
/**
* Tree path.
*/
protected TreePath path;
/**
* Creates a tree path ID.
*
* @param path
* Tree path.
*/
public TreePathId(TreePath path) {
this.path = path;
}
/*
* (non-Javadoc)
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(Object o) {
if (o instanceof TreePathId) {
TreePathId otherId = (TreePathId) o;
if ((this.path == null) && (otherId.path != null))
return 1;
if ((otherId.path == null) && (this.path != null))
return -1;
Object[] path1Objs = this.path.getPath();
Object[] path2Objs = otherId.path.getPath();
if (path1Objs.length != path2Objs.length)
return 1;
for (int i = 0; i < path1Objs.length; i++)
if (!path1Objs[i].equals(path2Objs[i]))
return 1;
return 0;
}
return -1;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
return this.compareTo(obj) == 0;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
if (this.path == null)
return 0;
Object[] pathObjs = this.path.getPath();
int result = pathObjs[0].hashCode();
for (int i = 1; i < pathObjs.length; i++)
result = result ^ pathObjs[i].hashCode();
return result;
}
}
/**
* Selection listener for selection animation effects.
*
* @author Kirill Grouchnikov
*/
protected class MyTreeSelectionListener implements TreeSelectionListener {
/*
* (non-Javadoc)
*
* @see
* javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.
* event.TreeSelectionEvent)
*/
@Override
public void valueChanged(TreeSelectionEvent e) {
// Map currSelected = (Map)
// tree
// .getClientProperty(SELECTED_INDICES);
if (tree.getSelectionPaths() != null) {
for (TreePath selectionPath : tree.getSelectionPaths()) {
TreePathId pathId = new TreePathId(selectionPath);
// check if was selected before
if (!selectedPaths.containsKey(pathId)) {
// start fading in
StateTransitionTracker tracker = getTracker(pathId,
(currRolloverPathId != null)
&& pathId.equals(currRolloverPathId),
false);
tracker.getModel().setSelected(true);
selectedPaths.put(pathId, selectionPath
.getLastPathComponent());
}
}
}
for (Iterator> it = selectedPaths
.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = it.next();
if (tree.getSelectionModel()
.isPathSelected(entry.getKey().path))
continue;
// fade out for deselected path
TreePathId pathId = entry.getKey();
StateTransitionTracker tracker = getTracker(pathId,
(currRolloverPathId != null)
&& pathId.equals(currRolloverPathId), true);
tracker.getModel().setSelected(false);
it.remove();
}
}
}
/**
* Repaints a single path during the fade animation cycle.
*
* @author Kirill Grouchnikov
*/
protected class PathRepaintCallback extends UIThreadTimelineCallbackAdapter {
/**
* Associated tree.
*/
protected JTree tree;
/**
* Associated (animated) path.
*/
protected TreePath treePath;
/**
* Creates a new animation repaint callback.
*
* @param tree
* Associated tree.
* @param treePath
* Associated (animated) path.
*/
public PathRepaintCallback(JTree tree, TreePath treePath) {
super();
this.tree = tree;
this.treePath = treePath;
}
@Override
public void onTimelinePulse(float durationFraction,
float timelinePosition) {
this.repaintPath();
}
@Override
public void onTimelineStateChanged(TimelineState oldState,
TimelineState newState, float durationFraction,
float timelinePosition) {
this.repaintPath();
}
/**
* Repaints the associated path.
*/
private void repaintPath() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (SubstanceTreeUI.this.tree == null) {
// may happen if the LAF was switched in the meantime
return;
}
Rectangle boundsBuffer = new Rectangle();
Rectangle bounds = treeState.getBounds(treePath,
boundsBuffer);
if (bounds != null) {
// still visible
// fix for defect 180 - refresh the entire row
bounds.x = 0;
bounds.width = tree.getWidth();
// fix for defect 188 - rollover effects for trees
// with insets
Insets insets = tree.getInsets();
bounds.x += insets.left;
bounds.y += insets.top;
tree.repaint(bounds);
}
}
});
}
}
/**
* Listener for rollover animation effects.
*
* @author Kirill Grouchnikov
*/
private class RolloverFadeListener implements MouseListener,
MouseMotionListener {
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
if (!tree.isEnabled())
return;
// isInside = true;
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
if (!tree.isEnabled())
return;
// isInside = false;
this.fadeOut();
// System.out.println("Nulling RO index");
currRolloverPathId = null;
}
@Override
public void mouseMoved(MouseEvent e) {
if (!tree.isEnabled())
return;
// isInside = true;
handleMove(e);
}
@Override
public void mouseDragged(MouseEvent e) {
if (!tree.isEnabled())
return;
handleMove(e);
}
/**
* Handles various mouse move events and initiates the fade animation if
* necessary.
*
* @param e
* Mouse event.
*/
private void handleMove(MouseEvent e) {
TreePath closestPath = tree.getClosestPathForLocation(e.getX(), e
.getY());
Rectangle bounds = tree.getPathBounds(closestPath);
if (bounds == null) {
this.fadeOut();
currRolloverPathId = null;
return;
}
if ((e.getY() < bounds.y)
|| (e.getY() > (bounds.y + bounds.height))) {
this.fadeOut();
currRolloverPathId = null;
return;
}
// check if this is the same index
TreePathId newPathId = new TreePathId(closestPath);
if ((currRolloverPathId != null)
&& newPathId.equals(currRolloverPathId)) {
// System.out.println("Same location " +
// System.currentTimeMillis());
// System.out.print("Current : ");
// for (Object o1 : currPathId.path.getPath()) {
// System.out.print(o1);
// }
// System.out.println("");
// System.out.print("Closest : ");
// for (Object o2 : newPathId.path.getPath()) {
// System.out.print(o2);
// }
// System.out.println("");
return;
}
this.fadeOut();
StateTransitionTracker tracker = getTracker(newPathId, false,
selectedPaths.containsKey(newPathId));
tracker.getModel().setRollover(true);
currRolloverPathId = newPathId;
}
/**
* Initiates the fade out effect.
*/
private void fadeOut() {
if (currRolloverPathId == null)
return;
StateTransitionTracker tracker = getTracker(currRolloverPathId,
true, selectedPaths.containsKey(currRolloverPathId));
tracker.getModel().setRollover(false);
}
}
/**
* Listener for selecting the entire rows.
*
* @author Kirill Grouchnikov
*/
private class RowSelectionListener extends MouseAdapter {
/*
* (non-Javadoc)
*
* @see
* java.awt.event.MouseAdapter#mousePressed(java.awt.event.MouseEvent)
*/
@Override
public void mousePressed(MouseEvent e) {
if (!tree.isEnabled())
return;
TreePath closestPath = tree.getClosestPathForLocation(e.getX(), e
.getY());
if (closestPath == null)
return;
Rectangle bounds = tree.getPathBounds(closestPath);
// Process events outside the immediate bounds - fix for defect
// 19 on substance-netbeans. This properly handles Ctrl and Shift
// selections on trees.
if ((e.getY() >= bounds.y)
&& (e.getY() < (bounds.y + bounds.height))
&& ((e.getX() < bounds.x) || (e.getX() > (bounds.x + bounds.width)))) {
// tree.setSelectionPath(closestPath);
// fix - don't select a node if the click was on the
// expand control
if (isLocationInExpandControl(closestPath, e.getX(), e.getY()))
return;
selectPathForEvent(closestPath, e);
}
}
}
/**
* Returns the pivot X for the cells rendered in the specified area. Used
* for the smart tree scroll (
* {@link SubstanceLookAndFeel#TREE_SMART_SCROLL_ANIMATION_KIND}).
*
* @param paintBounds
* Area bounds.
* @return Pivot X for the cells rendered in the specified area
*/
public int getPivotRendererX(Rectangle paintBounds) {
TreePath initialPath = getClosestPathForLocation(tree, 0, paintBounds.y);
Enumeration paintingEnumerator = treeState
.getVisiblePathsFrom(initialPath);
int endY = paintBounds.y + paintBounds.height;
int totalY = 0;
int count = 0;
if (initialPath != null && paintingEnumerator != null) {
boolean done = false;
Rectangle boundsBuffer = new Rectangle();
Rectangle bounds;
TreePath path;
Insets insets = tree.getInsets();
while (!done && paintingEnumerator.hasMoreElements()) {
path = (TreePath) paintingEnumerator.nextElement();
if (path != null) {
bounds = treeState.getBounds(path, boundsBuffer);
bounds.x += insets.left;
bounds.y += insets.top;
int currMedianX = bounds.x;// + bounds.width / 2;
totalY += currMedianX;
count++;
if ((bounds.y + bounds.height) >= endY)
done = true;
} else {
done = true;
}
}
}
if (count == 0)
return -1;
return totalY
/ count
- 2
* SubstanceSizeUtils.getTreeIconSize(SubstanceSizeUtils
.getComponentFontSize(tree));
}
/**
* Returns the current state for the specified path.
*
* @param pathId
* Path index.
* @return The current state for the specified path.
*/
public ComponentState getPathState(TreePathId pathId) {
boolean isEnabled = this.tree.isEnabled();
StateTransitionTracker tracker = this.stateTransitionMultiTracker
.getTracker(pathId);
if (tracker == null) {
int rowIndex = this.tree.getRowForPath(pathId.path);
boolean isRollover = (this.currRolloverPathId != null)
&& pathId.equals(this.currRolloverPathId);
boolean isSelected = this.tree.isRowSelected(rowIndex);
return ComponentState.getState(isEnabled, isRollover, isSelected);
} else {
ComponentState fromTracker = tracker.getModelStateInfo()
.getCurrModelState();
return ComponentState.getState(isEnabled, fromTracker
.isFacetActive(ComponentStateFacet.ROLLOVER), fromTracker
.isFacetActive(ComponentStateFacet.SELECTION));
}
}
public StateTransitionTracker.ModelStateInfo getModelStateInfo(
TreePathId pathId) {
if (this.stateTransitionMultiTracker.size() == 0)
return null;
StateTransitionTracker tracker = this.stateTransitionMultiTracker
.getTracker(pathId);
if (tracker == null) {
return null;
} else {
return tracker.getModelStateInfo();
}
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
* javax.swing.JComponent)
*/
@Override
public void update(Graphics g, JComponent c) {
BackgroundPaintingUtils.updateIfOpaque(g, c);
// Should never happen if installed for a UI
if (treeState == null) {
return;
}
// compute the default color scheme - to optimize the performance
// SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
// .getColorScheme(this.tree,
// this.tree.isEnabled() ? ComponentState.DEFAULT
// : ComponentState.DISABLED_UNSELECTED);
// this.currHashColor = scheme.getLineColor();
this.currDefaultColorScheme = SubstanceColorSchemeUtilities
.getColorScheme(tree, ComponentState.ENABLED);
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;
// second part - fix for defect 214 (rollover effects on non-opaque
// trees resulted in inconsistent behaviour)
boolean isWatermarkBleed = SubstanceCoreUtilities.toDrawWatermark(tree)
|| !tree.isOpaque();
Graphics2D g2d = (Graphics2D) g.create();
SubstanceStripingUtils.setup(c);
if (initialPath != null && paintingEnumerator != null) {
boolean done = false;
Rectangle boundsBuffer = new Rectangle();
Rectangle bounds;
TreePath path;
while (!done && paintingEnumerator.hasMoreElements()) {
path = (TreePath) paintingEnumerator.nextElement();
if (path != null) {
// respect the background color of the renderer.
boolean isLeaf = treeModel.isLeaf(path
.getLastPathComponent());
boolean isExpanded = isLeaf ? false : treeState
.getExpandedState(path);
Component renderer = this.currentCellRenderer
.getTreeCellRendererComponent(this.tree, path
.getLastPathComponent(), this.tree
.isRowSelected(row), isExpanded, isLeaf,
row, tree.hasFocus() ? (tree
.getLeadSelectionRow() == row)
: false);
Color background = renderer.getBackground();
if (background == null)
background = tree.getBackground();
bounds = treeState.getBounds(path, boundsBuffer);
if(bounds != null){
bounds.x += insets.left;
bounds.y += insets.top;
if (!isWatermarkBleed) {
g2d.setColor(background);
g2d.fillRect(paintBounds.x, bounds.y,
paintBounds.width, bounds.height);
} else {
if (this.tree.getComponentOrientation().isLeftToRight()) {
BackgroundPaintingUtils.fillAndWatermark(g2d,
this.tree, background, new Rectangle(
paintBounds.x, bounds.y,
paintBounds.width, bounds.height));
} else {
BackgroundPaintingUtils.fillAndWatermark(g2d,
this.tree, background, new Rectangle(
paintBounds.x, bounds.y,
paintBounds.width, bounds.height));
}
}
if ((bounds.y + bounds.height) >= endY)
done = true;
}
} else {
done = true;
}
row++;
}
}
this.paint(g2d, c);
SubstanceStripingUtils.tearDown(c);
g2d.dispose();
}
// /*
// * (non-Javadoc)
// *
// * @see javax.swing.plaf.basic.BasicTreeUI#getHashColor()
// */
// @Override
// protected Color getHashColor() {
// return this.currHashColor;
// }
/**
* Returns the default color scheme of this tree. Is for internal use only.
*
* @return The default color scheme of this tree.
*/
public SubstanceColorScheme getDefaultColorScheme() {
return this.currDefaultColorScheme;
}
/**
* Returns the cell renderer insets of this tree. Is for internal use only.
*
* @return The cell renderer insets of this tree.
*/
public Insets getCellRendererInsets() {
return cellRendererInsets;
}
@Override
public Rectangle getPathBounds(JTree tree, TreePath path) {
Rectangle result = super.getPathBounds(tree, path);
if (result != null) {
if (tree.getComponentOrientation().isLeftToRight()) {
result.width = tree.getWidth() - tree.getInsets().right
- result.x;
} else {
int delta = result.x - tree.getInsets().left;
result.x -= delta;
result.width += delta;
}
}
return result;
}
private StateTransitionTracker getTracker(final TreePathId pathId,
boolean initialRollover, boolean initialSelected) {
StateTransitionTracker tracker = stateTransitionMultiTracker
.getTracker(pathId);
if (tracker == null) {
ButtonModel model = new DefaultButtonModel();
model.setSelected(initialSelected);
model.setRollover(initialRollover);
tracker = new StateTransitionTracker(this.tree, model);
tracker.registerModelListeners();
tracker.setRepaintCallback(new RepaintCallback() {
@Override
public TimelineCallback getRepaintCallback() {
return new PathRepaintCallback(tree, pathId.path);
}
});
stateTransitionMultiTracker.addTracker(pathId, tracker);
}
return tracker;
}
public StateTransitionTracker getStateTransitionTracker(TreePathId pathId) {
return this.stateTransitionMultiTracker.getTracker(pathId);
}
}