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

Last change on this file since 10594 was 10384, checked in by Don-vip, 8 years ago

fix #12954 - IllegalComponentStateException with detached dialogs

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