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

Last change on this file since 10178 was 10115, checked in by Don-vip, 8 years ago

fix coverity 1011666, 1011667, 1011836, 1011837, 1011838

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