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

Last change on this file since 7693 was 7463, checked in by Don-vip, 10 years ago

fix #10392 - rework of MenuScroller to replace static scrollCount approach by dynamic behaviour (fix regressions from #10207)

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