1 /* 2 * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.awt; 27 28 import java.awt.Component; 29 import java.awt.Graphics; 30 import java.awt.Rectangle; 31 import java.awt.event.PaintEvent; 32 33 /** 34 * The {@code RepaintArea} is a geometric construct created for the 35 * purpose of holding the geometry of several coalesced paint events. 36 * This geometry is accessed synchronously, although it is written such 37 * that painting may still be executed asynchronously. 38 * 39 * @author Eric Hawkes 40 * @since 1.3 41 */ 42 public class RepaintArea { 43 44 /** 45 * Maximum ratio of bounding rectangle to benefit for which 46 * both the vertical and horizontal unions are repainted. 47 * For smaller ratios the whole bounding rectangle is repainted. 48 * @see #paint 49 */ 50 private static final int MAX_BENEFIT_RATIO = 4; 51 52 private static final int HORIZONTAL = 0; 53 private static final int VERTICAL = 1; 54 private static final int UPDATE = 2; 55 56 private static final int RECT_COUNT = UPDATE + 1; 57 58 private Rectangle[] paintRects = new Rectangle[RECT_COUNT]; 59 60 61 /** 62 * Constructs a new {@code RepaintArea} 63 * @since 1.3 64 */ 65 public RepaintArea() { 66 } 67 68 /** 69 * Constructs a new {@code RepaintArea} initialized to match 70 * the values of the specified RepaintArea. 71 * 72 * @param ra the {@code RepaintArea} from which to copy initial 73 * values to a newly constructed RepaintArea 74 * @since 1.3 75 */ 76 private RepaintArea(RepaintArea ra) { 77 // This constructor is private because it should only be called 78 // from the cloneAndReset method 79 for (int i = 0; i < RECT_COUNT; i++) { 80 paintRects[i] = ra.paintRects[i]; 81 } 82 } 83 84 /** 85 * Adds a {@code Rectangle} to this {@code RepaintArea}. 86 * PAINT Rectangles are divided into mostly vertical and mostly horizontal. 87 * Each group is unioned together. 88 * UPDATE Rectangles are unioned. 89 * 90 * @param r the specified {@code Rectangle} 91 * @param id possible values PaintEvent.UPDATE or PaintEvent.PAINT 92 * @since 1.3 93 */ 94 public synchronized void add(Rectangle r, int id) { 95 // Make sure this new rectangle has positive dimensions 96 if (r.isEmpty()) { 97 return; 98 } 99 int addTo = UPDATE; 100 if (id == PaintEvent.PAINT) { 101 addTo = (r.width > r.height) ? HORIZONTAL : VERTICAL; 102 } 103 if (paintRects[addTo] != null) { 104 paintRects[addTo].add(r); 105 } else { 106 paintRects[addTo] = new Rectangle(r); 107 } 108 } 109 110 111 /** 112 * Creates a new {@code RepaintArea} with the same geometry as this 113 * RepaintArea, then removes all of the geometry from this 114 * RepaintArea and restores it to an empty RepaintArea. 115 * 116 * @return ra a new {@code RepaintArea} having the same geometry as 117 * this RepaintArea. 118 * @since 1.3 119 */ 120 private synchronized RepaintArea cloneAndReset() { 121 RepaintArea ra = new RepaintArea(this); 122 for (int i = 0; i < RECT_COUNT; i++) { 123 paintRects[i] = null; 124 } 125 return ra; 126 } 127 128 public boolean isEmpty() { 129 for (int i = 0; i < RECT_COUNT; i++) { 130 if (paintRects[i] != null) { 131 return false; 132 } 133 } 134 return true; 135 } 136 137 /** 138 * Constrains the size of the repaint area to the passed in bounds. 139 */ 140 public synchronized void constrain(int x, int y, int w, int h) { 141 for (int i = 0; i < RECT_COUNT; i++) { 142 Rectangle rect = paintRects[i]; 143 if (rect != null) { 144 if (rect.x < x) { 145 rect.width -= (x - rect.x); 146 rect.x = x; 147 } 148 if (rect.y < y) { 149 rect.height -= (y - rect.y); 150 rect.y = y; 151 } 152 int xDelta = rect.x + rect.width - x - w; 153 if (xDelta > 0) { 154 rect.width -= xDelta; 155 } 156 int yDelta = rect.y + rect.height - y - h; 157 if (yDelta > 0) { 158 rect.height -= yDelta; 159 } 160 if (rect.width <= 0 || rect.height <= 0) { 161 paintRects[i] = null; 162 } 163 } 164 } 165 } 166 167 /** 168 * Marks the passed in region as not needing to be painted. It's possible 169 * this will do nothing. 170 */ 171 public synchronized void subtract(int x, int y, int w, int h) { 172 Rectangle subtract = new Rectangle(x, y, w, h); 173 for (int i = 0; i < RECT_COUNT; i++) { 174 if (subtract(paintRects[i], subtract)) { 175 if (paintRects[i] != null && paintRects[i].isEmpty()) { 176 paintRects[i] = null; 177 } 178 } 179 } 180 } 181 182 /** 183 * Invokes paint and update on target Component with optimal 184 * rectangular clip region. 185 * If PAINT bounding rectangle is less than 186 * MAX_BENEFIT_RATIO times the benefit, then the vertical and horizontal unions are 187 * painted separately. Otherwise the entire bounding rectangle is painted. 188 * 189 * @param target Component to {@code paint} or {@code update} 190 * @since 1.4 191 */ 192 public void paint(Object target, boolean shouldClearRectBeforePaint) { 193 Component comp = (Component)target; 194 195 if (isEmpty()) { 196 return; 197 } 198 199 if (!comp.isVisible()) { 200 return; 201 } 202 203 RepaintArea ra = this.cloneAndReset(); 204 205 if (!subtract(ra.paintRects[VERTICAL], ra.paintRects[HORIZONTAL])) { 206 subtract(ra.paintRects[HORIZONTAL], ra.paintRects[VERTICAL]); 207 } 208 209 if (ra.paintRects[HORIZONTAL] != null && ra.paintRects[VERTICAL] != null) { 210 Rectangle paintRect = ra.paintRects[HORIZONTAL].union(ra.paintRects[VERTICAL]); 211 int square = paintRect.width * paintRect.height; 212 int benefit = square - ra.paintRects[HORIZONTAL].width 213 * ra.paintRects[HORIZONTAL].height - ra.paintRects[VERTICAL].width 214 * ra.paintRects[VERTICAL].height; 215 // if benefit is comparable with bounding box 216 if (MAX_BENEFIT_RATIO * benefit < square) { 217 ra.paintRects[HORIZONTAL] = paintRect; 218 ra.paintRects[VERTICAL] = null; 219 } 220 } 221 for (int i = 0; i < paintRects.length; i++) { 222 if (ra.paintRects[i] != null 223 && !ra.paintRects[i].isEmpty()) 224 { 225 // Should use separate Graphics for each paint() call, 226 // since paint() can change Graphics state for next call. 227 Graphics g = comp.getGraphics(); 228 if (g != null) { 229 try { 230 g.setClip(ra.paintRects[i]); 231 if (i == UPDATE) { 232 updateComponent(comp, g); 233 } else { 234 if (shouldClearRectBeforePaint) { 235 g.clearRect( ra.paintRects[i].x, 236 ra.paintRects[i].y, 237 ra.paintRects[i].width, 238 ra.paintRects[i].height); 239 } 240 paintComponent(comp, g); 241 } 242 } finally { 243 g.dispose(); 244 } 245 } 246 } 247 } 248 } 249 250 /** 251 * Calls {@code Component.update(Graphics)} with given Graphics. 252 */ 253 protected void updateComponent(Component comp, Graphics g) { 254 if (comp != null) { 255 comp.update(g); 256 } 257 } 258 259 /** 260 * Calls {@code Component.paint(Graphics)} with given Graphics. 261 */ 262 protected void paintComponent(Component comp, Graphics g) { 263 if (comp != null) { 264 comp.paint(g); 265 } 266 } 267 268 /** 269 * Subtracts subtr from rect. If the result is rectangle 270 * changes rect and returns true. Otherwise false. 271 */ 272 static boolean subtract(Rectangle rect, Rectangle subtr) { 273 if (rect == null || subtr == null) { 274 return true; 275 } 276 Rectangle common = rect.intersection(subtr); 277 if (common.isEmpty()) { 278 return true; 279 } 280 if (rect.x == common.x && rect.y == common.y) { 281 if (rect.width == common.width) { 282 rect.y += common.height; 283 rect.height -= common.height; 284 return true; 285 } else 286 if (rect.height == common.height) { 287 rect.x += common.width; 288 rect.width -= common.width; 289 return true; 290 } 291 } else 292 if (rect.x + rect.width == common.x + common.width 293 && rect.y + rect.height == common.y + common.height) 294 { 295 if (rect.width == common.width) { 296 rect.height -= common.height; 297 return true; 298 } else 299 if (rect.height == common.height) { 300 rect.width -= common.width; 301 return true; 302 } 303 } 304 return false; 305 } 306 307 public String toString() { 308 return super.toString() + "[ horizontal=" + paintRects[0] + 309 " vertical=" + paintRects[1] + 310 " update=" + paintRects[2] + "]"; 311 } 312 }