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