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

Last change on this file since 13691 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

  • Property svn:eol-style set to native
File size: 19.2 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
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 window.setLocation(p);
360 window.setSize(size);
361 }
362
363 /**
364 * Determines if the bug affecting getMaximumWindowBounds() occured.
365 *
366 * @param maxbounds result of getMaximumWindowBounds()
367 * @return {@code true} if the bug happened, {@code false otherwise}
368 *
369 * @see <a href="https://josm.openstreetmap.de/ticket/9699">JOSM-9699</a>
370 * @see <a href="https://bugs.launchpad.net/ubuntu/+source/openjdk-7/+bug/1171563">Ubuntu-1171563</a>
371 * @see <a href="http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=1669">IcedTea-1669</a>
372 * @see <a href="https://bugs.openjdk.java.net/browse/JDK-8034224">JDK-8034224</a>
373 */
374 protected static boolean isBugInMaximumWindowBounds(Rectangle maxbounds) {
375 return maxbounds.width <= 0 || maxbounds.height <= 0;
376 }
377
378 /**
379 * Computes the virtual bounds of graphics environment, as an union of all screen bounds.
380 * @return The virtual bounds of graphics environment, as an union of all screen bounds.
381 * @since 6522
382 */
383 public static Rectangle getVirtualScreenBounds() {
384 Rectangle virtualBounds = new Rectangle();
385 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
386 if (!GraphicsEnvironment.isHeadless()) {
387 for (GraphicsDevice gd : ge.getScreenDevices()) {
388 if (gd.getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
389 virtualBounds = virtualBounds.union(gd.getDefaultConfiguration().getBounds());
390 }
391 }
392 }
393 return virtualBounds;
394 }
395
396 /**
397 * Computes the maximum dimension for a component to fit in screen displaying {@code component}.
398 * @param component The component to get current screen info from. Must not be {@code null}
399 * @return the maximum dimension for a component to fit in current screen
400 * @throws IllegalArgumentException if {@code component} is null
401 * @since 7463
402 */
403 public static Dimension getMaxDimensionOnScreen(JComponent component) {
404 CheckParameterUtil.ensureParameterNotNull(component, "component");
405 // Compute max dimension of current screen
406 Dimension result = new Dimension();
407 GraphicsConfiguration gc = component.getGraphicsConfiguration();
408 if (gc == null && Main.parent != null) {
409 gc = Main.parent.getGraphicsConfiguration();
410 }
411 if (gc != null) {
412 // Max displayable dimension (max screen dimension - insets)
413 Rectangle bounds = gc.getBounds();
414 Insets insets = component.getToolkit().getScreenInsets(gc);
415 result.width = bounds.width - insets.left - insets.right;
416 result.height = bounds.height - insets.top - insets.bottom;
417 }
418 return result;
419 }
420
421 /**
422 * Find the size and position of the screen for given coordinates. Use first screen,
423 * when no coordinates are stored or null is passed.
424 *
425 * @param preferenceKey the key to get size and position from
426 * @return bounds of the screen
427 */
428 public static Rectangle getScreenInfo(String preferenceKey) {
429 Rectangle g = new WindowGeometry(preferenceKey,
430 /* default: something on screen 1 */
431 new WindowGeometry(new Point(0, 0), new Dimension(10, 10))).getRectangle();
432 return getScreenInfo(g);
433 }
434
435 /**
436 * Find the size and position of the screen for given coordinates. Use first screen,
437 * when no coordinates are stored or null is passed.
438 *
439 * @param g coordinates to check
440 * @return bounds of the screen
441 */
442 private static Rectangle getScreenInfo(Rectangle g) {
443 Rectangle bounds = null;
444 if (!GraphicsEnvironment.isHeadless()) {
445 int intersect = 0;
446 for (GraphicsDevice gd : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) {
447 if (gd.getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
448 Rectangle b = gd.getDefaultConfiguration().getBounds();
449 if (b.height > 0 && b.width / b.height >= 3) /* multiscreen with wrong definition */ {
450 b.width /= 2;
451 Rectangle is = b.intersection(g);
452 int s = is.width * is.height;
453 if (bounds == null || intersect < s) {
454 intersect = s;
455 bounds = b;
456 }
457 b = new Rectangle(b);
458 b.x += b.width;
459 is = b.intersection(g);
460 s = is.width * is.height;
461 if (intersect < s) {
462 intersect = s;
463 bounds = b;
464 }
465 } else {
466 Rectangle is = b.intersection(g);
467 int s = is.width * is.height;
468 if (bounds == null || intersect < s) {
469 intersect = s;
470 bounds = b;
471 }
472 }
473 }
474 }
475 }
476 return bounds != null ? bounds : g;
477 }
478
479 /**
480 * Find the size of the full virtual screen.
481 * @return size of the full virtual screen
482 */
483 public static Rectangle getFullScreenInfo() {
484 return new Rectangle(new Point(0, 0), GuiHelper.getScreenSize());
485 }
486
487 @Override
488 public int hashCode() {
489 final int prime = 31;
490 int result = 1;
491 result = prime * result + ((extent == null) ? 0 : extent.hashCode());
492 result = prime * result + ((topLeft == null) ? 0 : topLeft.hashCode());
493 return result;
494 }
495
496 @Override
497 public boolean equals(Object obj) {
498 if (this == obj)
499 return true;
500 if (obj == null || getClass() != obj.getClass())
501 return false;
502 WindowGeometry other = (WindowGeometry) obj;
503 if (extent == null) {
504 if (other.extent != null)
505 return false;
506 } else if (!extent.equals(other.extent))
507 return false;
508 if (topLeft == null) {
509 if (other.topLeft != null)
510 return false;
511 } else if (!topLeft.equals(other.topLeft))
512 return false;
513 return true;
514 }
515
516 @Override
517 public String toString() {
518 return "WindowGeometry{topLeft="+topLeft+",extent="+extent+'}';
519 }
520}
Note: See TracBrowser for help on using the repository browser.