/* * Copyright 2003-2010 Tufts University Licensed under the * Educational Community License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. */ package tufts.vue.gui; import tufts.Util; import java.awt.*; /** * The Screen class encapuslates information about a single * screen/display. E.g., a computer with two monitors connected * and powered up should return Screen[2] from getAllScreens(). * * Currently does not provide for any handling of HeadlessException's * * @version $Revision: 1.3 $ / $Date: 2010-02-03 19:15:46 $ / $Author: mike $ * @author Scott Fraize */ public class Screen { private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(Screen.class); /** normally 0,0 for single display systems, but could be anything if there are multiple displays */ public final int x, y; /** logical pixel width and height irrespective of any operating system software margins (e.g., insets for docks, etc) */ public final int width, height; /** The same bounds expressed by x,y,width,height, but as insets. top always == y, left always == x */ public final int top, left, bottom, right; /** The relative pixel insets as determined by Toolkit.getScreenInsets(GraphicsConfiguration) * Note that the members of Insets are mutable, but doing so will have no effect except to make toString() lie */ public final Insets margin; /** convience totals: topIn = x + margin.top, leftIn = x + margin.left, rightIn = right - margin.right, etc */ public final int topIn, leftIn, bottomIn, rightIn; /** normally the relevant device, but could be null for user-created logical screens */ public final GraphicsDevice device; /** convenience call to java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment() */ public static GraphicsEnvironment genv() { return GraphicsEnvironment.getLocalGraphicsEnvironment(); } public static Screen[] getAllScreens() { final GraphicsDevice[] devices = genv().getScreenDevices(); final Screen[] screens = new Screen[devices.length]; for (int i = 0; i < devices.length; i++) { screens[i] = Screen.create(devices[i]); } return screens; } public static GraphicsDevice getDefaultDevice() { return genv().getDefaultScreenDevice(); } /** If the given screen # exists, return it, otherwise return null */ public static Screen getScreen(int n) { final GraphicsDevice[] devices = genv().getScreenDevices(); for (int i = 0; i < devices.length; i++) { if (i == n) return Screen.create(devices[i]); } return null; } /** @return the GraphicsDevice the given window is currently displayed on. * * In the case where the Window currently overlaps two physical or logical devices, * the device the window is "on" is determined by the device which is displaying the * greatest portion of the total area of the Window * * If the given Window is null, this return the default device. */ public static GraphicsDevice getDeviceForWindow(Window w) { if (w == null) return getDefaultDevice(); final GraphicsDevice[] devices = genv().getScreenDevices(); if (devices.length < 2) return getDefaultDevice(); return getDeviceForRegion(devices, w.getBounds()); } /** @return the GraphicsDevice the given region is currently displayed on. * * In the case where the region currently overlaps two physical or logical devices, * the device the region is "on" is determined by the device which is displaying the * greatest portion of the total area of the region. * * If the given region is null or empty, this returns the default device. */ public static GraphicsDevice getDeviceForRegion(final Rectangle region) { if (region == null || region.isEmpty()) return getDefaultDevice(); else return getDeviceForRegion(genv().getScreenDevices(), region); } private static GraphicsDevice getDeviceForRegion(final GraphicsDevice[] devices, final Rectangle region) { if (devices.length < 2 || region == null || region.isEmpty()) return getDefaultDevice(); GraphicsDevice selected = null; // scan all screen devices, and find the one that this // window most overlaps: int maxArea = 0; for (int i = 0; i < devices.length; i++) { GraphicsDevice device = devices[i]; try { GraphicsConfiguration config = device.getDefaultConfiguration(); Rectangle overlap = config.getBounds().intersection(region); int area = overlap.width * overlap.height; if (area > maxArea) { maxArea = area; selected = device; } } catch (Throwable t) { Log.error("scanning device: " + Util.tags(device)); } } return selected == null ? getDefaultDevice() : selected; } /** @return the device the given point is on, or the default device if not on any screen */ public static GraphicsDevice getDeviceForPoint(final Point point) { if (point == null) return getDefaultDevice(); final GraphicsDevice[] devices = genv().getScreenDevices(); if (devices.length < 2) return getDefaultDevice(); // scan all screen devices, and find the one that this point is on: for (int i = 0; i < devices.length; i++) { GraphicsDevice device = devices[i]; try { GraphicsConfiguration config = device.getDefaultConfiguration(); if (config.getBounds().contains(point)) return device; } catch (Throwable t) { Log.error("scanning device: " + Util.tags(device)); } } return getDefaultDevice(); } public static Screen getScreenForPoint(final Point point) { return Screen.create(getDeviceForPoint(point)); } public static Screen getScreenForWindow(Window w) { return Screen.create(getDeviceForWindow(w)); } public static Screen create(GraphicsDevice device) { // note: the config changes when the display mode changes (e.g., resolution change) final GraphicsConfiguration config = device.getDefaultConfiguration(); // note: the screen insets may change at any time due to user changes (e.g., dock hiding) final Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(config); return new Screen(config.getBounds(), insets, device); } /** create's a purely logical screen definition w/out a device */ public static Screen create(Rectangle bounds, Insets insets) { return new Screen(bounds, insets, null); } /** @param device may be null -- only needed for makeWindowFullScreen, otherwise provided as pass-through information */ Screen(final Rectangle b, final Insets insets, final GraphicsDevice device) { this.device = device; this.width = b.width; this.height = b.height; this.top = this.y = b.y; this.left = this.x = b.x; this.bottom = top + height; this.right = left + width; this.topIn = top + insets.top; this.leftIn = left + insets.left; this.bottomIn = bottom - insets.bottom; this.rightIn = right - insets.right; this.margin = insets; } public GraphicsDevice getDevice() { return device; } public void makeWindowCentered(Window w) { w.setLocation(x + (width - w.getWidth()) / 2, y + (height - w.getHeight()) / 2); } /** uses getMaxWindowBounds */ public void makeWindowMaximized(Window w) { w.setBounds(getMaxWindowBounds()); } /** uses getVisibleBounds */ public void makeWindowFillVisible(Window w) { w.setBounds(getVisibleBounds()); } /** uses native full-screen mode if available (e.g., allows menu-bar hiding on the mac, and usually blanks other screens) */ public void makeWindowFullScreen(Window w) { device.setFullScreenWindow(w); } public Rectangle getBounds() { return new Rectangle(x, y, width, height); } /** @return the bounds as insets offset in the global space of all screens */ public Insets getAsInsets() { return new Insets(top, left, bottom, right); } /** @return the bounds of the maximum sized window that will not overlap any screen insets (e.g., menu bars, docks, etc) */ public Rectangle getMaxWindowBounds() { return new Rectangle(leftIn, // x + insets.left topIn, // y + insets.top rightIn - leftIn, // width - (insets.left + insets.right) bottomIn - topIn); // height - (insets.top + insets.bottom) } /** for now, this only works to create a window that doesn't get clipped by the mac menu bar, but fills the rest of the screen */ public Rectangle getVisibleBounds() { return new Rectangle(left, topIn, // y + insets.top width, height - (topIn-top)); // margin contents are exposed as mutable, so we re-compute margin.top } // todo: the below atXXX/inXXX could be smarter by accounting for // other nearby screens e.g., if off-screen at any side, and there // isn't a screen off to the side, those coordinates could be // considered to be "at" that side, which would be useful for many // cases. E.g., "atTop" in a vanilla single-screen setup could be // true for checking the y coordinate -1, but false if there was a // screen above it. public boolean atTop(int y) { return y == top; } public boolean atLeft(int x) { return x == left; } public boolean atBottom(int y) { return y == bottom; } public boolean atRight(int x) { return x == right; } public boolean inTop(int y) { return y >= top && y <= topIn; } public boolean inLeft(int x) { return x >= left && x <= leftIn; } public boolean inBottom(int y) { return y <= bottom && y >= bottomIn; } public boolean inRight(int x) { return x <= right && x >= rightIn; } @Override public String toString() { StringBuilder s = new StringBuilder (String.format("Screen[%dx%d @ %d,%d %s", width, height, x, y, (device==null?"n/a":Util.quote(device.getIDstring())))); // warning: margin is exposed as mutable -- it's possible for this report to be incorrect as to actual behavior if (margin.hashCode() == 0) { s.append(']'); } else { if (margin.top != 0) s.append(" top="+margin.top); else s.append(' '); if (margin.left != 0) s.append(",left="+margin.left); if (margin.bottom != 0) s.append(",bottom="+margin.bottom); if (margin.right != 0) s.append(",right="+margin.right); s.append(']'); } return s.toString(); } /** @return the smallest single rectangle that when filled would * fill ALL attached displays. If the displays do not have have * perfect logical alignment and compatible sizes, then some * regions of the rectangle will actually be off-screen (never * visible). This can be used to size a singe window to cover all * displays. */ public static Rectangle getSpaceBounds() { return getAllDeviceBounds(genv().getScreenDevices()); } // todo: add something like getSpaceFit that returns the largest // rectangle that does NOT leave anything off screen. /** @see getSpaceBounds */ public static Rectangle getAllDeviceBounds(GraphicsDevice[] devices) { Rectangle bounds = null; for (int i = 0; i < devices.length; i++) { final GraphicsDevice device = devices[i]; final GraphicsConfiguration config = device.getDefaultConfiguration(); final Rectangle newBounds = config.getBounds(); if (bounds == null) { bounds = new Rectangle(newBounds); } else { bounds.add(newBounds); } } return bounds; } public static Insets boundsToInsets(Rectangle r) { return new Insets(r.y, r.x, r.y + r.height, r.x + r.width); } public static void dumpOut() { dump(System.out); } public static void dump(java.io.PrintStream ps) { Rectangle sb = getSpaceBounds(); ps.println(Screen.class + " report:" + "\n\t environment: " + Util.tags(genv()) + "\n\tenviroMaxWin: " + genv().getMaximumWindowBounds() + "\n\t allBounds: " + sb + "\n\t asInsets: " + boundsToInsets(sb) ); Screen[] screens = getAllScreens(); for (int i = 0; i < screens.length; i++) { ps.format("\tscreen %d: %s max(%s)\n", i, screens[i], Util.fmt(screens[i].getMaxWindowBounds())); } } }