| /* |
| * Copyright (c) 1999, 2007, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.awt; |
| |
| import java.awt.Component; |
| import java.awt.Graphics; |
| import java.awt.Rectangle; |
| import java.awt.event.PaintEvent; |
| |
| /** |
| * The {@code RepaintArea} is a geometric construct created for the |
| * purpose of holding the geometry of several coalesced paint events. |
| * This geometry is accessed synchronously, although it is written such |
| * that painting may still be executed asynchronously. |
| * |
| * @author Eric Hawkes |
| * @since 1.3 |
| */ |
| public class RepaintArea { |
| |
| /** |
| * Maximum ratio of bounding rectangle to benefit for which |
| * both the vertical and horizontal unions are repainted. |
| * For smaller ratios the whole bounding rectangle is repainted. |
| * @see #paint |
| */ |
| private static final int MAX_BENEFIT_RATIO = 4; |
| |
| private static final int HORIZONTAL = 0; |
| private static final int VERTICAL = 1; |
| private static final int UPDATE = 2; |
| |
| private static final int RECT_COUNT = UPDATE + 1; |
| |
| private Rectangle paintRects[] = new Rectangle[RECT_COUNT]; |
| |
| |
| /** |
| * Constructs a new {@code RepaintArea} |
| * @since 1.3 |
| */ |
| public RepaintArea() { |
| } |
| |
| /** |
| * Constructs a new {@code RepaintArea} initialized to match |
| * the values of the specified RepaintArea. |
| * |
| * @param ra the {@code RepaintArea} from which to copy initial |
| * values to a newly constructed RepaintArea |
| * @since 1.3 |
| */ |
| private RepaintArea(RepaintArea ra) { |
| // This constructor is private because it should only be called |
| // from the cloneAndReset method |
| for (int i = 0; i < RECT_COUNT; i++) { |
| paintRects[i] = ra.paintRects[i]; |
| } |
| } |
| |
| /** |
| * Adds a {@code Rectangle} to this {@code RepaintArea}. |
| * PAINT Rectangles are divided into mostly vertical and mostly horizontal. |
| * Each group is unioned together. |
| * UPDATE Rectangles are unioned. |
| * |
| * @param r the specified {@code Rectangle} |
| * @param id possible values PaintEvent.UPDATE or PaintEvent.PAINT |
| * @since 1.3 |
| */ |
| public synchronized void add(Rectangle r, int id) { |
| // Make sure this new rectangle has positive dimensions |
| if (r.isEmpty()) { |
| return; |
| } |
| int addTo = UPDATE; |
| if (id == PaintEvent.PAINT) { |
| addTo = (r.width > r.height) ? HORIZONTAL : VERTICAL; |
| } |
| if (paintRects[addTo] != null) { |
| paintRects[addTo].add(r); |
| } else { |
| paintRects[addTo] = new Rectangle(r); |
| } |
| } |
| |
| |
| /** |
| * Creates a new {@code RepaintArea} with the same geometry as this |
| * RepaintArea, then removes all of the geometry from this |
| * RepaintArea and restores it to an empty RepaintArea. |
| * |
| * @return ra a new {@code RepaintArea} having the same geometry as |
| * this RepaintArea. |
| * @since 1.3 |
| */ |
| private synchronized RepaintArea cloneAndReset() { |
| RepaintArea ra = new RepaintArea(this); |
| for (int i = 0; i < RECT_COUNT; i++) { |
| paintRects[i] = null; |
| } |
| return ra; |
| } |
| |
| public boolean isEmpty() { |
| for (int i = 0; i < RECT_COUNT; i++) { |
| if (paintRects[i] != null) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Constrains the size of the repaint area to the passed in bounds. |
| */ |
| public synchronized void constrain(int x, int y, int w, int h) { |
| for (int i = 0; i < RECT_COUNT; i++) { |
| Rectangle rect = paintRects[i]; |
| if (rect != null) { |
| if (rect.x < x) { |
| rect.width -= (x - rect.x); |
| rect.x = x; |
| } |
| if (rect.y < y) { |
| rect.height -= (y - rect.y); |
| rect.y = y; |
| } |
| int xDelta = rect.x + rect.width - x - w; |
| if (xDelta > 0) { |
| rect.width -= xDelta; |
| } |
| int yDelta = rect.y + rect.height - y - h; |
| if (yDelta > 0) { |
| rect.height -= yDelta; |
| } |
| if (rect.width <= 0 || rect.height <= 0) { |
| paintRects[i] = null; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Marks the passed in region as not needing to be painted. It's possible |
| * this will do nothing. |
| */ |
| public synchronized void subtract(int x, int y, int w, int h) { |
| Rectangle subtract = new Rectangle(x, y, w, h); |
| for (int i = 0; i < RECT_COUNT; i++) { |
| if (subtract(paintRects[i], subtract)) { |
| if (paintRects[i] != null && paintRects[i].isEmpty()) { |
| paintRects[i] = null; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Invokes paint and update on target Component with optimal |
| * rectangular clip region. |
| * If PAINT bounding rectangle is less than |
| * MAX_BENEFIT_RATIO times the benefit, then the vertical and horizontal unions are |
| * painted separately. Otherwise the entire bounding rectangle is painted. |
| * |
| * @param target Component to {@code paint} or {@code update} |
| * @since 1.4 |
| */ |
| public void paint(Object target, boolean shouldClearRectBeforePaint) { |
| Component comp = (Component)target; |
| |
| if (isEmpty()) { |
| return; |
| } |
| |
| if (!comp.isVisible()) { |
| return; |
| } |
| |
| RepaintArea ra = this.cloneAndReset(); |
| |
| if (!subtract(ra.paintRects[VERTICAL], ra.paintRects[HORIZONTAL])) { |
| subtract(ra.paintRects[HORIZONTAL], ra.paintRects[VERTICAL]); |
| } |
| |
| if (ra.paintRects[HORIZONTAL] != null && ra.paintRects[VERTICAL] != null) { |
| Rectangle paintRect = ra.paintRects[HORIZONTAL].union(ra.paintRects[VERTICAL]); |
| int square = paintRect.width * paintRect.height; |
| int benefit = square - ra.paintRects[HORIZONTAL].width |
| * ra.paintRects[HORIZONTAL].height - ra.paintRects[VERTICAL].width |
| * ra.paintRects[VERTICAL].height; |
| // if benefit is comparable with bounding box |
| if (MAX_BENEFIT_RATIO * benefit < square) { |
| ra.paintRects[HORIZONTAL] = paintRect; |
| ra.paintRects[VERTICAL] = null; |
| } |
| } |
| for (int i = 0; i < paintRects.length; i++) { |
| if (ra.paintRects[i] != null |
| && !ra.paintRects[i].isEmpty()) |
| { |
| // Should use separate Graphics for each paint() call, |
| // since paint() can change Graphics state for next call. |
| Graphics g = comp.getGraphics(); |
| if (g != null) { |
| try { |
| g.setClip(ra.paintRects[i]); |
| if (i == UPDATE) { |
| updateComponent(comp, g); |
| } else { |
| if (shouldClearRectBeforePaint) { |
| g.clearRect( ra.paintRects[i].x, |
| ra.paintRects[i].y, |
| ra.paintRects[i].width, |
| ra.paintRects[i].height); |
| } |
| paintComponent(comp, g); |
| } |
| } finally { |
| g.dispose(); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Calls {@code Component.update(Graphics)} with given Graphics. |
| */ |
| protected void updateComponent(Component comp, Graphics g) { |
| if (comp != null) { |
| comp.update(g); |
| } |
| } |
| |
| /** |
| * Calls {@code Component.paint(Graphics)} with given Graphics. |
| */ |
| protected void paintComponent(Component comp, Graphics g) { |
| if (comp != null) { |
| comp.paint(g); |
| } |
| } |
| |
| /** |
| * Subtracts subtr from rect. If the result is rectangle |
| * changes rect and returns true. Otherwise false. |
| */ |
| static boolean subtract(Rectangle rect, Rectangle subtr) { |
| if (rect == null || subtr == null) { |
| return true; |
| } |
| Rectangle common = rect.intersection(subtr); |
| if (common.isEmpty()) { |
| return true; |
| } |
| if (rect.x == common.x && rect.y == common.y) { |
| if (rect.width == common.width) { |
| rect.y += common.height; |
| rect.height -= common.height; |
| return true; |
| } else |
| if (rect.height == common.height) { |
| rect.x += common.width; |
| rect.width -= common.width; |
| return true; |
| } |
| } else |
| if (rect.x + rect.width == common.x + common.width |
| && rect.y + rect.height == common.y + common.height) |
| { |
| if (rect.width == common.width) { |
| rect.height -= common.height; |
| return true; |
| } else |
| if (rect.height == common.height) { |
| rect.width -= common.width; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public String toString() { |
| return super.toString() + "[ horizontal=" + paintRects[0] + |
| " vertical=" + paintRects[1] + |
| " update=" + paintRects[2] + "]"; |
| } |
| } |