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 */ 086 static public class WindowGeometryException extends Exception { 087 public WindowGeometryException(String message, Throwable cause) { 088 super(message, cause); 089 } 090 091 public WindowGeometryException(String message) { 092 super(message); 093 } 094 } 095 096 /** the top left point */ 097 private Point topLeft; 098 /** the size */ 099 private Dimension extent; 100 101 /** 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 * 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 protected int parseField(String preferenceKey, String preferenceValue, String field) throws WindowGeometryException { 130 String v = ""; 131 try { 132 Pattern p = Pattern.compile(field + "=(-?\\d+)",Pattern.CASE_INSENSITIVE); 133 Matcher m = p.matcher(preferenceValue); 134 if (!m.find()) 135 throw new WindowGeometryException(tr("Preference with key ''{0}'' does not include ''{1}''. Cannot restore window geometry from preferences.", preferenceKey, field)); 136 v = m.group(1); 137 return Integer.parseInt(v); 138 } catch(WindowGeometryException e) { 139 throw e; 140 } catch(NumberFormatException e) { 141 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)); 142 } catch(Exception e) { 143 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); 144 } 145 } 146 147 protected void initFromPreferences(String preferenceKey) throws WindowGeometryException { 148 String value = Main.pref.get(preferenceKey); 149 if (value == null || value.equals("")) 150 throw new WindowGeometryException(tr("Preference with key ''{0}'' does not exist. Cannot restore window geometry from preferences.", preferenceKey)); 151 topLeft = new Point(); 152 extent = new Dimension(); 153 topLeft.x = parseField(preferenceKey, value, "x"); 154 topLeft.y = parseField(preferenceKey, value, "y"); 155 extent.width = parseField(preferenceKey, value, "width"); 156 extent.height = parseField(preferenceKey, value, "height"); 157 } 158 159 protected void initFromWindowGeometry(WindowGeometry other) { 160 this.topLeft = other.topLeft; 161 this.extent = other.extent; 162 } 163 164 static public WindowGeometry mainWindow(String preferenceKey, String arg, boolean maximize) { 165 Rectangle screenDimension = getScreenInfo("gui.geometry"); 166 if (arg != null) { 167 final Matcher m = Pattern.compile("(\\d+)x(\\d+)(([+-])(\\d+)([+-])(\\d+))?").matcher(arg); 168 if (m.matches()) { 169 int w = Integer.valueOf(m.group(1)); 170 int h = Integer.valueOf(m.group(2)); 171 int x = screenDimension.x, y = screenDimension.y; 172 if (m.group(3) != null) { 173 x = Integer.valueOf(m.group(5)); 174 y = Integer.valueOf(m.group(7)); 175 if (m.group(4).equals("-")) { 176 x = screenDimension.x + screenDimension.width - x - w; 177 } 178 if (m.group(6).equals("-")) { 179 y = screenDimension.y + screenDimension.height - y - h; 180 } 181 } 182 return new WindowGeometry(new Point(x,y), new Dimension(w,h)); 183 } else { 184 Main.warn(tr("Ignoring malformed geometry: {0}", arg)); 185 } 186 } 187 WindowGeometry def; 188 if(maximize) { 189 def = new WindowGeometry(screenDimension); 190 } else { 191 Point p = screenDimension.getLocation(); 192 p.x += (screenDimension.width-1000)/2; 193 p.y += (screenDimension.height-740)/2; 194 def = new WindowGeometry(p, new Dimension(1000, 740)); 195 } 196 return new WindowGeometry(preferenceKey, def); 197 } 198 199 /** 200 * Creates a window geometry from the values kept in the preference store under the 201 * key <code>preferenceKey</code> 202 * 203 * @param preferenceKey the preference key 204 * @throws WindowGeometryException thrown if no such key exist or if the preference value has 205 * an illegal format 206 */ 207 public WindowGeometry(String preferenceKey) throws WindowGeometryException { 208 initFromPreferences(preferenceKey); 209 } 210 211 /** 212 * Creates a window geometry from the values kept in the preference store under the 213 * key <code>preferenceKey</code>. Falls back to the <code>defaultGeometry</code> if 214 * something goes wrong. 215 * 216 * @param preferenceKey the preference key 217 * @param defaultGeometry the default geometry 218 * 219 */ 220 public WindowGeometry(String preferenceKey, WindowGeometry defaultGeometry) { 221 try { 222 initFromPreferences(preferenceKey); 223 } catch(WindowGeometryException e) { 224 initFromWindowGeometry(defaultGeometry); 225 } 226 } 227 228 /** 229 * Remembers a window geometry under a specific preference key 230 * 231 * @param preferenceKey the preference key 232 */ 233 public void remember(String preferenceKey) { 234 StringBuffer value = new StringBuffer(); 235 value.append("x=").append(topLeft.x).append(",") 236 .append("y=").append(topLeft.y).append(",") 237 .append("width=").append(extent.width).append(",") 238 .append("height=").append(extent.height); 239 Main.pref.put(preferenceKey, value.toString()); 240 } 241 242 /** 243 * Replies the top left point for the geometry 244 * 245 * @return the top left point for the geometry 246 */ 247 public Point getTopLeft() { 248 return topLeft; 249 } 250 251 /** 252 * Replies the size spezified by the geometry 253 * 254 * @return the size spezified by the geometry 255 */ 256 public Dimension getSize() { 257 return extent; 258 } 259 260 private Rectangle getRectangle() { 261 return new Rectangle(topLeft, extent); 262 } 263 264 /** 265 * Applies this geometry to a window. Makes sure that the window is not 266 * placed outside of the coordinate range of all available screens. 267 * 268 * @param window the window 269 */ 270 public void applySafe(Window window) { 271 Point p = new Point(topLeft); 272 273 Rectangle virtualBounds = new Rectangle(); 274 GraphicsEnvironment ge = GraphicsEnvironment 275 .getLocalGraphicsEnvironment(); 276 GraphicsDevice[] gs = ge.getScreenDevices(); 277 for (int j = 0; j < gs.length; j++) { 278 GraphicsDevice gd = gs[j]; 279 GraphicsConfiguration[] gc = gd.getConfigurations(); 280 for (int i = 0; i < gc.length; i++) { 281 virtualBounds = virtualBounds.union(gc[i].getBounds()); 282 } 283 } 284 285 if (p.x < virtualBounds.x) { 286 p.x = virtualBounds.x; 287 } else if (p.x > virtualBounds.x + virtualBounds.width - extent.width) { 288 p.x = virtualBounds.x + virtualBounds.width - extent.width; 289 } 290 291 if (p.y < virtualBounds.y) { 292 p.y = virtualBounds.y; 293 } else if (p.y > virtualBounds.y + virtualBounds.height - extent.height) { 294 p.y = virtualBounds.y + virtualBounds.height - extent.height; 295 } 296 297 window.setLocation(p); 298 window.setSize(extent); 299 } 300 301 /** 302 * Find the size and position of the screen for given coordinates. Use first screen, 303 * when no coordinates are stored or null is passed. 304 * 305 * @param preferenceKey the key to get size and position from 306 */ 307 public static Rectangle getScreenInfo(String preferenceKey) { 308 Rectangle g = new WindowGeometry(preferenceKey, 309 /* default: something on screen 1 */ 310 new WindowGeometry(new Point(0,0), new Dimension(10,10))).getRectangle(); 311 GraphicsEnvironment ge = GraphicsEnvironment 312 .getLocalGraphicsEnvironment(); 313 GraphicsDevice[] gs = ge.getScreenDevices(); 314 int intersect = 0; 315 Rectangle bounds = null; 316 for (int j = 0; j < gs.length; j++) { 317 GraphicsDevice gd = gs[j]; 318 GraphicsConfiguration[] gc = gd.getConfigurations(); 319 for (int i = 0; i < gc.length; i++) { 320 Rectangle b = gc[i].getBounds(); 321 if(b.width/b.height >= 3) /* multiscreen with wrong definition */ 322 { 323 b.width /= 2; 324 Rectangle is = b.intersection(g); 325 int s = is.width*is.height; 326 if(bounds == null || intersect < s) { 327 intersect = s; 328 bounds = b; 329 } 330 b = new Rectangle(b); 331 b.x += b.width; 332 is = b.intersection(g); 333 s = is.width*is.height; 334 if(bounds == null || intersect < s) { 335 intersect = s; 336 bounds = b; 337 } 338 } 339 else 340 { 341 Rectangle is = b.intersection(g); 342 int s = is.width*is.height; 343 if(bounds == null || intersect < s) { 344 intersect = s; 345 bounds = b; 346 } 347 } 348 } 349 } 350 return bounds; 351 } 352 353 /** 354 * Find the size of the full virtual screen. 355 */ 356 public static Rectangle getFullScreenInfo() { 357 return new Rectangle(new Point(0,0), Toolkit.getDefaultToolkit().getScreenSize()); 358 } 359 360 public String toString() { 361 return "WindowGeometry{topLeft="+topLeft+",extent="+extent+"}"; 362 } 363 }