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    }