/* * 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.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) { 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.paint(g, 0, myImageHeight, width, max, force); value.myModified = false; } 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(Graphics2D g, int x, int y, int width, int height, Object object) { myImageY = y; super.paint(g, x, y, width, height, object); } private abstract static class Value { boolean myModified; abstract boolean set(ErrorStripe stripe); abstract boolean add(ErrorStripe stripe); abstract ErrorStripe get(); abstract void paint(Graphics2D g, int x, int y, int width, int height); 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 void paint(Graphics2D g, int x, int y, int width, int height) { if (myStripe != null) { int thickness = myAlignment != null ? myMin + myGap : height; y += getOffset(height, thickness); g.setColor(myStripe.getColor()); g.fillRect(x, y, width, thickness - myGap); } } } private final class ComplexValue extends Value { private TreeSet<ErrorStripe> 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<ErrorStripe> iterator = mySet.iterator(); return iterator.hasNext() ? iterator.next() : null; } @Override void paint(Graphics2D g, int x, int y, int width, int height) { if (mySet != null) { Iterator<ErrorStripe> 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 { g.setColor(iterator.next().getColor()); g.fillRect(x, y, width, thickness - myGap); y += thickness; } while (--count > 0 && iterator.hasNext()); } else { g.setColor(iterator.next().getColor()); g.fillRect(x, y, width, thickness - myGap); } } } } } }