diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPSIPane.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPSIPane.java index d8d666d7e3ef..c8958277f979 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPSIPane.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPSIPane.java @@ -22,19 +22,30 @@ package com.intellij.ide.projectView.impl; import com.intellij.ide.DataManager; import com.intellij.ide.PsiCopyPasteManager; import com.intellij.ide.projectView.BaseProjectTreeBuilder; +import com.intellij.ide.projectView.PresentationData; import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; import com.intellij.ide.ui.customization.CustomizationUtil; import com.intellij.ide.util.treeView.AbstractTreeBuilder; import com.intellij.ide.util.treeView.AbstractTreeUpdater; +import com.intellij.ide.util.treeView.NodeDescriptor; +import com.intellij.ide.util.treeView.PresentableNodeDescriptor; import com.intellij.ide.util.treeView.TreeBuilderUtil; import com.intellij.openapi.actionSystem.ActionPlaces; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.TreeSpeedSearch; +import com.intellij.ui.stripe.ErrorStripe; +import com.intellij.ui.stripe.ExtraErrorStripePainter; +import com.intellij.ui.stripe.TreeUpdater; import com.intellij.util.EditSourceOnDoubleClickHandler; import com.intellij.util.OpenSourceUtil; import com.intellij.util.ui.UIUtil; @@ -71,6 +82,45 @@ public abstract class AbstractProjectViewPSIPane extends AbstractProjectViewPane myTree = createTree(treeModel); enableDnD(); myComponent = ScrollPaneFactory.createScrollPane(myTree); + if (Registry.is("error.stripe.enabled")) { + ExtraErrorStripePainter painter = new ExtraErrorStripePainter(true); + Disposer.register(this, new TreeUpdater(painter, myComponent, myTree) { + @Override + protected void update(ExtraErrorStripePainter painter, int index, Object object) { + if (object instanceof DefaultMutableTreeNode) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode)object; + object = node.getUserObject(); + } + painter.setExtraStripe(index, getExtraStripe(object)); + if (object instanceof PsiDirectoryNode && !myTree.isCollapsed(index)) { + object = null; + } + super.update(painter, index, object); + } + + @Override + protected ErrorStripe getErrorStripe(Object object) { + if (object instanceof PresentableNodeDescriptor) { + PresentableNodeDescriptor node = (PresentableNodeDescriptor)object; + PresentationData presentation = node.getPresentation(); + TextAttributesKey key = presentation.getTextAttributesKey(); + if (key != null) { + TextAttributes attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(key); + if (attributes != null) return ErrorStripe.create(attributes.getEffectColor(), 1); + } + } + return null; + } + + private ErrorStripe getExtraStripe(Object object) { + if (object instanceof NodeDescriptor) { + NodeDescriptor node = (NodeDescriptor)object; + return ErrorStripe.create(node.getColor(), 0); + } + return null; + } + }); + } myTreeStructure = createStructure(); setTreeBuilder(createBuilder(treeModel)); diff --git a/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java b/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java index d0433da9a714..e168c002550f 100644 --- a/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java +++ b/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java @@ -637,6 +637,10 @@ public class ButtonlessScrollBarUI extends BasicScrollBarUI { if (alwaysShowTrack() || myMouseOverScrollbarExpandLevel > 0) { doPaintTrack(g, c, trackBounds); } + RegionPainter painter = UIUtil.getClientProperty(c, RegionPainter.BUTTONLESS_SCROLL_BAR_UI_EXTRA_TRACK); + if (painter != null) { + painter.paint(g, trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height); + } } protected void doPaintTrack(Graphics g, JComponent c, Rectangle bounds) { diff --git a/platform/platform-api/src/com/intellij/util/ui/RegionPainter.java b/platform/platform-api/src/com/intellij/util/ui/RegionPainter.java new file mode 100644 index 000000000000..14d6169dbe21 --- /dev/null +++ b/platform/platform-api/src/com/intellij/util/ui/RegionPainter.java @@ -0,0 +1,61 @@ +/* + * Copyright 2000-2015 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.util.ui; + +import com.intellij.openapi.util.Key; +import com.intellij.util.ui.UIUtil; + +import java.awt.Graphics; +import java.awt.image.BufferedImage; + +/** + * @author Sergey.Malenkov + */ +public interface RegionPainter { + void paint(Graphics g, int x, int y, int width, int height); + + Key BUTTONLESS_SCROLL_BAR_UI_EXTRA_TRACK = Key.create("BUTTONLESS_SCROLL_BAR_UI_EXTRA_TRACK"); + + class Image implements RegionPainter { + private BufferedImage myImage; + + protected void updateImage(BufferedImage image) { + } + + protected BufferedImage createImage(int width, int height) { + return UIUtil.createImage(width, height, BufferedImage.TYPE_INT_ARGB); + } + + protected void invalidate() { + myImage = null; + } + + @Override + public void paint(Graphics g, int x, int y, int width, int height) { + if (width > 0 && width > 0) { + if (myImage == null || width != myImage.getWidth() || height != myImage.getHeight()) { + myImage = createImage(width, height); + } + else if (myImage != null) { + updateImage(myImage); + } + if (myImage != null) { + UIUtil.drawImage(g, myImage, null, x, y); + } + } + } + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripe.java b/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripe.java new file mode 100644 index 000000000000..d3e9525c256b --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripe.java @@ -0,0 +1,79 @@ +/* + * Copyright 2000-2015 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.stripe; + +import com.intellij.util.ui.RegionPainter; + +import java.awt.Color; +import java.awt.Graphics; + +/** + * @author Sergey.Malenkov + */ +public final class ErrorStripe implements RegionPainter, Comparable { + private final Color myColor; + private final int myLayer; + + private ErrorStripe(Color color, int layer) { + myColor = color; + myLayer = layer; + } + + public static ErrorStripe create(Color color, int layer) { + return color == null ? null : new ErrorStripe(color, layer); + } + + public Color getColor() { + return myColor; + } + + public int getLayer() { + return myLayer; + } + + @Override + public void paint(Graphics g, int x, int y, int width, int height) { + g.setColor(myColor); + g.fillRect(x, y, width, height); + } + + @Override + public int hashCode() { + return myLayer + myColor.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (object == this) return true; + if (object instanceof ErrorStripe) { + ErrorStripe stripe = (ErrorStripe)object; + return stripe.myLayer == myLayer && stripe.myColor.getRGB() == myColor.getRGB(); + } + return false; + } + + @Override + public int compareTo(ErrorStripe stripe) { + if (stripe == this) return 0; + if (stripe == null || stripe.myLayer < myLayer) return -1; + if (stripe.myLayer > myLayer) return 1; + + int thisRGB = myColor.getRGB(); + int thatRGB = stripe.myColor.getRGB(); + if (thatRGB == thisRGB) return 0; + return thatRGB < thisRGB ? -1 : 1; + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripePainter.java b/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripePainter.java new file mode 100644 index 000000000000..144e8eec4ec3 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/stripe/ErrorStripePainter.java @@ -0,0 +1,348 @@ +/* + * Copyright 2000-2015 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.stripe; + +import com.intellij.util.ui.RegionPainter; + +import java.awt.AlphaComposite; +import java.awt.Composite; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.util.Iterator; +import java.util.TreeSet; + +/** + * @author Sergey.Malenkov + */ +public class ErrorStripePainter extends RegionPainter.Image { + public enum Alignment {TOP, CENTER, BOTTOM} + + private final boolean mySingleValue; + private final Alignment myAlignment; + private Value[] myArray; + private int myArraySize; + private int myImageY; + private int myImageHeight; + private int myMax = Integer.MAX_VALUE; + private int myMin = 1; + private int myGap; + + public ErrorStripePainter(boolean single) { + this(single, Alignment.CENTER); + } + + public ErrorStripePainter(boolean single, Alignment style) { + mySingleValue = single; + myAlignment = style; + } + + public int getMaximalThickness() { + return myMax; + } + + public void setMaximalThickness(int thickness) { + if (myMax != thickness) { + myMax = thickness; + invalidate(); + } + } + + public int getMinimalThickness() { + return myMin; + } + + public void setMinimalThickness(int thickness) { + if (thickness < 1) thickness = 1; + if (myMin != thickness) { + myMin = thickness; + invalidate(); + } + } + + public int getErrorStripeGap() { + return myGap; + } + + public void setErrorStripeGap(int gap) { + if (gap < 0) gap = 0; + if (myGap != gap) { + myGap = gap; + invalidate(); + } + } + + public int findIndex(int x, int y) { + if (0 < myImageHeight && myImageY <= y) { + int index = myArraySize * (y - myImageY) / myImageHeight; + if (index < myArraySize) return index; + } + return -1; + } + + public int getErrorStripeCount() { + return myArraySize; + } + + public void setErrorStripeCount(int count) { + if (count < 0) count = 0; + if (myArray == null) { + myArray = new Value[count]; + } + else if (myArray.length < count) { + Value[] old = myArray; + myArray = new Value[count]; + System.arraycopy(old, 0, myArray, 0, old.length); + } + if (myArraySize != count) { + myArraySize = count; + invalidate(); + } + } + + public boolean isModified() { + for (int index = 0; index < myArraySize; index++) { + Value value = myArray[index]; + if (value != null && value.myModified) return true; + } + return false; + } + + public void clear() { + for (int index = 0; index < myArraySize; index++) { + Value value = myArray[index]; + if (value != null) value.set(null); + } + } + + public void clear(int index) { + Value value = getValue(index, false); + if (value != null) value.set(null); + } + + public ErrorStripe getErrorStripe(int index) { + Value value = getValue(index, false); + return value == null ? null : value.get(); + } + + public void setErrorStripe(int index, ErrorStripe stripe) { + Value value = getValue(index, stripe != null); + if (value != null) value.set(stripe); + } + + public void addErrorStripe(int index, ErrorStripe stripe) { + Value value = getValue(index, stripe != null); + if (value != null) value.add(stripe); + } + + private Value getValue(int index, boolean create) { + if (0 > index || index >= myArraySize) return null; + if (create && null == myArray[index]) { + myArray[index] = mySingleValue ? new SingleValue() : new ComplexValue(); + } + return myArray[index]; + } + + private int getOffset(int height, int thickness) { + if (height > thickness) { + if (myAlignment == Alignment.CENTER) return (height - thickness) / 2; + if (myAlignment == Alignment.BOTTOM) return (height - thickness); + } + return 0; + } + + private void updateImage(BufferedImage image, boolean force) { + int width = image.getWidth(); + int height = image.getHeight(); + Graphics2D g = image.createGraphics(); + + myImageHeight = 0; + int min = myMin + myGap; + int max = height / myArraySize; + if (max < min) { + max = height / min; + int currentIndex = 0; + SingleValue currentValue = new SingleValue(); + for (int index = 0; index < myArraySize; index++) { + Value value = myArray[index]; + int i = index * max / myArraySize; + if (i > currentIndex) { + currentValue.paint(g, 0, myImageHeight, width, min, force); + myImageHeight += min; + currentIndex = i; + currentValue.myStripe = value == null ? null : value.get(); + currentValue.myModified = value != null && value.myModified; + } + else if (value != null) { + value.myModified = false; + ErrorStripe stripe = value.get(); + if (stripe != null && stripe.compareTo(currentValue.myStripe) < 0) { + currentValue.myStripe = stripe; + } + if (value.myModified) { + currentValue.myModified = true; + } + } + if (value != null) { + value.myModified = false; + } + } + currentValue.paint(g, 0, myImageHeight, width, min, force); + myImageHeight += min; + } + else { + if (max > myMax) { + max = Math.max(myMax, min); + } + for (int index = 0; index < myArraySize; index++) { + Value value = myArray[index]; + if (value != null) { + value.myModified = false; + value.paint(g, 0, myImageHeight, width, max, force); + } + myImageHeight += max; + } + } + g.dispose(); + } + + @Override + protected void updateImage(BufferedImage image) { + if (isModified()) updateImage(image, false); + } + + @Override + protected BufferedImage createImage(int width, int height) { + BufferedImage image = myArraySize == 0 ? null : super.createImage(width, height); + if (image != null) updateImage(image, true); + return image; + } + + @Override + public void paint(Graphics g, int x, int y, int width, int height) { + myImageY = y; + super.paint(g, x, y, width, height); + } + + private abstract static class Value implements RegionPainter { + boolean myModified; + + abstract boolean set(ErrorStripe stripe); + + abstract boolean add(ErrorStripe stripe); + + abstract ErrorStripe get(); + + void paint(Graphics2D g, int x, int y, int width, int height, boolean force) { + if (force || myModified) { + if (!force) { + Composite old = g.getComposite(); + g.setComposite(AlphaComposite.Clear); + g.fillRect(x, y, width, height); + g.setComposite(old); + } + paint(g, x, y, width, height); + } + } + } + + private final class SingleValue extends Value { + private ErrorStripe myStripe; + + @Override + boolean set(ErrorStripe stripe) { + if (stripe == null ? myStripe == null : stripe.equals(myStripe)) return false; + myStripe = stripe; + myModified = true; + return true; + } + + @Override + boolean add(ErrorStripe stripe) { + if (stripe == null || stripe.compareTo(myStripe) >= 0) return false; + myStripe = stripe; + myModified = true; + return true; + } + + @Override + ErrorStripe get() { + return myStripe; + } + + @Override + public void paint(Graphics g, int x, int y, int width, int height) { + myModified = false; + if (myStripe != null) { + int thickness = myAlignment != null ? myMin + myGap : height; + y += getOffset(height, thickness); + myStripe.paint(g, x, y, width, thickness - myGap); + } + } + } + + private final class ComplexValue extends Value { + private TreeSet mySet; + + @Override + boolean set(ErrorStripe stripe) { + if (add(stripe)) return true; + if (mySet == null || mySet.isEmpty()) return false; + mySet.clear(); + myModified = true; + return true; + } + + @Override + boolean add(ErrorStripe stripe) { + if (stripe == null) return false; + if (mySet == null) mySet = new TreeSet(); + mySet.add(stripe); + myModified = true; + return true; + } + + @Override + ErrorStripe get() { + if (mySet == null) return null; + Iterator iterator = mySet.iterator(); + return iterator.hasNext() ? iterator.next() : null; + } + + @Override + public void paint(Graphics g, int x, int y, int width, int height) { + myModified = false; + if (mySet != null) { + Iterator iterator = mySet.iterator(); + if (iterator.hasNext()) { + int thickness = myAlignment != null ? myMin + myGap : height; + if (thickness < height) { + int count = Math.min(height / thickness, mySet.size()); + y += getOffset(height, thickness * count); + do { + iterator.next().paint(g, x, y, width, thickness - myGap); + y += thickness; + } + while (--count > 0 && iterator.hasNext()); + } + else { + iterator.next().paint(g, x, y, width, thickness - myGap); + } + } + } + } + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/stripe/ExtraErrorStripePainter.java b/platform/platform-impl/src/com/intellij/ui/stripe/ExtraErrorStripePainter.java new file mode 100644 index 000000000000..d73da2157043 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/stripe/ExtraErrorStripePainter.java @@ -0,0 +1,87 @@ +/* + * Copyright 2000-2015 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.stripe; + +import java.awt.Graphics; + +/** + * @author Sergey.Malenkov + */ +public class ExtraErrorStripePainter extends ErrorStripePainter { + private final ErrorStripePainter myPainter = new ErrorStripePainter(true, null); + private boolean myGroupSwap; + private Integer myGroupGap; + + public ExtraErrorStripePainter(boolean single) { + super(single); + } + + public boolean isGroupSwap() { + return myGroupSwap; + } + + public void setGroupSwap(boolean swap) { + myGroupSwap = swap; + } + + public int getGroupGap() { + return myGroupGap; + } + + public void setGroupGap(int gap) { + myGroupGap = gap; + } + + public void setMaximalThickness(int thickness) { + super.setMaximalThickness(thickness); + myPainter.setMaximalThickness(getMaximalThickness()); + } + + public void setMinimalThickness(int thickness) { + super.setMinimalThickness(thickness); + myPainter.setMinimalThickness(getMinimalThickness() + getErrorStripeGap()); + } + + @Override + public void setErrorStripeGap(int gap) { + super.setErrorStripeGap(gap); + myPainter.setMinimalThickness(getMinimalThickness() + getErrorStripeGap()); + } + + public void setErrorStripeCount(int count) { + super.setErrorStripeCount(count); + myPainter.setErrorStripeCount(count); + } + + public void setExtraStripe(int index, ErrorStripe stripe) { + myPainter.setErrorStripe(index, stripe); + } + + @Override + public void paint(Graphics g, int x, int y, int width, int height) { + int min = getMinimalThickness(); + int gap = myGroupGap == null ? min : myGroupGap; + int pos = x; + if (myGroupSwap) { + pos += width - min; + } + else { + x += gap + min; + } + myPainter.paint(g, pos, y, min, height); + super.paint(g, x, y, width - gap - min, height); + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/stripe/TreeUpdater.java b/platform/platform-impl/src/com/intellij/ui/stripe/TreeUpdater.java new file mode 100644 index 000000000000..0f855a44db52 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/stripe/TreeUpdater.java @@ -0,0 +1,95 @@ +/* + * Copyright 2000-2015 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.stripe; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.DumbAwareAction; + +import java.beans.EventHandler; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import javax.swing.JScrollPane; +import javax.swing.JTree; +import javax.swing.event.TreeModelListener; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; + +/** + * @author Sergey.Malenkov + */ +public class TreeUpdater extends Updater { + private final JTree myTree; + + private final TreeModelListener myTreeModelListener = EventHandler.create(TreeModelListener.class, this, "update"); + private final PropertyChangeListener myPropertyChangeListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + Object oldValue = event.getOldValue(); + if (oldValue instanceof TreeModel) ((TreeModel)oldValue).removeTreeModelListener(myTreeModelListener); + Object newValue = event.getNewValue(); + if (newValue instanceof TreeModel) ((TreeModel)newValue).addTreeModelListener(myTreeModelListener); + } + }; + + public TreeUpdater(Painter painter, JScrollPane pane, JTree tree) { + super(painter, pane); + myTree = tree; + myTree.addPropertyChangeListener(JTree.TREE_MODEL_PROPERTY, myPropertyChangeListener); + TreeModel model = myTree.getModel(); + if (model != null) model.addTreeModelListener(myTreeModelListener); + new DumbAwareAction() { + @Override + public void actionPerformed(AnActionEvent event) { + selectNext(myTree.getMaxSelectionRow()); + } + }.registerCustomShortcutSet(getNextErrorShortcut(), myTree, this); + new DumbAwareAction() { + @Override + public void actionPerformed(AnActionEvent event) { + selectPrevious(myTree.getMinSelectionRow()); + } + }.registerCustomShortcutSet(getPreviousErrorShortcut(), myTree, this); + } + + @Override + public void dispose() { + myTree.removePropertyChangeListener(JTree.TREE_MODEL_PROPERTY, myPropertyChangeListener); + TreeModel model = myTree.getModel(); + if (model != null) model.removeTreeModelListener(myTreeModelListener); + super.dispose(); + } + + @Override + protected void onSelect(Painter painter, int index) { + if (0 <= index) { + myTree.setSelectionRow(index); + myTree.scrollRowToVisible(index); + } + } + + protected void update(Painter painter) { + update(painter, myTree); + } + + protected void update(Painter painter, JTree tree) { + int count = tree.getRowCount(); + painter.setErrorStripeCount(count); + for (int index = 0; index < count; index++) { + TreePath path = tree.getPathForRow(index); + update(painter, index, path == null ? null : path.getLastPathComponent()); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/stripe/Updater.java b/platform/platform-impl/src/com/intellij/ui/stripe/Updater.java new file mode 100644 index 000000000000..b1d4c584c194 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/stripe/Updater.java @@ -0,0 +1,221 @@ +/* + * Copyright 2000-2015 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.stripe; + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.CustomShortcutSet; +import com.intellij.openapi.actionSystem.ShortcutSet; +import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.util.ui.JBUI; +import com.intellij.util.ui.RegionPainter; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.update.MergingUpdateQueue; +import com.intellij.util.ui.update.Update; +import org.jetbrains.annotations.NotNull; + +import javax.swing.JScrollBar; +import javax.swing.JScrollPane; +import java.awt.Cursor; +import java.awt.Graphics; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +/** + * @author Sergey.Malenkov + */ +public abstract class Updater implements Disposable { + private final Painter myPainter; + private final JScrollBar myScrollBar; + private final MergingUpdateQueue myQueue; + private final MouseAdapter myMouseAdapter = new MouseAdapter() { + @Override + public void mouseMoved(MouseEvent event) { + onMouseMove(myPainter, event.getX(), event.getY()); + } + + @Override + public void mouseClicked(MouseEvent event) { + onMouseClick(myPainter, event.getX(), event.getY()); + } + }; + + protected Updater(@NotNull Painter painter, JScrollPane pane) { + myPainter = painter; + myScrollBar = pane.getVerticalScrollBar(); + myScrollBar.addMouseListener(myMouseAdapter); + myScrollBar.addMouseMotionListener(myMouseAdapter); + myQueue = new MergingUpdateQueue("ErrorStripeUpdater", 100, true, myScrollBar, this); + UIUtil.putClientProperty(myScrollBar, RegionPainter.BUTTONLESS_SCROLL_BAR_UI_EXTRA_TRACK, new RegionPainter() { + @Override + public void paint(Graphics g, int x, int y, int width, int height) { + DaemonCodeAnalyzerSettings settings = DaemonCodeAnalyzerSettings.getInstance(); + myPainter.setMinimalThickness(settings == null ? 2 : Math.min(settings.ERROR_STRIPE_MARK_MIN_HEIGHT, JBUI.scale(4))); + myPainter.setErrorStripeGap(Registry.intValue("error.stripe.gap", 0)); + myPainter.paint(g, x, y, width, height); + } + }); + } + + @Override + public void dispose() { + myScrollBar.removeMouseListener(myMouseAdapter); + myScrollBar.removeMouseMotionListener(myMouseAdapter); + UIUtil.putClientProperty(myScrollBar, RegionPainter.BUTTONLESS_SCROLL_BAR_UI_EXTRA_TRACK, null); + } + + private int findErrorStripeIndex(Painter painter, int x, int y) { + int index = painter.findIndex(x, y); + if (null != painter.getErrorStripe(index)) return index; + index = painter.findIndex(x, y + 1); + if (null != painter.getErrorStripe(index)) return index; + index = painter.findIndex(x, y - 1); + if (null != painter.getErrorStripe(index)) return index; + index = painter.findIndex(x, y + 2); + if (null != painter.getErrorStripe(index)) return index; + return -1; + } + + protected void onMouseMove(Painter painter, int x, int y) { + onMouseMove(painter, findErrorStripeIndex(painter, x, y)); + } + + protected void onMouseMove(Painter painter, int index) { + myScrollBar.setCursor(index < 0 ? null : Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + + protected void onMouseClick(Painter painter, int x, int y) { + onMouseClick(painter, findErrorStripeIndex(painter, x, y)); + } + + protected void onMouseClick(Painter painter, int index) { + onSelect(painter, index); + } + + protected void onSelect(Painter painter, int index) { + } + + protected ShortcutSet getNextErrorShortcut() { + return new CustomShortcutSet(KeymapManager.getInstance().getActiveKeymap().getShortcuts("GotoNextError")); + } + + public void selectNext(int index) { + onSelect(myPainter, findNextIndex(index)); + } + + protected ShortcutSet getPreviousErrorShortcut() { + return new CustomShortcutSet(KeymapManager.getInstance().getActiveKeymap().getShortcuts("GotoPreviousError")); + } + + public void selectPrevious(int index) { + onSelect(myPainter, findPreviousIndex(index)); + } + + protected abstract void update(Painter painter); + + protected void update(Painter painter, int index, Object object) { + painter.setErrorStripe(index, getErrorStripe(object)); + } + + protected ErrorStripe getErrorStripe(Object object) { + return null; + } + + public final void update() { + myQueue.cancelAllUpdates(); + myQueue.queue(new Update("update") { + @Override + public void run() { + update(myPainter); + if (myPainter.isModified()) { + myScrollBar.repaint(); + } + } + }); + } + + public int findNextIndex(int current) { + int count = myPainter.getErrorStripeCount(); + int foundIndex = -1; + int foundLayer = 0; + if (0 <= current && current < count) { + current++; + for (int index = current; index < count; index++) { + int layer = getLayer(index); + if (layer > foundLayer) { + foundIndex = index; + foundLayer = layer; + } + } + for (int index = 0; index < current; index++) { + int layer = getLayer(index); + if (layer > foundLayer) { + foundIndex = index; + foundLayer = layer; + } + } + } + else { + for (int index = 0; index < count; index++) { + int layer = getLayer(index); + if (layer > foundLayer) { + foundIndex = index; + foundLayer = layer; + } + } + } + return foundIndex; + } + + public int findPreviousIndex(int current) { + int count = myPainter.getErrorStripeCount(); + int foundIndex = -1; + int foundLayer = 0; + if (0 <= current && current < count) { + current--; + for (int index = count - 1; index >= 0; index++) { + int layer = getLayer(index); + if (layer > foundLayer) { + foundIndex = index; + foundLayer = layer; + } + } + for (int index = current - 1; index >= 0; index++) { + int layer = getLayer(index); + if (layer > foundLayer) { + foundIndex = index; + foundLayer = layer; + } + } + } + else { + for (int index = count - 1; index >= 0; index--) { + int layer = getLayer(index); + if (layer > foundLayer) { + foundIndex = index; + foundLayer = layer; + } + } + } + return foundIndex; + } + + private int getLayer(int index) { + ErrorStripe stripe = myPainter.getErrorStripe(index); + return stripe == null ? -1 : stripe.getLayer(); + } +} diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index cfc328efdbfc..d754117a23cd 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -606,6 +606,10 @@ editor.config.stop.at.project.root.description=Stops searching for .editorconfig editor.bold.underline.height=2 editor.bold.underline.height.description=Underline height for EffectType.BOLD_LINE_UNDERSCORE +error.stripe.enabled=true +error.stripe.gap=1 +error.stripe.gap.description=Minimal space between error stripe. + JDK8042508.bug.fixed=false JDK8042508.bug.fixed.description=Disable check for type variable until javac bug is fixed