001/* An editor for Ptolemy II objects. 002 003 Copyright (c) 1998-2018 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.Component; 031import java.awt.Window; 032import java.util.HashMap; 033import java.util.HashSet; 034import java.util.Iterator; 035import java.util.LinkedList; 036import java.util.List; 037import java.util.Set; 038 039import javax.swing.BoxLayout; 040import javax.swing.JPanel; 041import javax.swing.SwingUtilities; 042 043import ptolemy.data.expr.Parameter; 044import ptolemy.data.type.BaseType; 045import ptolemy.gui.CloseListener; 046import ptolemy.kernel.util.ChangeRequest; 047import ptolemy.kernel.util.Decorator; 048import ptolemy.kernel.util.DecoratorAttributes; 049import ptolemy.kernel.util.IllegalActionException; 050import ptolemy.kernel.util.InternalErrorException; 051import ptolemy.kernel.util.KernelException; 052import ptolemy.kernel.util.NamedObj; 053import ptolemy.kernel.util.Settable; 054import ptolemy.moml.MoMLChangeRequest; 055import ptolemy.util.MessageHandler; 056import ptolemy.util.StringUtilities; 057 058/////////////////////////////////////////////////////////////////// 059//// Configurer 060 061/** 062 This class is an editor for the user settable attributes of an object. 063 It may consist of more than one editor panel. If the object has 064 any attributes that are instances of EditorPaneFactory, then the 065 panes made by those factories are stacked vertically in this panel. 066 Otherwise, a static method of EditorPaneFactory is 067 used to construct a default editor. 068 <p> 069 The restore() method restores the values of the attributes of the 070 object to their values when this object was created. This can be used 071 in a modal dialog to implement a cancel button, which restores 072 the attribute values to those before the dialog was opened. 073 <p> 074 This class is created by an instance of the EditParametersDialog class 075 to handle the part of the dialog that edits the parameters. 076 077 @see EditorPaneFactory 078 @author Steve Neuendorffer and Edward A. Lee 079 @version $Id$ 080 @since Ptolemy II 0.4 081 @Pt.ProposedRating Yellow (eal) 082 @Pt.AcceptedRating Yellow (neuendor) 083 */ 084@SuppressWarnings("serial") 085public class Configurer extends JPanel implements CloseListener { 086 /** Construct a configurer for the specified object. This stores 087 * the current values of any Settable attributes of the given object, 088 * and then defers to any editor pane factories contained by 089 * the given object to populate this panel with widgets that 090 * edit the attributes of the given object. If there are no 091 * editor pane factories, then a default editor pane is created. 092 * @param object The object to configure. 093 */ 094 public Configurer(final NamedObj object) { 095 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 096 //setLayout(new BorderLayout()); 097 _object = object; 098 099 // Record the original values so a restore can happen later. 100 _originalValues = new HashMap<Settable, String>(); 101 Set<Settable> parameters = _getVisibleSettables(object, true); 102 for (Settable parameter : parameters) { 103 _originalValues.put(parameter, parameter.getExpression()); 104 } 105 106 boolean foundOne = false; 107 Iterator<?> editors = object.attributeList(EditorPaneFactory.class) 108 .iterator(); 109 110 while (editors.hasNext()) { 111 foundOne = true; 112 113 EditorPaneFactory editor = (EditorPaneFactory) editors.next(); 114 Component pane = editor.createEditorPane(); 115 add(pane); 116 117 // Inherit the background color from the container. 118 pane.setBackground(null); 119 120 if (pane instanceof CloseListener) { 121 _closeListeners.add(pane); 122 } 123 } 124 125 if (!foundOne) { 126 // There is no attribute of class EditorPaneFactory. 127 // We cannot create one because that would have to be done 128 // as a mutation, something that is very difficult to do 129 // while constructing a modal dialog. Synchronized interactions 130 // between the thread in which the manager performs mutations 131 // and the event dispatch thread prove to be very tricky, 132 // and likely lead to deadlock. Hence, instead, we use 133 // the static method of EditorPaneFactory. 134 Component pane = EditorPaneFactory.createEditorPane(object); 135 add(pane);//, BorderLayout.CENTER); 136 137 // Inherit the background color from the container. 138 pane.setBackground(null); 139 140 if (pane instanceof CloseListener) { 141 _closeListeners.add(pane); 142 } 143 } 144 } 145 146 /////////////////////////////////////////////////////////////////// 147 //// public methods //// 148 149 /** Return true if the given settable should be visible in a 150 * configurer panel for the specified target. Any settable with 151 * visibility FULL or NOT_EDITABLE will be visible. If the target 152 * contains an attribute named "_expertMode", then any 153 * attribute with visibility EXPERT will also be visible. 154 * @param target The object to be configured. 155 * @param settable The object whose visibility is returned. 156 * @return True if settable is FULL or NOT_EDITABLE or True 157 * if the target has an _expertMode attribute and the settable 158 * is EXPERT. Otherwise, return false. 159 */ 160 public static boolean isVisible(NamedObj target, Settable settable) { 161 if (settable.getVisibility() == Settable.FULL 162 || settable.getVisibility() == Settable.NOT_EDITABLE) { 163 return true; 164 } 165 166 if (target.getAttribute("_expertMode") != null 167 && settable.getVisibility() == Settable.EXPERT) { 168 return true; 169 } 170 171 return false; 172 } 173 174 /** Request restoration of the user settable attribute values to what they 175 * were when this object was created. The actual restoration 176 * occurs later, in the UI thread, in order to allow all pending 177 * changes to the attribute values to be processed first. If the original 178 * values match the current values, then nothing is done. 179 */ 180 public void restore() { 181 // This is done in the UI thread in order to 182 // ensure that all pending UI events have been 183 // processed. In particular, some of these events 184 // may trigger notification of new attribute values, 185 // which must not be allowed to occur after this 186 // restore is done. In particular, the default 187 // attribute editor has lines where notification 188 // of updates occurs when the line loses focus. 189 // That notification occurs some time after the 190 // window is destroyed. 191 // FIXME: Unfortunately, this gets 192 // invoked before that notification occurs if the 193 // "X" is used to close the window. Swing bug? 194 SwingUtilities.invokeLater(new Runnable() { 195 @Override 196 public void run() { 197 // First check for changes. 198 199 // FIXME Currently it is not possible to restore decorated attributes 200 // since they don't show up in moml yet. 201 Set<Settable> parameters = _getVisibleSettables(_object, false); 202 boolean hasChanges = false; 203 StringBuffer buffer = new StringBuffer("<group>\n"); 204 205 for (Settable parameter : parameters) { 206 String newValue = parameter.getExpression(); 207 String oldValue = _originalValues.get(parameter); 208 209 if (!newValue.equals(oldValue)) { 210 hasChanges = true; 211 buffer.append("<property name=\""); 212 buffer.append(((NamedObj) parameter).getName(_object)); 213 buffer.append("\" value=\""); 214 buffer.append(StringUtilities.escapeForXML(oldValue)); 215 buffer.append("\"/>\n"); 216 } 217 } 218 219 buffer.append("</group>\n"); 220 221 // If there are changes, then issue a change request. 222 // Use a MoMLChangeRequest so undo works... I.e., you can undo a cancel 223 // of a previous change. 224 if (hasChanges) { 225 MoMLChangeRequest request = new MoMLChangeRequest(this, // originator 226 _object, // context 227 buffer.toString(), // MoML code 228 null); // base 229 _object.requestChange(request); 230 } 231 } 232 }); 233 } 234 235 /** Restore parameter values to their defaults. 236 */ 237 public void restoreToDefaults() { 238 // This is done in the UI thread in order to 239 // ensure that all pending UI events have been 240 // processed. In particular, some of these events 241 // may trigger notification of new attribute values, 242 // which must not be allowed to occur after this 243 // restore is done. In particular, the default 244 // attribute editor has lines where notification 245 // of updates occurs when the line loses focus. 246 // That notification occurs some time after the 247 // window is destroyed. 248 SwingUtilities.invokeLater(new Runnable() { 249 @Override 250 public void run() { 251 // FIXME Currently it is not possible to restore decorated attributes 252 // since they don't show up in moml yet. 253 254 Set<Settable> parameters = _getVisibleSettables(_object, false); 255 StringBuffer buffer = new StringBuffer("<group>\n"); 256 final List<Settable> parametersReset = new LinkedList<Settable>(); 257 258 for (Settable parameter : parameters) { 259 String newValue = parameter.getExpression(); 260 String defaultValue = parameter.getDefaultExpression(); 261 262 if (defaultValue != null 263 && !newValue.equals(defaultValue)) { 264 buffer.append("<property name=\""); 265 buffer.append(((NamedObj) parameter).getName(_object)); 266 buffer.append("\" value=\""); 267 buffer.append( 268 StringUtilities.escapeForXML(defaultValue)); 269 buffer.append("\"/>\n"); 270 parametersReset.add(parameter); 271 } 272 } 273 274 buffer.append("</group>\n"); 275 276 // If there are changes, then issue a change request. 277 // Use a MoMLChangeRequest so undo works... I.e., you can undo a cancel 278 // of a previous change. 279 if (parametersReset.size() > 0) { 280 MoMLChangeRequest request = new MoMLChangeRequest(this, // originator 281 _object, // context 282 buffer.toString(), // MoML code 283 null) { // base 284 @Override 285 protected void _execute() throws Exception { 286 super._execute(); 287 288 // Reset the derived level, which has the side 289 // effect of marking the object not overridden. 290 Iterator<Settable> parameters = parametersReset 291 .iterator(); 292 293 while (parameters.hasNext()) { 294 Settable parameter = parameters.next(); 295 296 if (isVisible(_object, parameter)) { 297 int derivedLevel = ((NamedObj) parameter) 298 .getDerivedLevel(); 299 // This has the side effect of 300 // setting to false the flag that 301 // indicates whether the value of 302 // this object overrides some 303 // inherited value. 304 ((NamedObj) parameter) 305 .setDerivedLevel(derivedLevel); 306 } 307 } 308 } 309 }; 310 311 _object.requestChange(request); 312 } 313 } 314 }); 315 } 316 317 /** Notify any panels in this configurer that implement the 318 * CloseListener interface that the specified window has closed. 319 * The second argument, if non-null, gives the name of the button 320 * that was used to close the window. 321 * @param window The window that closed. 322 * @param button The name of the button that was used to close the window. 323 */ 324 @Override 325 public void windowClosed(Window window, String button) { 326 Iterator<Component> listeners = _closeListeners.iterator(); 327 328 while (listeners.hasNext()) { 329 CloseListener listener = (CloseListener) listeners.next(); 330 listener.windowClosed(window, button); 331 } 332 } 333 334 /////////////////////////////////////////////////////////////////// 335 //// private variables //// 336 337 /** Return the visible Settables of NamedObj object. When 338 * addDecoratorAttributes is true we will also return the 339 * decorated attributes. 340 * In case the passed NamedObj is the top level container, the 341 * parameter enableBackwardTypeInference is added if not present, 342 * with default value false. 343 * @param object The named object for which to show the visible 344 * Settables 345 * @param addDecoratorAttributes A flag that specifies whether 346 * decorated attributes should also be included. 347 * @return The visible attributes. 348 */ 349 private Set<Settable> _getVisibleSettables(final NamedObj object, 350 boolean addDecoratorAttributes) { 351 Set<Settable> attributes = new HashSet<Settable>(); 352 Iterator<?> parameters = object.attributeList(Settable.class) 353 .iterator(); 354 355 // Add parameter enableBackwardTypeInference to top level container 356 if (object.equals(object.toplevel())) { 357 try { 358 Parameter backwardTypeInf = (Parameter) object.getAttribute( 359 "enableBackwardTypeInference", Parameter.class); 360 if (backwardTypeInf == null) { 361 // It is extremely dangerous here to just add a parameter because 362 // that requires getting write permission on the Workspace. 363 // This can cause deadlocks, so this should be done in a ChangeRequest. 364 // Unfortunately, this means that the parameter won't show up immediately. 365 // The user will have to reopen the dialog to have the parameter appear. 366 ChangeRequest request = new ChangeRequest(object, 367 "Add parameter enableBackwardTypeInference") { 368 @Override 369 protected void _execute() throws Exception { 370 Parameter backwardTypeInf = new Parameter(object, 371 "enableBackwardTypeInference"); 372 backwardTypeInf.setExpression("false"); 373 backwardTypeInf.setTypeEquals(BaseType.BOOLEAN); 374 _originalValues.put(backwardTypeInf, "false"); 375 } 376 }; 377 object.requestChange(request); 378 } 379 } catch (KernelException e) { 380 // This should not happen 381 throw new InternalErrorException(e); 382 } 383 } 384 385 while (parameters.hasNext()) { 386 Settable parameter = (Settable) parameters.next(); 387 388 if (isVisible(object, parameter)) { 389 attributes.add(parameter); 390 } 391 } 392 393 if (addDecoratorAttributes) { 394 // Get the decorators that decorate this object, if any. 395 Set<Decorator> decorators; 396 try { 397 decorators = object.decorators(); 398 for (Decorator decorator : decorators) { 399 // Get the attributes provided by the decorator. 400 DecoratorAttributes decoratorAttributes = object 401 .getDecoratorAttributes(decorator); 402 if (decoratorAttributes != null) { 403 for (Object attribute : decoratorAttributes 404 .attributeList()) { 405 if (attribute instanceof Settable) { 406 Settable settable = (Settable) attribute; 407 if (isVisible(object, settable)) { 408 attributes.add(settable); 409 } 410 } 411 } 412 } 413 } 414 } catch (IllegalActionException e) { 415 MessageHandler.error("Invalid decorator value", e); 416 } 417 } 418 return attributes; 419 } 420 421 /////////////////////////////////////////////////////////////////// 422 //// protected variables //// 423 424 /** A record of the original values. */ 425 protected HashMap<Settable, String> _originalValues; 426 427 /////////////////////////////////////////////////////////////////// 428 //// private variables //// 429 430 // A list of panels in this configurer that implement CloseListener, 431 // if there are any. 432 private List<Component> _closeListeners = new LinkedList<Component>(); 433 434 // The object that this configurer configures. 435 private NamedObj _object; 436}