source: josm/trunk/src/org/openstreetmap/josm/tools/WindowGeometry.java@ 6870

Last change on this file since 6870 was 6828, checked in by Don-vip, 10 years ago

fix #9699 - workaround to java bug in getMaximumWindowBounds() (patch by kendzi)

  • Property svn:eol-style set to native
File size: 16.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.Dimension;
8import java.awt.GraphicsDevice;
9import java.awt.GraphicsEnvironment;
10import java.awt.Point;
11import java.awt.Rectangle;
12import java.awt.Toolkit;
13import java.awt.Window;
14import java.util.regex.Matcher;
15import java.util.regex.Pattern;
16
17import org.openstreetmap.josm.Main;
18
19/**
20 * This is a helper class for persisting the geometry of a JOSM window to the preference store
21 * and for restoring it from the preference store.
22 *
23 */
24public class WindowGeometry {
25
26 /**
27 * Replies a window geometry object for a window with a specific size which is
28 * centered on screen, where main window is
29 *
30 * @param extent the size
31 * @return the geometry object
32 */
33 static public WindowGeometry centerOnScreen(Dimension extent) {
34 return centerOnScreen(extent, "gui.geometry");
35 }
36
37 /**
38 * Replies a window geometry object for a window with a specific size which is
39 * centered on screen where the corresponding window is.
40 *
41 * @param extent the size
42 * @param preferenceKey the key to get window size and position from, null value format
43 * for whole virtual screen
44 * @return the geometry object
45 */
46 static public WindowGeometry centerOnScreen(Dimension extent, String preferenceKey) {
47 Rectangle size = preferenceKey != null ? getScreenInfo(preferenceKey)
48 : getFullScreenInfo();
49 Point topLeft = new Point(
50 size.x + Math.max(0, (size.width - extent.width) /2),
51 size.y + Math.max(0, (size.height - extent.height) /2)
52 );
53 return new WindowGeometry(topLeft, extent);
54 }
55
56 /**
57 * Replies a window geometry object for a window with a specific size which is centered
58 * relative to the parent window of a reference component.
59 *
60 * @param reference the reference component.
61 * @param extent the size
62 * @return the geometry object
63 */
64 static public WindowGeometry centerInWindow(Component reference, Dimension extent) {
65 Window parentWindow = null;
66 while(reference != null && ! (reference instanceof Window) ) {
67 reference = reference.getParent();
68 }
69 if (reference == null)
70 return new WindowGeometry(new Point(0,0), extent);
71 parentWindow = (Window)reference;
72 Point topLeft = new Point(
73 Math.max(0, (parentWindow.getSize().width - extent.width) /2),
74 Math.max(0, (parentWindow.getSize().height - extent.height) /2)
75 );
76 topLeft.x += parentWindow.getLocation().x;
77 topLeft.y += parentWindow.getLocation().y;
78 return new WindowGeometry(topLeft, extent);
79 }
80
81 /**
82 * Exception thrown by the WindowGeometry class if something goes wrong
83 */
84 static public class WindowGeometryException extends Exception {
85 public WindowGeometryException(String message, Throwable cause) {
86 super(message, cause);
87 }
88
89 public WindowGeometryException(String message) {
90 super(message);
91 }
92 }
93
94 /** the top left point */
95 private Point topLeft;
96 /** the size */
97 private Dimension extent;
98
99 /**
100 * Creates a window geometry from a position and dimension
101 *
102 * @param topLeft the top left point
103 * @param extent the extent
104 */
105 public WindowGeometry(Point topLeft, Dimension extent) {
106 this.topLeft = topLeft;
107 this.extent = extent;
108 }
109
110 /**
111 * Creates a window geometry from a rectangle
112 *
113 * @param rect the position
114 */
115 public WindowGeometry(Rectangle rect) {
116 this.topLeft = rect.getLocation();
117 this.extent = rect.getSize();
118 }
119
120 /**
121 * Creates a window geometry from the position and the size of a window.
122 *
123 * @param window the window
124 */
125 public WindowGeometry(Window window) {
126 this(window.getLocationOnScreen(), window.getSize());
127 }
128
129 /**
130 * Fixes a window geometry to shift to the correct screen.
131 *
132 * @param window the window
133 */
134 public void fixScreen(Window window) {
135 Rectangle oldScreen = getScreenInfo(getRectangle());
136 Rectangle newScreen = getScreenInfo(new Rectangle(window.getLocationOnScreen(), window.getSize()));
137 if(oldScreen.x != newScreen.x) {
138 this.topLeft.x += newScreen.x - oldScreen.x;
139 }
140 if(oldScreen.y != newScreen.y) {
141 this.topLeft.y += newScreen.y - oldScreen.y;
142 }
143 }
144
145 protected int parseField(String preferenceKey, String preferenceValue, String field) throws WindowGeometryException {
146 String v = "";
147 try {
148 Pattern p = Pattern.compile(field + "=(-?\\d+)",Pattern.CASE_INSENSITIVE);
149 Matcher m = p.matcher(preferenceValue);
150 if (!m.find())
151 throw new WindowGeometryException(tr("Preference with key ''{0}'' does not include ''{1}''. Cannot restore window geometry from preferences.", preferenceKey, field));
152 v = m.group(1);
153 return Integer.parseInt(v);
154 } catch(WindowGeometryException e) {
155 throw e;
156 } catch(NumberFormatException e) {
157 throw new WindowGeometryException(tr("Preference with key ''{0}'' does not provide an int value for ''{1}''. Got {2}. Cannot restore window geometry from preferences.", preferenceKey, field, v));
158 } catch(Exception e) {
159 throw new WindowGeometryException(tr("Failed to parse field ''{1}'' in preference with key ''{0}''. Exception was: {2}. Cannot restore window geometry from preferences.", preferenceKey, field, e.toString()), e);
160 }
161 }
162
163 protected void initFromPreferences(String preferenceKey) throws WindowGeometryException {
164 String value = Main.pref.get(preferenceKey);
165 if (value == null || value.isEmpty())
166 throw new WindowGeometryException(tr("Preference with key ''{0}'' does not exist. Cannot restore window geometry from preferences.", preferenceKey));
167 topLeft = new Point();
168 extent = new Dimension();
169 topLeft.x = parseField(preferenceKey, value, "x");
170 topLeft.y = parseField(preferenceKey, value, "y");
171 extent.width = parseField(preferenceKey, value, "width");
172 extent.height = parseField(preferenceKey, value, "height");
173 }
174
175 protected void initFromWindowGeometry(WindowGeometry other) {
176 this.topLeft = other.topLeft;
177 this.extent = other.extent;
178 }
179
180 static public WindowGeometry mainWindow(String preferenceKey, String arg, boolean maximize) {
181 Rectangle screenDimension = getScreenInfo("gui.geometry");
182 if (arg != null) {
183 final Matcher m = Pattern.compile("(\\d+)x(\\d+)(([+-])(\\d+)([+-])(\\d+))?").matcher(arg);
184 if (m.matches()) {
185 int w = Integer.valueOf(m.group(1));
186 int h = Integer.valueOf(m.group(2));
187 int x = screenDimension.x, y = screenDimension.y;
188 if (m.group(3) != null) {
189 x = Integer.valueOf(m.group(5));
190 y = Integer.valueOf(m.group(7));
191 if (m.group(4).equals("-")) {
192 x = screenDimension.x + screenDimension.width - x - w;
193 }
194 if (m.group(6).equals("-")) {
195 y = screenDimension.y + screenDimension.height - y - h;
196 }
197 }
198 return new WindowGeometry(new Point(x,y), new Dimension(w,h));
199 } else {
200 Main.warn(tr("Ignoring malformed geometry: {0}", arg));
201 }
202 }
203 WindowGeometry def;
204 if(maximize) {
205 def = new WindowGeometry(screenDimension);
206 } else {
207 Point p = screenDimension.getLocation();
208 p.x += (screenDimension.width-1000)/2;
209 p.y += (screenDimension.height-740)/2;
210 def = new WindowGeometry(p, new Dimension(1000, 740));
211 }
212 return new WindowGeometry(preferenceKey, def);
213 }
214
215 /**
216 * Creates a window geometry from the values kept in the preference store under the
217 * key <code>preferenceKey</code>
218 *
219 * @param preferenceKey the preference key
220 * @throws WindowGeometryException thrown if no such key exist or if the preference value has
221 * an illegal format
222 */
223 public WindowGeometry(String preferenceKey) throws WindowGeometryException {
224 initFromPreferences(preferenceKey);
225 }
226
227 /**
228 * Creates a window geometry from the values kept in the preference store under the
229 * key <code>preferenceKey</code>. Falls back to the <code>defaultGeometry</code> if
230 * something goes wrong.
231 *
232 * @param preferenceKey the preference key
233 * @param defaultGeometry the default geometry
234 *
235 */
236 public WindowGeometry(String preferenceKey, WindowGeometry defaultGeometry) {
237 try {
238 initFromPreferences(preferenceKey);
239 } catch(WindowGeometryException e) {
240 initFromWindowGeometry(defaultGeometry);
241 }
242 }
243
244 /**
245 * Remembers a window geometry under a specific preference key
246 *
247 * @param preferenceKey the preference key
248 */
249 public void remember(String preferenceKey) {
250 StringBuilder value = new StringBuilder();
251 value.append("x=").append(topLeft.x).append(",")
252 .append("y=").append(topLeft.y).append(",")
253 .append("width=").append(extent.width).append(",")
254 .append("height=").append(extent.height);
255 Main.pref.put(preferenceKey, value.toString());
256 }
257
258 /**
259 * Replies the top left point for the geometry
260 *
261 * @return the top left point for the geometry
262 */
263 public Point getTopLeft() {
264 return topLeft;
265 }
266
267 /**
268 * Replies the size specified by the geometry
269 *
270 * @return the size specified by the geometry
271 */
272 public Dimension getSize() {
273 return extent;
274 }
275
276 /**
277 * Replies the size and position specified by the geometry
278 *
279 * @return the size and position specified by the geometry
280 */
281 private Rectangle getRectangle() {
282 return new Rectangle(topLeft, extent);
283 }
284
285 /**
286 * Applies this geometry to a window. Makes sure that the window is not
287 * placed outside of the coordinate range of all available screens.
288 *
289 * @param window the window
290 */
291 public void applySafe(Window window) {
292 Point p = new Point(topLeft);
293 Dimension size = new Dimension(extent);
294
295 Rectangle virtualBounds = getVirtualScreenBounds();
296
297 // Ensure window fit on screen
298
299 if (p.x < virtualBounds.x) {
300 p.x = virtualBounds.x;
301 } else if (p.x > virtualBounds.x + virtualBounds.width - size.width) {
302 p.x = virtualBounds.x + virtualBounds.width - size.width;
303 }
304
305 if (p.y < virtualBounds.y) {
306 p.y = virtualBounds.y;
307 } else if (p.y > virtualBounds.y + virtualBounds.height - size.height) {
308 p.y = virtualBounds.y + virtualBounds.height - size.height;
309 }
310
311 int deltax = (p.x + size.width) - (virtualBounds.x + virtualBounds.width);
312 if (deltax > 0) {
313 size.width -= deltax;
314 }
315
316 int deltay = (p.y + size.height) - (virtualBounds.y + virtualBounds.height);
317 if (deltay > 0) {
318 size.height -= deltay;
319 }
320
321 // Ensure window does not hide taskbar
322
323 Rectangle maxbounds = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
324
325 if (!isBugInMaximumWindowBounds(maxbounds)) {
326 deltax = size.width - maxbounds.width;
327 if (deltax > 0) {
328 size.width -= deltax;
329 }
330
331 deltay = size.height - maxbounds.height;
332 if (deltay > 0) {
333 size.height -= deltay;
334 }
335 }
336 window.setLocation(p);
337 window.setSize(size);
338 }
339
340 /**
341 * Determines if the bug affecting getMaximumWindowBounds() occured.
342 *
343 * @param maxbounds result of getMaximumWindowBounds()
344 * @return {@code true} if the bug happened, {@code false otherwise}
345 *
346 * @see <a href="https://josm.openstreetmap.de/ticket/9699">JOSM-9699</a>
347 * @see <a href="https://bugs.launchpad.net/ubuntu/+source/openjdk-7/+bug/1171563">Ubuntu-1171563</a>
348 * @see <a href="http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=1669">IcedTea-1669</a>
349 * @see <a href="https://bugs.openjdk.java.net/browse/JI-9010334">JI-9010334</a>
350 */
351 protected static boolean isBugInMaximumWindowBounds(Rectangle maxbounds) {
352 return maxbounds.width <= 0 || maxbounds.height <= 0;
353 }
354
355 /**
356 * Computes the virtual bounds of graphics environment, as an union of all screen bounds.
357 * @return The virtual bounds of graphics environment, as an union of all screen bounds.
358 * @since 6522
359 */
360 public static Rectangle getVirtualScreenBounds() {
361 Rectangle virtualBounds = new Rectangle();
362 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
363 for (GraphicsDevice gd : ge.getScreenDevices()) {
364 if (gd.getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
365 virtualBounds = virtualBounds.union(gd.getDefaultConfiguration().getBounds());
366 }
367 }
368 return virtualBounds;
369 }
370
371 /**
372 * Find the size and position of the screen for given coordinates. Use first screen,
373 * when no coordinates are stored or null is passed.
374 *
375 * @param preferenceKey the key to get size and position from
376 * @return bounds of the screen
377 */
378 public static Rectangle getScreenInfo(String preferenceKey) {
379 Rectangle g = new WindowGeometry(preferenceKey,
380 /* default: something on screen 1 */
381 new WindowGeometry(new Point(0,0), new Dimension(10,10))).getRectangle();
382 return getScreenInfo(g);
383 }
384
385 /**
386 * Find the size and position of the screen for given coordinates. Use first screen,
387 * when no coordinates are stored or null is passed.
388 *
389 * @param g coordinates to check
390 * @return bounds of the screen
391 */
392 private static Rectangle getScreenInfo(Rectangle g) {
393 GraphicsEnvironment ge = GraphicsEnvironment
394 .getLocalGraphicsEnvironment();
395 GraphicsDevice[] gs = ge.getScreenDevices();
396 int intersect = 0;
397 Rectangle bounds = null;
398 for (GraphicsDevice gd : gs) {
399 if (gd.getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
400 Rectangle b = gd.getDefaultConfiguration().getBounds();
401 if (b.height > 0 && b.width / b.height >= 3) /* multiscreen with wrong definition */ {
402 b.width /= 2;
403 Rectangle is = b.intersection(g);
404 int s = is.width * is.height;
405 if (bounds == null || intersect < s) {
406 intersect = s;
407 bounds = b;
408 }
409 b = new Rectangle(b);
410 b.x += b.width;
411 is = b.intersection(g);
412 s = is.width * is.height;
413 if (bounds == null || intersect < s) {
414 intersect = s;
415 bounds = b;
416 }
417 } else {
418 Rectangle is = b.intersection(g);
419 int s = is.width * is.height;
420 if (bounds == null || intersect < s) {
421 intersect = s;
422 bounds = b;
423 }
424 }
425 }
426 }
427 return bounds;
428 }
429
430 /**
431 * Find the size of the full virtual screen.
432 * @return size of the full virtual screen
433 */
434 public static Rectangle getFullScreenInfo() {
435 return new Rectangle(new Point(0,0), Toolkit.getDefaultToolkit().getScreenSize());
436 }
437
438 @Override
439 public String toString() {
440 return "WindowGeometry{topLeft="+topLeft+",extent="+extent+"}";
441 }
442}
Note: See TracBrowser for help on using the repository browser.