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

Last change on this file since 12411 was 12382, checked in by michael2402, 7 years ago

More documentation for the tools package

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