001/* An attribute representing the size, location, and other window properties. 002 003 Copyright (c) 1998-2014 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.actor.gui; 029 030import java.awt.Frame; 031import java.awt.GraphicsDevice; 032import java.awt.GraphicsEnvironment; 033import java.awt.Rectangle; 034import java.awt.event.ComponentEvent; 035import java.awt.event.ComponentListener; 036import java.lang.ref.WeakReference; 037 038import ptolemy.data.ArrayToken; 039import ptolemy.data.BooleanToken; 040import ptolemy.data.IntToken; 041import ptolemy.data.RecordToken; 042import ptolemy.data.expr.Parameter; 043import ptolemy.gui.Top; 044import ptolemy.kernel.util.IllegalActionException; 045import ptolemy.kernel.util.InternalErrorException; 046import ptolemy.kernel.util.NameDuplicationException; 047import ptolemy.kernel.util.NamedObj; 048import ptolemy.kernel.util.Settable; 049import ptolemy.kernel.util.Workspace; 050 051/////////////////////////////////////////////////////////////////// 052//// WindowPropertiesAttribute 053 054/** 055 This attribute stores properties of a window, including the width, 056 height, and location. The token in this attribute is a RecordToken 057 containing a field "bounds" with a 4-element integer array. 058 There is also a field that indicates whether the window is maximized. 059 By default, this attribute has visibility NONE, so the user will not 060 see it in parameter editing dialogs. 061 062 @author Edward A. Lee, Contributors: Jason E. Smith, Christopher Brooks 063 @version $Id$ 064 @since Ptolemy II 2.1 065 @Pt.ProposedRating Red (eal) 066 @Pt.AcceptedRating Red (johnr) 067 */ 068public class WindowPropertiesAttribute extends Parameter 069 implements ComponentListener { 070 /** Construct an attribute with the given name contained by the specified 071 * entity. The container argument must not be null, or a 072 * NullPointerException will be thrown. This attribute will use the 073 * workspace of the container for synchronization and version counts. 074 * If the name argument is null, then the name is set to the empty string. 075 * Increment the version of the workspace. 076 * @param container The container. 077 * @param name The name of this attribute. 078 * @exception IllegalActionException If the attribute is not of an 079 * acceptable class for the container, or if the name contains a period. 080 * @exception NameDuplicationException If the name coincides with 081 * an attribute already in the container. 082 */ 083 public WindowPropertiesAttribute(NamedObj container, String name) 084 throws IllegalActionException, NameDuplicationException { 085 super(container, name); 086 setVisibility(Settable.NONE); 087 088 // The following line, if uncommented, results in 089 // icons that are defined in an external file always being 090 // exported along with the model that defines them. This bloats 091 // the MoML files with information that is not needed. I suspect 092 // the line was put there because it wasn't clear that you need 093 // to invoke File->Save in a submodel if you want the location 094 // and position of the submodel window to be saved. It is not 095 // sufficient to invoke save just at the top level. 096 // setPersistent(true); 097 } 098 099 /////////////////////////////////////////////////////////////////// 100 //// public methods //// 101 102 /** Clone the attribute into the specified workspace. This calls the 103 * base class and then sets the attribute public members to refer 104 * to the attributes of the new attribute 105 * @param workspace The workspace for the new attribute 106 * @return A new director. 107 * @exception CloneNotSupportedException If a derived class contains 108 * an attribute that cannot be cloned. 109 */ 110 @Override 111 public Object clone(Workspace workspace) throws CloneNotSupportedException { 112 WindowPropertiesAttribute newObject = (WindowPropertiesAttribute) super.clone( 113 workspace); 114 newObject._listeningTo = new WeakReference<Frame>(null); 115 return newObject; 116 } 117 118 /** Do nothing. This method is 119 * invoked when the component has been made invisible. 120 * @param event The component event. 121 */ 122 @Override 123 public void componentHidden(ComponentEvent event) { 124 } 125 126 /** Record the new position. This method is 127 * invoked when the component's position changes. 128 * @param event The component event. 129 */ 130 @Override 131 public void componentMoved(ComponentEvent event) { 132 recordProperties(_listeningTo.get()); 133 } 134 135 /** Record the new size. This method is 136 * invoked when the component's size changes. 137 * @param event The component event. 138 */ 139 @Override 140 public void componentResized(ComponentEvent event) { 141 recordProperties(_listeningTo.get()); 142 } 143 144 /** Do nothing. This method is 145 * invoked when the component has been made visible. 146 * @param event The component event. 147 */ 148 @Override 149 public void componentShown(ComponentEvent event) { 150 } 151 152 /** Set the value of the attribute to match those of the specified 153 * frame. 154 * @param frame The frame whose properties are to be recorded. 155 */ 156 public void recordProperties(Frame frame) { 157 try { 158 Rectangle bounds = frame.getBounds(); 159 160 // Determine whether the window is maximized. 161 boolean maximized = (frame.getExtendedState() 162 & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH; 163 164 // Get the current values. 165 RecordToken value = null; 166 try { 167 value = (RecordToken) getToken(); 168 } catch (IllegalActionException ex) { 169 throw new IllegalActionException(this, ex, 170 "Attempting to get the value of the WindowPropertiesAttribute " 171 + "failed. This can happen when a model with graphical actors " 172 + "is run using MoMLSimpleApplication " 173 + "because MoMLSimpleApplication does not run the model in " 174 + "the Swing event thread. One possibility is that the model " 175 + "needs to be run in Vergil and saved."); 176 } 177 boolean updateValue = false; 178 179 if (value == null) { 180 updateValue = true; 181 } else { 182 ArrayToken boundsToken = (ArrayToken) value.get("bounds"); 183 BooleanToken maximizedToken = (BooleanToken) value 184 .get("maximized"); 185 int x = ((IntToken) boundsToken.getElement(0)).intValue(); 186 int y = ((IntToken) boundsToken.getElement(1)).intValue(); 187 int width = ((IntToken) boundsToken.getElement(2)).intValue(); 188 int height = ((IntToken) boundsToken.getElement(3)).intValue(); 189 190 if (maximizedToken == null) { 191 maximizedToken = BooleanToken.FALSE; 192 } 193 194 // If the new values are different, then do a MoMLChangeRequest. 195 if (maximizedToken.booleanValue() != maximized || x != bounds.x 196 || y != bounds.y || width != bounds.width 197 || height != bounds.height) { 198 updateValue = true; 199 } 200 } 201 202 // Check that the toplevel is non-empty so that if a user 203 // opens Kepler and closes, they are not prompted to save 204 // a blank model. 205 if (updateValue) { 206 207 // Construct values for the record token. 208 String values = "{bounds={" + bounds.x + ", " + bounds.y + ", " 209 + bounds.width + ", " + bounds.height + "}, maximized=" 210 + maximized + "}"; 211 212 // Don't call setToken(), instead use a MoMLChangeRequest so that 213 // the model is marked modified so that any changes are preserved. 214 // See "closing workflow does not save the location change of popup display windows." 215 // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5188 216 // 217 // The problem is that if the user opens a model and 218 // moves the Display actor and then closes it, they 219 // are prompted for saving. 220 // 221 // If the user wants to save the location of the 222 // Display actors, then they should explicitly save the model. 223 setToken(values); 224 225 // String moml = "<property name=\"" + getName() 226 // + "\" value=\"" + values + "\"/>"; 227 228 // MoMLChangeRequest request = new MoMLChangeRequest(this, 229 // getContainer(), moml, false); 230 // getContainer().requestChange(request); 231 // Not clear why the following is needed, but if it isn't there, 232 // then window properties may not be recorded. 233 propagateValue(); 234 } 235 } catch (IllegalActionException ex) { 236 throw new InternalErrorException(this, ex, 237 "Can't set propertes value!"); 238 } 239 } 240 241 /** Set the properties of the specified frame to match the 242 * current value of the attribute. If the value of the attribute 243 * has not been set, then do nothing and return true. If the 244 * value of this attribute is malformed in any way, then just 245 * return false. 246 * 247 * <p>If the x or y position is less than 0 pixels or greater 248 * than (width - 10 pixels) or (height - 10) pixels of the 249 * screen, then offset the position by 30 pixels so the user can 250 * drag the window. 251 * 252 * @param frame The frame whose properties are to be set. 253 * @return True if successful. 254 */ 255 public boolean setProperties(Frame frame) { 256 Frame listeningTo = _listeningTo.get(); 257 if (listeningTo != frame) { 258 if (listeningTo != null) { 259 listeningTo.removeComponentListener(this); 260 } 261 262 frame.addComponentListener(this); 263 _listeningTo.clear(); 264 _listeningTo = new WeakReference<Frame>(frame); 265 } 266 267 try { 268 RecordToken value = (RecordToken) getToken(); 269 270 if (value == null) { 271 return true; 272 } 273 274 ArrayToken boundsToken = (ArrayToken) value.get("bounds"); 275 BooleanToken maximizedToken = (BooleanToken) value.get("maximized"); 276 int x = ((IntToken) boundsToken.getElement(0)).intValue(); 277 int y = ((IntToken) boundsToken.getElement(1)).intValue(); 278 int width = ((IntToken) boundsToken.getElement(2)).intValue(); 279 int height = ((IntToken) boundsToken.getElement(3)).intValue(); 280 281 //System.out.println("x: " + x + " y: " + y); 282 //System.out.println("width: " + width + " height: " + height); 283 284 // FIXME: If we change the size, should we mark the model 285 // as dirty so it gets saved? 286 287 // See ptolemy/actor/gui/test/MyFourCorners.xml for a test file 288 // that produces four plots in the four corners of a multi-screen window. 289 GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment 290 .getLocalGraphicsEnvironment(); 291 GraphicsDevice[] graphicsDevices = graphicsEnvironment 292 .getScreenDevices(); 293 294 int widths[] = new int[graphicsDevices.length]; 295 int heights[] = new int[graphicsDevices.length]; 296 boolean widthsEqual = true; 297 boolean heightsEqual = true; 298 int maxWidth = 0; 299 int maxHeight = 0; 300 301 for (int j = 0; j < graphicsDevices.length; j++) { 302 GraphicsDevice graphicsDevice = graphicsDevices[j]; 303 widths[j] = graphicsDevice.getDisplayMode().getWidth(); 304 heights[j] = graphicsDevice.getDisplayMode().getHeight(); 305 } 306 for (int j = 0; j < graphicsDevices.length - 1; j++) { 307 if (widths[j] != widths[j + 1]) { 308 widthsEqual = false; 309 } 310 if (heights[j] != heights[j + 1]) { 311 heightsEqual = false; 312 } 313 } 314 if (widthsEqual && heightsEqual) { 315 //Nominal setup 316 if (heights[0] > widths[0]) {//Width is cumulative. 317 for (int j = 0; j < graphicsDevices.length; j++) { 318 maxWidth += widths[j]; 319 } 320 maxHeight = heights[0]; 321 } else {//Height is cumulative. 322 for (int j = 0; j < graphicsDevices.length; j++) { 323 maxHeight += heights[j]; 324 } 325 maxWidth = widths[0]; 326 } 327 } else { 328 //Strange setup. 329 maxWidth = widths[0]; 330 maxHeight = heights[0]; 331 for (int j = 0; j < graphicsDevices.length; j++) { 332 maxWidth = maxWidth > widths[j] ? widths[j] : maxWidth; 333 maxHeight = maxHeight > heights[j] ? heights[j] : maxHeight; 334 } 335 } 336 337 x = x < 0 ? 0 : x; 338 y = y < 0 ? 0 : y + 0; 339 340 width = width > maxWidth ? maxWidth : width; 341 height = height > maxHeight ? maxHeight : height; 342 343 y = y + height > maxHeight ? maxHeight - height : y; 344 x = x + width > maxWidth ? maxWidth - width : x; 345 346 frame.setBounds(x, y, width, height); 347 348 if (maximizedToken != null) { 349 boolean maximized = maximizedToken.booleanValue(); 350 351 if (maximized) { 352 // FIXME: Regrettably, this doesn't make the window 353 // actually maximized under Windows, at least. 354 frame.setExtendedState( 355 frame.getExtendedState() | Frame.MAXIMIZED_BOTH); 356 } 357 } 358 359 if (frame instanceof Top) { 360 // Disable centering. 361 ((Top) frame).setCentering(false); 362 } 363 364 return true; 365 } catch (Throwable throwable) { 366 return false; 367 } 368 } 369 370 /////////////////////////////////////////////////////////////////// 371 //// private variables //// 372 373 /** The frame we are listening to. */ 374 private WeakReference<Frame> _listeningTo = new WeakReference<Frame>(null); 375}