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

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

code refactoring for unit tests / headless mode

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