001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.tools;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005
006 import java.awt.Component;
007 import java.awt.Dimension;
008 import java.awt.GraphicsConfiguration;
009 import java.awt.GraphicsDevice;
010 import java.awt.GraphicsEnvironment;
011 import java.awt.Point;
012 import java.awt.Rectangle;
013 import java.awt.Toolkit;
014 import java.awt.Window;
015 import java.util.regex.Matcher;
016 import java.util.regex.Pattern;
017
018 import org.openstreetmap.josm.Main;
019
020 /**
021 * This is a helper class for persisting the geometry of a JOSM window to the preference store
022 * and for restoring it from the preference store.
023 *
024 */
025 public class WindowGeometry {
026
027 /**
028 * Replies a window geometry object for a window with a specific size which is
029 * centered on screen, where main window is
030 *
031 * @param extent the size
032 * @return the geometry object
033 */
034 static public WindowGeometry centerOnScreen(Dimension extent) {
035 return centerOnScreen(extent, "gui.geometry");
036 }
037
038 /**
039 * Replies a window geometry object for a window with a specific size which is
040 * centered on screen where the corresponding window is.
041 *
042 * @param extent the size
043 * @param preferenceKey the key to get window size and position from, null value format
044 * for whole virtual screen
045 * @return the geometry object
046 */
047 static public WindowGeometry centerOnScreen(Dimension extent, String preferenceKey) {
048 Rectangle size = preferenceKey != null ? getScreenInfo(preferenceKey)
049 : getFullScreenInfo();
050 Point topLeft = new Point(
051 size.x + Math.max(0, (size.width - extent.width) /2),
052 size.y + Math.max(0, (size.height - extent.height) /2)
053 );
054 return new WindowGeometry(topLeft, extent);
055 }
056
057 /**
058 * Replies a window geometry object for a window with a specific size which is centered
059 * relative to the parent window of a reference component.
060 *
061 * @param reference the reference component.
062 * @param extent the size
063 * @return the geometry object
064 */
065 static public WindowGeometry centerInWindow(Component reference, Dimension extent) {
066 Window parentWindow = null;
067 while(reference != null && ! (reference instanceof Window) ) {
068 reference = reference.getParent();
069 }
070 if (reference == null)
071 return new WindowGeometry(new Point(0,0), extent);
072 parentWindow = (Window)reference;
073 Point topLeft = new Point(
074 Math.max(0, (parentWindow.getSize().width - extent.width) /2),
075 Math.max(0, (parentWindow.getSize().height - extent.height) /2)
076 );
077 topLeft.x += parentWindow.getLocation().x;
078 topLeft.y += parentWindow.getLocation().y;
079 return new WindowGeometry(topLeft, extent);
080 }
081
082 /**
083 * Exception thrown by the WindowGeometry class if something goes wrong
084 */
085 static public class WindowGeometryException extends Exception {
086 public WindowGeometryException(String message, Throwable cause) {
087 super(message, cause);
088 }
089
090 public WindowGeometryException(String message) {
091 super(message);
092 }
093 }
094
095 /** the top left point */
096 private Point topLeft;
097 /** the size */
098 private Dimension extent;
099
100 /**
101 * Creates a window geometry from a position and dimension
102 *
103 * @param topLeft the top left point
104 * @param extent the extent
105 */
106 public WindowGeometry(Point topLeft, Dimension extent) {
107 this.topLeft = topLeft;
108 this.extent = extent;
109 }
110
111 /**
112 * Creates a window geometry from a rectangle
113 *
114 * @param rect the position
115 */
116 public WindowGeometry(Rectangle rect) {
117 this.topLeft = rect.getLocation();
118 this.extent = 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(tr("Preference with key ''{0}'' does not include ''{1}''. Cannot restore window geometry from preferences.", preferenceKey, field));
153 v = m.group(1);
154 return Integer.parseInt(v);
155 } catch(WindowGeometryException e) {
156 throw e;
157 } catch(NumberFormatException e) {
158 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));
159 } catch(Exception e) {
160 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);
161 }
162 }
163
164 protected void initFromPreferences(String preferenceKey) throws WindowGeometryException {
165 String value = Main.pref.get(preferenceKey);
166 if (value == null || value.equals(""))
167 throw new WindowGeometryException(tr("Preference with key ''{0}'' does not exist. Cannot restore window geometry from preferences.", preferenceKey));
168 topLeft = new Point();
169 extent = new Dimension();
170 topLeft.x = parseField(preferenceKey, value, "x");
171 topLeft.y = parseField(preferenceKey, value, "y");
172 extent.width = parseField(preferenceKey, value, "width");
173 extent.height = parseField(preferenceKey, value, "height");
174 }
175
176 protected void initFromWindowGeometry(WindowGeometry other) {
177 this.topLeft = other.topLeft;
178 this.extent = other.extent;
179 }
180
181 static public WindowGeometry mainWindow(String preferenceKey, String arg, boolean maximize) {
182 Rectangle screenDimension = getScreenInfo("gui.geometry");
183 if (arg != null) {
184 final Matcher m = Pattern.compile("(\\d+)x(\\d+)(([+-])(\\d+)([+-])(\\d+))?").matcher(arg);
185 if (m.matches()) {
186 int w = Integer.valueOf(m.group(1));
187 int h = Integer.valueOf(m.group(2));
188 int x = screenDimension.x, y = screenDimension.y;
189 if (m.group(3) != null) {
190 x = Integer.valueOf(m.group(5));
191 y = Integer.valueOf(m.group(7));
192 if (m.group(4).equals("-")) {
193 x = screenDimension.x + screenDimension.width - x - w;
194 }
195 if (m.group(6).equals("-")) {
196 y = screenDimension.y + screenDimension.height - y - h;
197 }
198 }
199 return new WindowGeometry(new Point(x,y), new Dimension(w,h));
200 } else {
201 Main.warn(tr("Ignoring malformed geometry: {0}", arg));
202 }
203 }
204 WindowGeometry def;
205 if(maximize) {
206 def = new WindowGeometry(screenDimension);
207 } else {
208 Point p = screenDimension.getLocation();
209 p.x += (screenDimension.width-1000)/2;
210 p.y += (screenDimension.height-740)/2;
211 def = new WindowGeometry(p, new Dimension(1000, 740));
212 }
213 return new WindowGeometry(preferenceKey, def);
214 }
215
216 /**
217 * Creates a window geometry from the values kept in the preference store under the
218 * key <code>preferenceKey</code>
219 *
220 * @param preferenceKey the preference key
221 * @throws WindowGeometryException thrown if no such key exist or if the preference value has
222 * an illegal format
223 */
224 public WindowGeometry(String preferenceKey) throws WindowGeometryException {
225 initFromPreferences(preferenceKey);
226 }
227
228 /**
229 * Creates a window geometry from the values kept in the preference store under the
230 * key <code>preferenceKey</code>. Falls back to the <code>defaultGeometry</code> if
231 * something goes wrong.
232 *
233 * @param preferenceKey the preference key
234 * @param defaultGeometry the default geometry
235 *
236 */
237 public WindowGeometry(String preferenceKey, WindowGeometry defaultGeometry) {
238 try {
239 initFromPreferences(preferenceKey);
240 } catch(WindowGeometryException e) {
241 initFromWindowGeometry(defaultGeometry);
242 }
243 }
244
245 /**
246 * Remembers a window geometry under a specific preference key
247 *
248 * @param preferenceKey the preference key
249 */
250 public void remember(String preferenceKey) {
251 StringBuffer value = new StringBuffer();
252 value.append("x=").append(topLeft.x).append(",")
253 .append("y=").append(topLeft.y).append(",")
254 .append("width=").append(extent.width).append(",")
255 .append("height=").append(extent.height);
256 Main.pref.put(preferenceKey, value.toString());
257 }
258
259 /**
260 * Replies the top left point for the geometry
261 *
262 * @return the top left point for the geometry
263 */
264 public Point getTopLeft() {
265 return topLeft;
266 }
267
268 /**
269 * Replies the size specified by the geometry
270 *
271 * @return the size specified by the geometry
272 */
273 public Dimension getSize() {
274 return extent;
275 }
276
277 /**
278 * Replies the size and position specified by the geometry
279 *
280 * @return the size and position specified by the geometry
281 */
282 private Rectangle getRectangle() {
283 return new Rectangle(topLeft, extent);
284 }
285
286 /**
287 * Applies this geometry to a window. Makes sure that the window is not
288 * placed outside of the coordinate range of all available screens.
289 *
290 * @param window the window
291 */
292 public void applySafe(Window window) {
293 Point p = new Point(topLeft);
294
295 Rectangle virtualBounds = new Rectangle();
296 GraphicsEnvironment ge = GraphicsEnvironment
297 .getLocalGraphicsEnvironment();
298 GraphicsDevice[] gs = ge.getScreenDevices();
299 for (int j = 0; j < gs.length; j++) {
300 GraphicsDevice gd = gs[j];
301 if (gd.getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
302 virtualBounds = virtualBounds.union(gd.getDefaultConfiguration().getBounds());
303 }
304 }
305
306 if (p.x < virtualBounds.x) {
307 p.x = virtualBounds.x;
308 } else if (p.x > virtualBounds.x + virtualBounds.width - extent.width) {
309 p.x = virtualBounds.x + virtualBounds.width - extent.width;
310 }
311
312 if (p.y < virtualBounds.y) {
313 p.y = virtualBounds.y;
314 } else if (p.y > virtualBounds.y + virtualBounds.height - extent.height) {
315 p.y = virtualBounds.y + virtualBounds.height - extent.height;
316 }
317
318 window.setLocation(p);
319 window.setSize(extent);
320 }
321
322 /**
323 * Find the size and position of the screen for given coordinates. Use first screen,
324 * when no coordinates are stored or null is passed.
325 *
326 * @param preferenceKey the key to get size and position from
327 * @return bounds of the screen
328 */
329 public static Rectangle getScreenInfo(String preferenceKey) {
330 Rectangle g = new WindowGeometry(preferenceKey,
331 /* default: something on screen 1 */
332 new WindowGeometry(new Point(0,0), new Dimension(10,10))).getRectangle();
333 return getScreenInfo(g);
334 }
335
336 /**
337 * Find the size and position of the screen for given coordinates. Use first screen,
338 * when no coordinates are stored or null is passed.
339 *
340 * @param g coordinates to check
341 * @return bounds of the screen
342 */
343 private static Rectangle getScreenInfo(Rectangle g) {
344 GraphicsEnvironment ge = GraphicsEnvironment
345 .getLocalGraphicsEnvironment();
346 GraphicsDevice[] gs = ge.getScreenDevices();
347 int intersect = 0;
348 Rectangle bounds = null;
349 for (int j = 0; j < gs.length; j++) {
350 GraphicsDevice gd = gs[j];
351 if (gd.getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
352 Rectangle b = gd.getDefaultConfiguration().getBounds();
353 if (b.height > 0 && b.width/b.height >= 3) /* multiscreen with wrong definition */
354 {
355 b.width /= 2;
356 Rectangle is = b.intersection(g);
357 int s = is.width*is.height;
358 if(bounds == null || intersect < s) {
359 intersect = s;
360 bounds = b;
361 }
362 b = new Rectangle(b);
363 b.x += b.width;
364 is = b.intersection(g);
365 s = is.width*is.height;
366 if(bounds == null || intersect < s) {
367 intersect = s;
368 bounds = b;
369 }
370 }
371 else
372 {
373 Rectangle is = b.intersection(g);
374 int s = is.width*is.height;
375 if(bounds == null || intersect < s) {
376 intersect = s;
377 bounds = b;
378 }
379 }
380 }
381 }
382 return bounds;
383 }
384
385 /**
386 * Find the size of the full virtual screen.
387 * @return size of the full virtual screen
388 */
389 public static Rectangle getFullScreenInfo() {
390 return new Rectangle(new Point(0,0), Toolkit.getDefaultToolkit().getScreenSize());
391 }
392
393 public String toString() {
394 return "WindowGeometry{topLeft="+topLeft+",extent="+extent+"}";
395 }
396 }