source: josm/trunk/src/org/openstreetmap/josm/gui/util/WindowGeometry.java@ 13963

Last change on this file since 13963 was 13963, checked in by Don-vip, 6 years ago

fix #16410 - workaround for "IllegalArgumentException: Window must not be zero" on Linux (patch by kendzi)

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