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

Last change on this file since 16488 was 16488, checked in by simon04, 4 years ago

fix #19281, see #19174 - Use Objects.hash where it is not used (patch by hiddewie, modified)

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