TreeUi: infinite update for some cases if fixed
[fedora-idea.git] / platform-api / src / com / intellij / ui / treeStructure / Tree.java
blobc8c73f5cc5438c55cfa2983f3cb80ef9a97ad400
1 /*
2 * Copyright 2000-2007 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.ui.treeStructure;
18 import com.intellij.Patches;
19 import com.intellij.ide.util.treeView.*;
20 import com.intellij.openapi.ui.TestableUi;
21 import com.intellij.openapi.util.Disposer;
22 import com.intellij.openapi.util.SystemInfo;
23 import com.intellij.openapi.wm.impl.content.GraphicsConfig;
24 import com.intellij.util.ui.AsyncProcessIcon;
25 import com.intellij.util.ui.UIUtil;
26 import org.jetbrains.annotations.Nullable;
28 import javax.swing.*;
29 import javax.swing.plaf.TreeUI;
30 import javax.swing.plaf.basic.BasicTreeUI;
31 import javax.swing.text.Position;
32 import javax.swing.tree.*;
33 import java.awt.*;
34 import java.awt.dnd.Autoscroll;
35 import java.awt.event.FocusAdapter;
36 import java.awt.event.FocusEvent;
37 import java.awt.event.MouseAdapter;
38 import java.awt.event.MouseEvent;
39 import java.lang.reflect.Array;
40 import java.util.ArrayList;
41 import java.util.Map;
43 public class Tree extends JTree implements Autoscroll, TestableUi {
45 private AsyncProcessIcon myBusyIcon;
46 private boolean myBusy;
47 private Rectangle myLastVisibleRec;
49 public Tree() {
50 initTree_();
53 public Tree(TreeModel treemodel) {
54 super(treemodel);
55 initTree_();
58 public Tree(TreeNode root) {
59 super(root);
60 initTree_();
63 private void initTree_() {
64 addMouseListener(new MyMouseListener());
65 if (Patches.SUN_BUG_ID_4893787) {
66 addFocusListener(new MyFocusListener());
69 addFocusListener(new SelectionFixer());
71 setCellRenderer(new NodeRenderer());
74 @Override
75 public void addNotify() {
76 super.addNotify();
78 updateBusy();
81 @Override
82 public void removeNotify() {
83 super.removeNotify();
85 if (myBusyIcon != null) {
86 remove(myBusyIcon);
87 Disposer.dispose(myBusyIcon);
88 myBusyIcon = null;
92 @Override
93 public void doLayout() {
94 super.doLayout();
96 updateBusyIconLocation();
99 private void updateBusyIconLocation() {
100 if (myBusyIcon != null) {
101 final Rectangle rec = getVisibleRect();
103 final Dimension iconSize = myBusyIcon.getPreferredSize();
105 final Rectangle newBounds = new Rectangle(rec.x + rec.width - iconSize.width, rec.y, iconSize.width, iconSize.height);
106 if (!newBounds.equals(myBusyIcon.getBounds())) {
107 myBusyIcon.setBounds(newBounds);
108 repaint();
114 * Hack to prevent loosing multiple selection on Mac when clicking Ctrl+Left Mouse Button.
115 * See faulty code at BasicTreeUI.selectPathForEvent():2245
117 * @param e
119 protected void processMouseEvent(MouseEvent e) {
120 if (SystemInfo.isMac) {
121 if (SwingUtilities.isLeftMouseButton(e) && e.isControlDown() && e.getID() == MouseEvent.MOUSE_PRESSED) {
122 int modifiers = (e.getModifiers() & ~(MouseEvent.CTRL_MASK | MouseEvent.BUTTON1_MASK)) | MouseEvent.BUTTON3_MASK;
123 e = new MouseEvent(e.getComponent(), e.getID(), e.getWhen(), modifiers, e.getX(), e.getY(), e.getClickCount(), true,
124 MouseEvent.BUTTON3);
127 super.processMouseEvent(e);
131 * Disable Sun's speedsearch
133 public TreePath getNextMatch(String prefix, int startingRow, Position.Bias bias) {
134 return null;
137 private static final int AUTOSCROLL_MARGIN = 10;
139 public Insets getAutoscrollInsets() {
140 return new Insets(getLocation().y + AUTOSCROLL_MARGIN, 0, getParent().getHeight() - AUTOSCROLL_MARGIN, getWidth() - 1);
143 public void autoscroll(Point p) {
144 int realrow = getClosestRowForLocation(p.x, p.y);
145 if (getLocation().y + p.y <= AUTOSCROLL_MARGIN) {
146 if (realrow >= 1) realrow--;
148 else {
149 if (realrow < getRowCount() - 1) realrow++;
151 scrollRowToVisible(realrow);
154 @Override
155 protected void paintComponent(Graphics g) {
156 g.setColor(getBackground());
157 g.fillRect(0, 0, getWidth(), getHeight());
159 paintNodeContent(g);
161 super.paintComponent(g);
164 protected boolean highlightSingleNode() {
165 return true;
168 private void paintNodeContent(Graphics g) {
169 if (!(getUI() instanceof BasicTreeUI)) return;
171 final AbstractTreeBuilder builder = AbstractTreeBuilder.getBuilderFor(this);
172 if (builder == null || builder.isDisposed()) return;
174 GraphicsConfig config = new GraphicsConfig(g);
175 config.setAntialiasing(true);
177 final AbstractTreeStructure structure = builder.getTreeStructure();
179 for (int eachRow = 0; eachRow < getRowCount(); eachRow++) {
180 final TreePath path = getPathForRow(eachRow);
181 PresentableNodeDescriptor node = toPresentableNode(path.getLastPathComponent());
182 if (node == null) continue;
184 if (!node.isContentHighlighted()) continue;
186 if (highlightSingleNode()) {
187 if (node.isContentHighlighted()) {
188 final TreePath nodePath = getPath(node);
190 Rectangle rect;
192 final Rectangle parentRect = getPathBounds(nodePath);
193 if (isExpanded(nodePath)) {
194 final int[] max = getMax(node, structure);
195 rect = new Rectangle(parentRect.x, parentRect.y, Math.max((int) parentRect.getMaxX(), max[1]) - parentRect.x - 1,
196 Math.max((int) parentRect.getMaxY(), max[0]) - parentRect.y - 1);
198 else {
199 rect = parentRect;
202 if (rect != null) {
203 final Color highlightColor = node.getHighlightColor();
204 g.setColor(highlightColor);
205 g.fillRoundRect(rect.x, rect.y, rect.width, rect.height, 4, 4);
206 g.setColor(highlightColor.darker());
207 g.drawRoundRect(rect.x, rect.y, rect.width, rect.height, 4, 4);
211 else {
212 //todo: to investigate why it might happen under 1.6: http://www.productiveme.net:8080/browse/PM-217
213 if (node.getParentDescriptor() == null) continue;
215 final Object[] kids = structure.getChildElements(node);
216 if (kids.length == 0) continue;
218 PresentableNodeDescriptor first = null;
219 PresentableNodeDescriptor last = null;
220 int lastIndex = -1;
221 for (int i = 0; i < kids.length; i++) {
222 final Object kid = kids[i];
223 if (kid instanceof PresentableNodeDescriptor) {
224 PresentableNodeDescriptor eachKid = (PresentableNodeDescriptor) kid;
225 if (!node.isHighlightableContentNode(eachKid)) continue;
226 if (first == null) {
227 first = eachKid;
229 last = eachKid;
230 lastIndex = i;
234 if (first == null || last == null) continue;
235 Rectangle firstBounds = getPathBounds(getPath(first));
237 if (isExpanded(getPath(last))) {
238 if (lastIndex + 1 < kids.length) {
239 final Object child = kids[lastIndex + 1];
240 if (child instanceof PresentableNodeDescriptor) {
241 PresentableNodeDescriptor nextKid = (PresentableNodeDescriptor) child;
242 int nextRow = getRowForPath(getPath(nextKid));
243 last = toPresentableNode(getPathForRow(nextRow - 1).getLastPathComponent());
246 else {
247 NodeDescriptor parentNode = node.getParentDescriptor();
248 if (parentNode instanceof PresentableNodeDescriptor) {
249 final PresentableNodeDescriptor ppd = (PresentableNodeDescriptor)parentNode;
250 int nodeIndex = node.getIndex();
251 if (nodeIndex + 1 < structure.getChildElements(ppd).length) {
252 PresentableNodeDescriptor nextChild = ppd.getChildToHighlightAt(nodeIndex + 1);
253 int nextRow = getRowForPath(getPath(nextChild));
254 last = toPresentableNode(getPathForRow(nextRow - 1).getLastPathComponent());
256 else {
257 int lastRow = getRowForPath(getPath(last));
258 PresentableNodeDescriptor lastParent = last;
259 boolean lastWasFound = false;
260 for (int i = lastRow + 1; i < getRowCount(); i++) {
261 PresentableNodeDescriptor eachNode = toPresentableNode(getPathForRow(i).getLastPathComponent());
262 if (!node.isParentOf(eachNode)) {
263 last = lastParent;
264 lastWasFound = true;
265 break;
267 lastParent = eachNode;
269 if (!lastWasFound) {
270 last = toPresentableNode(getPathForRow(getRowCount() - 1).getLastPathComponent());
277 Rectangle lastBounds = getPathBounds(getPath(last));
279 if (firstBounds == null || lastBounds == null) continue;
281 Rectangle toPaint = new Rectangle(firstBounds.x, firstBounds.y, 0, (int)lastBounds.getMaxY() - firstBounds.y - 1);
283 toPaint.width = getWidth() - toPaint.x - 4;
285 final Color highlightColor = first.getHighlightColor();
286 g.setColor(highlightColor);
287 g.fillRoundRect(toPaint.x, toPaint.y, toPaint.width, toPaint.height, 4, 4);
288 g.setColor(highlightColor.darker());
289 g.drawRoundRect(toPaint.x, toPaint.y, toPaint.width, toPaint.height, 4, 4);
293 config.restore();
296 private int[] getMax(final PresentableNodeDescriptor node, final AbstractTreeStructure structure) {
297 int x = 0;
298 int y = 0;
299 final Object[] children = structure.getChildElements(node);
300 for (final Object child : children) {
301 if (child instanceof PresentableNodeDescriptor) {
302 final TreePath childPath = getPath((PresentableNodeDescriptor)child);
303 if (childPath != null) {
304 if (isExpanded(childPath)) {
305 final int[] tmp = getMax((PresentableNodeDescriptor)child, structure);
306 y = Math.max(y, tmp[0]);
307 x = Math.max(x, tmp[1]);
310 final Rectangle r = getPathBounds(childPath);
311 if (r != null) {
312 y = Math.max(y, (int)r.getMaxY());
313 x = Math.max(x, (int)r.getMaxX());
319 return new int[]{y, x};
322 @Nullable
323 private static PresentableNodeDescriptor toPresentableNode(final Object pathComponent) {
324 if (!(pathComponent instanceof DefaultMutableTreeNode)) return null;
325 final Object userObject = ((DefaultMutableTreeNode)pathComponent).getUserObject();
326 if (!(userObject instanceof PresentableNodeDescriptor)) return null;
327 return (PresentableNodeDescriptor)userObject;
330 public TreePath getPath(PresentableNodeDescriptor node) {
331 final AbstractTreeBuilder builder = AbstractTreeBuilder.getBuilderFor(this);
332 final DefaultMutableTreeNode treeNode = builder.getNodeForElement(node);
334 return treeNode != null ? new TreePath(treeNode.getPath()) : new TreePath(node);
337 private class MyMouseListener extends MouseAdapter {
338 public void mousePressed(MouseEvent mouseevent) {
339 if (!SwingUtilities.isLeftMouseButton(mouseevent) &&
340 (SwingUtilities.isRightMouseButton(mouseevent) || SwingUtilities.isMiddleMouseButton(mouseevent))) {
341 TreePath treepath = getPathForLocation(mouseevent.getX(), mouseevent.getY());
342 if (treepath != null) {
343 if (getSelectionModel().getSelectionMode() != TreeSelectionModel.SINGLE_TREE_SELECTION) {
344 TreePath[] selectionPaths = getSelectionModel().getSelectionPaths();
345 if (selectionPaths != null) {
346 for (TreePath selectionPath : selectionPaths) {
347 if (selectionPath == treepath) return;
351 getSelectionModel().setSelectionPath(treepath);
358 * This is patch for 4893787 SUN bug. The problem is that the BasicTreeUI.FocusHandler repaints
359 * only lead selection index on focus changes. It's a problem with multiple selected nodes.
361 private class MyFocusListener extends FocusAdapter {
362 private void focusChanges() {
363 TreePath[] paths = getSelectionPaths();
364 if (paths != null) {
365 TreeUI ui = getUI();
366 for (int i = paths.length - 1; i >= 0; i--) {
367 Rectangle bounds = ui.getPathBounds(Tree.this, paths[i]);
368 if (bounds != null) {
369 repaint(bounds);
375 public void focusGained(FocusEvent e) {
376 focusChanges();
379 public void focusLost(FocusEvent e) {
380 focusChanges();
384 private class SelectionFixer extends FocusAdapter {
385 public void focusGained(final FocusEvent e) {
386 final TreePath[] paths = getSelectionPaths();
387 if (paths == null || paths.length == 0) {
388 for (int eachRow = 0; eachRow < getRowCount(); eachRow++) {
389 final TreePath path = getPathForRow(eachRow);
390 if (path != null && isVisible(path)) {
391 setSelectionPath(path);
392 break;
399 public final void setLineStyleAngled() {
400 UIUtil.setLineStyleAngled(this);
403 public <T> T[] getSelectedNodes(Class<T> nodeType, @Nullable NodeFilter<T> filter) {
404 TreePath[] paths = getSelectionPaths();
405 if (paths == null) return (T[])Array.newInstance(nodeType, 0);
407 ArrayList<T> nodes = new ArrayList<T>();
408 for (int i = 0; i < paths.length; i++) {
409 Object last = paths[i].getLastPathComponent();
410 if (nodeType.isAssignableFrom(last.getClass())) {
411 if (filter != null && !filter.accept((T)last)) continue;
412 nodes.add((T)last);
415 T[] result = (T[])Array.newInstance(nodeType, nodes.size());
416 nodes.toArray(result);
417 return result;
420 public static interface NodeFilter<T> {
421 boolean accept(T node);
424 public void putInfo(Map<String, String> info) {
425 final TreePath[] selection = getSelectionPaths();
426 if (selection == null) return;
428 StringBuffer nodesText = new StringBuffer();
430 for (TreePath eachPath : selection) {
431 final Object eachNode = eachPath.getLastPathComponent();
432 final Component c =
433 getCellRenderer().getTreeCellRendererComponent(this, eachNode, false, false, false, getRowForPath(eachPath), false);
435 if (c != null) {
436 if (nodesText.length() > 0) {
437 nodesText.append(";");
439 nodesText.append(c.toString());
443 if (nodesText.length() > 0) {
444 info.put("selectedNodes", nodesText.toString());
448 public void setPaintBusy(boolean paintBusy) {
449 if (myBusy == paintBusy) return;
451 myBusy = paintBusy;
452 updateBusy();
455 @Override
456 public void paint(Graphics g) {
457 super.paint(g);
459 final Rectangle visible = getVisibleRect();
461 if (!visible.equals(myLastVisibleRec)) {
462 updateBusyIconLocation();
465 myLastVisibleRec = visible;
468 private void updateBusy() {
469 if (myBusy) {
470 if (myBusyIcon == null) {
471 myBusyIcon = new AsyncProcessIcon(toString());
472 myBusyIcon.setPaintPassiveIcon(false);
473 add(myBusyIcon);
477 if (myBusyIcon != null) {
478 if (myBusy) {
479 myBusyIcon.resume();
480 } else {
481 myBusyIcon.suspend();
482 SwingUtilities.invokeLater(new Runnable() {
483 public void run() {
484 if (myBusyIcon != null) {
485 repaint();
490 updateBusyIconLocation();