1 /*
2  * Copyright (c) 1999, 2007, 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</code> 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</code>
63      * @since   1.3
64      */
RepaintArea()65     public RepaintArea() {
66     }
67 
68     /**
69      * Constructs a new <code>RepaintArea</code> initialized to match
70      * the values of the specified RepaintArea.
71      *
72      * @param   ra  the <code>RepaintArea</code> from which to copy initial
73      *              values to a newly constructed RepaintArea
74      * @since   1.3
75      */
RepaintArea(RepaintArea ra)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</code> to this <code>RepaintArea</code>.
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</code>
91      * @param   id  possible values PaintEvent.UPDATE or PaintEvent.PAINT
92      * @since   1.3
93      */
add(Rectangle r, int id)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</code> 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</code> having the same geometry as
117      *          this RepaintArea.
118      * @since   1.3
119      */
cloneAndReset()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 
isEmpty()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      */
constrain(int x, int y, int w, int h)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      */
subtract(int x, int y, int w, int h)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</code> or <code>update</code>
190      * @since   1.4
191      */
paint(Object target, boolean shouldClearRectBeforePaint)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)</code> with given Graphics.
252      */
updateComponent(Component comp, Graphics g)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)</code> with given Graphics.
261      */
paintComponent(Component comp, Graphics g)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      */
subtract(Rectangle rect, Rectangle subtr)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 
toString()307     public String toString() {
308         return super.toString() + "[ horizontal=" + paintRects[0] +
309             " vertical=" + paintRects[1] +
310             " update=" + paintRects[2] + "]";
311     }
312 }
313