/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.ui; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.awt.*; import java.util.Enumeration; /** * @author cdr */ public class DuplicateNodeRenderer { public interface DuplicatableNode<T> { //returns first duplicate node, if any, or null if there are none //duplicate nodes are painted gray @Nullable T getDuplicate(); } public static void paintDuplicateNodesBackground(Graphics g, JTree tree) { Rectangle clipBounds = g.getClipBounds(); int start = tree.getClosestRowForLocation(clipBounds.x, clipBounds.y); int end = Math.min(tree.getRowCount(), tree.getClosestRowForLocation(clipBounds.x+clipBounds.width, clipBounds.y+clipBounds.height)+1); Color old = g.getColor(); for (int i = start; i < end; i++) { TreePath path = tree.getPathForRow(i); if (path == null) continue; DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); Rectangle accumRect = null; TreePath accumPath = null; while (node != null) { Object userObject = node.getUserObject(); if (!(userObject instanceof DuplicatableNode)) break; DuplicatableNode duplicatableNode = (DuplicatableNode)userObject; Object duplicate = duplicatableNode.getDuplicate(); if (duplicate == null) break; accumPath = accumRect == null ? path : accumPath.getParentPath(); accumRect = union(tree.getPathBounds(accumPath), accumRect); node = (DefaultMutableTreeNode)node.getParent(); } if (accumRect != null) { Rectangle rowRect = tree.getRowBounds(tree.getRowForPath(accumPath)); accumRect = accumRect.intersection(new Rectangle(rowRect.x, rowRect.y, Integer.MAX_VALUE, Integer.MAX_VALUE)); //unite all expanded children node rectangles since they can stretch out of parent's node = (DefaultMutableTreeNode)accumPath.getLastPathComponent(); accumRect = union(accumRect, getExpandedNodesRect(tree, node, accumPath)); g.setColor(Gray._230); g.fillRoundRect(accumRect.x, accumRect.y, accumRect.width, accumRect.height, 10, 10); g.setColor(Color.lightGray); g.drawRoundRect(accumRect.x, accumRect.y, accumRect.width, accumRect.height, 10, 10); } } g.setColor(old); } @NotNull private static Rectangle union(Rectangle r1, Rectangle r2) { if (r1 == null) return r2; if (r2 == null) return r1; return r1.union(r2); } private static Rectangle getExpandedNodesRect(JTree tree, DefaultMutableTreeNode node, TreePath path) { Rectangle rect = tree.getRowBounds(tree.getRowForPath(path)); if (tree.isExpanded(path)) { Enumeration<TreeNode> children = node.children(); while (children.hasMoreElements()) { DefaultMutableTreeNode child = (DefaultMutableTreeNode)children.nextElement(); TreePath childPath = path.pathByAddingChild(child); assert !path.equals(childPath) : path+";"+child; rect = union(rect, getExpandedNodesRect(tree, child, childPath)); } } return rect; } }