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 }