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

Last change on this file since 12799 was 12678, checked in by Don-vip, 7 years ago

see #15182 - move WindowGeometry from tools to gui.util

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