001/* A top-level dialog window for editing parameters of a NamedObj. 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 */ 027package ptolemy.actor.gui; 028 029import java.awt.Frame; 030import java.net.URL; 031import java.util.Iterator; 032import java.util.List; 033 034import javax.swing.SwingUtilities; 035 036import ptolemy.actor.gui.style.StyleConfigurer; 037import ptolemy.data.expr.StringParameter; 038import ptolemy.gui.ComponentDialog; 039import ptolemy.gui.Query; 040import ptolemy.kernel.util.Attribute; 041import ptolemy.kernel.util.ChangeListener; 042import ptolemy.kernel.util.ChangeRequest; 043import ptolemy.kernel.util.IllegalActionException; 044import ptolemy.kernel.util.NamedObj; 045import ptolemy.kernel.util.Settable; 046import ptolemy.moml.MoMLChangeRequest; 047import ptolemy.util.CancelException; 048import ptolemy.util.MessageHandler; 049import ptolemy.util.StringUtilities; 050 051/////////////////////////////////////////////////////////////////// 052//// EditParametersDialog 053 054/** 055 This class is a modal dialog box for editing the parameters of a 056 target object, which is an instance of NamedObj. All attributes that 057 implement the Settable interface and have visibility FULL or 058 NOT_EDITABLE are included in the dialog. An instance of this class 059 contains an instance of Configurer, which examines the target for 060 attributes of type EditorPaneFactory. Those attributes, if they are 061 present, define the panels that are used to edit the parameters of the 062 target. If they are not present, then a default panel is created. 063 064 <p> If the panels returned by EditorPaneFactory implement the 065 CloseListener interface, then they are notified when this dialog 066 is closed, and are informed of which button (if any) was used to 067 close the dialog. 068 069 <p> The dialog is modal, so that (in lieu of a proper undo mechanism) 070 the Cancel button can properly undo any modifications that are made. 071 This means that the statement that creates the dialog will not return 072 until the user dismisses the dialog. The method buttonPressed() can 073 then be called to find out whether the user clicked the Commit button 074 or the Cancel button (or any other button specified in the 075 constructor). Then you can access the component to determine what 076 values were set by the user. 077 078 @author Edward A. Lee 079 @version $Id$ 080 @since Ptolemy II 1.0 081 @Pt.ProposedRating Yellow (eal) 082 @Pt.AcceptedRating Yellow (neuendor) 083 */ 084@SuppressWarnings("serial") 085public class EditParametersDialog extends ComponentDialog 086 implements ChangeListener { 087 /** Construct a dialog with the specified owner and target. 088 * A "Commit" and a "Cancel" button are added to the dialog. 089 * The dialog is placed relative to the owner. 090 * @param owner The object that, per the user, appears to be 091 * generating the dialog. 092 * @param target The object whose parameters are being edited. 093 */ 094 public EditParametersDialog(Frame owner, NamedObj target) { 095 this(owner, target, "Edit parameters for " + target.getName()); 096 } 097 098 /** Construct a dialog with the specified owner and target. 099 * A "Commit" and a "Cancel" button are added to the dialog. 100 * The dialog is placed relative to the owner. 101 * @param owner The object that, per the user, appears to be 102 * generating the dialog. 103 * @param target The object whose parameters are being edited. 104 * @param label The label for the dialog box. 105 */ 106 public EditParametersDialog(Frame owner, NamedObj target, String label) { 107 super(owner, label, new Configurer(target), _moreButtons); 108 109 // Once we get to here, the dialog has already been dismissed. 110 _owner = owner; 111 _target = target; 112 113 if (buttonPressed().equals("Add")) { 114 _openAddDialog(null, "", "", "ptolemy.data.expr.Parameter"); 115 _target.removeChangeListener(this); 116 } else if (buttonPressed().equals("Remove")) { 117 // Create a new dialog to remove a parameter, then open a new 118 // EditParametersDialog. 119 // First, create a string array with the names of all the 120 // parameters. 121 List<Settable> attributeList = _target 122 .attributeList(Settable.class); 123 124 // Count visible attributes 125 Iterator<Settable> parameters = attributeList.iterator(); 126 int count = 0; 127 128 while (parameters.hasNext()) { 129 Settable parameter = parameters.next(); 130 131 if (Configurer.isVisible(target, parameter)) { 132 count++; 133 } 134 } 135 136 String[] attributeNames = new String[count]; 137 parameters = attributeList.iterator(); 138 139 int index = 0; 140 141 while (parameters.hasNext()) { 142 Settable parameter = parameters.next(); 143 144 if (Configurer.isVisible(target, parameter)) { 145 attributeNames[index++] = ((Attribute) parameter).getName(); 146 } 147 } 148 149 Query query = new Query(); 150 query.addChoice("delete", "Parameter to delete", attributeNames, 151 null, false); 152 153 ComponentDialog dialog = new ComponentDialog(_owner, 154 "Delete a parameter for " + _target.getFullName(), query, 155 null); 156 157 // If the OK button was pressed, then queue a mutation 158 // to delete the parameter. 159 String deleteName = query.getStringValue("delete"); 160 161 if (dialog.buttonPressed().equals("OK") && !deleteName.equals("")) { 162 String moml = "<deleteProperty name=\"" + deleteName + "\"/>"; 163 _target.addChangeListener(this); 164 165 MoMLChangeRequest request = new MoMLChangeRequest(this, _target, 166 moml); 167 request.setUndoable(true); 168 _target.requestChange(request); 169 } 170 } else if (buttonPressed().equals("Defaults")) { 171 ((Configurer) contents).restoreToDefaults(); 172 173 // Open a new dialog (a modal dialog). 174 new EditParametersDialog(_owner, _target); 175 } else if (buttonPressed().equals("Preferences")) { 176 // Create a dialog for setting parameter styles. 177 try { 178 StyleConfigurer panel = new StyleConfigurer(target); 179 ComponentDialog dialog = new ComponentDialog(_owner, 180 "Edit preferences for " + target.getName(), panel); 181 182 if (!dialog.buttonPressed().equals("OK")) { 183 // Restore original parameter values. 184 panel.restore(); 185 } 186 187 new EditParametersDialog(_owner, _target); 188 189 // NOTE: Instead of te above line, this used 190 // to do the following. This isn't quite right because it violates 191 // the modal dialog premise, since this method will 192 // return and then a new dialog will open. 193 // In particular, the preferences manager relies on the 194 // modal behavior of the dialog. I'm sure other places do to. 195 // EAL 7/05. 196 // _reOpen(); 197 } catch (IllegalActionException ex) { 198 MessageHandler.error("Edit Parameter Styles failed", ex); 199 } 200 } else if (buttonPressed().equals("Help")) { 201 String helpURL = _getHelpURL(); 202 203 try { 204 URL doc = getClass().getClassLoader().getResource(helpURL); 205 206 // Try to use the configuration, if we can. 207 boolean success = false; 208 209 if (_owner instanceof TableauFrame) { 210 // According to FindBugs the cast is an error: 211 // [M D BC] Unchecked/unconfirmed cast [BC_UNCONFIRMED_CAST] 212 // However it is checked that _owner instanceof TableauFrame, 213 // so FindBugs is wrong. 214 215 Configuration configuration = ((TableauFrame) _owner) 216 .getConfiguration(); 217 218 if (configuration != null) { 219 configuration.openModel(null, doc, 220 doc.toExternalForm()); 221 success = true; 222 } 223 } 224 225 if (!success) { 226 // Just open an HTML page. 227 HTMLViewer viewer = new HTMLViewer(); 228 viewer.setPage(doc); 229 viewer.pack(); 230 viewer.show(); 231 } 232 } catch (Exception ex) { 233 try { 234 MessageHandler.warning( 235 "Cannot open help page \"" + helpURL + "\".", ex); 236 } catch (CancelException exception) { 237 // Ignore the cancel. 238 } 239 } 240 } 241 } 242 243 /////////////////////////////////////////////////////////////////// 244 //// public methods //// 245 246 /** React to the fact that a change has been successfully executed. 247 * This method opens a new parameter editor to replace the one that 248 * was closed. 249 * @param change The change that was executed. 250 */ 251 @Override 252 public void changeExecuted(ChangeRequest change) { 253 // Ignore if this is not the originator. 254 if (change == null || change.getSource() != this) { 255 return; 256 } 257 258 // Open a new dialog. 259 // NOTE: this is ugly. It is necessary because the dialog 260 // has been dismissed. 261 // NOTE: Do this in the event thread, since this might be invoked 262 // in whatever thread is processing mutations. 263 Runnable changeExecutedRunnable = new ChangeExecutedRunnable(); 264 SwingUtilities.invokeLater(changeExecutedRunnable); 265 _target.removeChangeListener(this); 266 } 267 268 /** Notify the listener that a change has resulted in an exception. 269 * @param change The change that was attempted. 270 * @param exception The exception that resulted. 271 */ 272 @Override 273 public void changeFailed(ChangeRequest change, final Exception exception) { 274 // Ignore if this is not the originator. 275 if (change == null || change.getSource() != this) { 276 return; 277 } 278 279 _target.removeChangeListener(this); 280 281 if (change.isErrorReported()) { 282 // Error has already been reported. 283 return; 284 } 285 286 change.setErrorReported(true); 287 288 // NOTE: Do this in the event thread, since this might be invoked 289 // in whatever thread is processing mutations. 290 Runnable changeFailedRunnable = new ChangeFailedRunnable(exception); 291 SwingUtilities.invokeLater(changeFailedRunnable); 292 } 293 294 /** Do the layout and then pack. 295 */ 296 public void doLayoutAndPack() { 297 // The doLayoutAndPack method is declared in the 298 // ptolemy.gui.EditableParametersDialog interface. 299 // That interface is necessary to avoid a dependency 300 // between ptolemy.gui.Query and this class. 301 doLayout(); 302 pack(); 303 } 304 305 /////////////////////////////////////////////////////////////////// 306 //// protected methods //// 307 308 /** If the contents of this dialog implements the CloseListener 309 * interface, then notify it that the window has closed. 310 */ 311 @Override 312 protected void _handleClosing() { 313 super._handleClosing(); 314 315 if (!buttonPressed().equals("Commit") && !buttonPressed().equals("Add") 316 && !buttonPressed().equals("Preferences") 317 && !buttonPressed().equals("Help") 318 && !buttonPressed().equals("Remove")) { 319 // Restore original parameter values. 320 ((Configurer) contents).restore(); 321 } 322 } 323 324 /** Open a dialog to add a new parameter. 325 * @param message A message to place at the top, or null if none. 326 * @param name The default name. 327 * @param defValue The default value. 328 * @param className The default class name. 329 * @return The dialog that is created. 330 */ 331 protected ComponentDialog _openAddDialog(String message, String name, 332 String defValue, String className) { 333 // Create a new dialog to add a parameter, then open a new 334 // EditParametersDialog. 335 _query = new Query(); 336 337 if (message != null) { 338 _query.setMessage(message); 339 } 340 341 _query.addChoice("class", "Class", 342 new String[] { "ptolemy.data.expr.Parameter", 343 "ptolemy.data.expr.FileParameter", 344 "ptolemy.kernel.util.StringAttribute", 345 "ptolemy.actor.gui.ColorAttribute" }, 346 "ptolemy.data.expr.Parameter", true); 347 348 _query.addLine("name", "Name", name); 349 _query.addLine("default", "Default value", defValue); 350 351 ComponentDialog dialog = new ComponentDialog(_owner, 352 "Add a new parameter to " + _target.getFullName(), _query, 353 null); 354 355 String parameterClass = _query.getStringValue("class"); 356 357 // If the OK button was pressed, then queue a mutation 358 // to create the parameter. 359 // A blank property name is interpreted as a cancel. 360 String newName = _query.getStringValue("name"); 361 362 // Need to escape quotes in default value. 363 String newDefValue = StringUtilities 364 .escapeForXML(_query.getStringValue("default")); 365 366 if (dialog.buttonPressed().equals("OK") && !newName.equals("")) { 367 String moml = "<property name=\"" + newName + "\" value=\"" 368 + newDefValue + "\" class=\"" + parameterClass + "\"/>"; 369 _target.addChangeListener(this); 370 371 MoMLChangeRequest request = new MoMLChangeRequest(this, _target, 372 moml); 373 request.setUndoable(true); 374 _target.requestChange(request); 375 } 376 return dialog; 377 378 } 379 380 /////////////////////////////////////////////////////////////////// 381 //// inner classes //// 382 383 /** A runnable for the change executed event. */ 384 class ChangeExecutedRunnable implements Runnable { 385 @Override 386 public void run() { 387 new EditParametersDialog(_owner, _target); 388 } 389 } 390 391 /** A runnable for the change failed event. */ 392 class ChangeFailedRunnable implements Runnable { 393 public ChangeFailedRunnable(Exception exception) { 394 _exception = exception; 395 } 396 397 @Override 398 public void run() { 399 // When a parameter is removed, and something depends on 400 // it, this gets called when _query is null. 401 // FIXME: Is this the right thing to do? 402 if (_query == null) { 403 return; 404 } 405 406 String newName = _query.getStringValue("name"); 407 ComponentDialog dialog = _openAddDialog( 408 _exception.getMessage() 409 + "\n\nPlease enter a new default value:", 410 newName, _query.getStringValue("default"), 411 _query.getStringValue("class")); 412 _target.removeChangeListener(EditParametersDialog.this); 413 414 if (!dialog.buttonPressed().equals("OK")) { 415 // Remove the parameter, since it seems to be erroneous 416 // and the user hit cancel or close. 417 String moml = "<deleteProperty name=\"" + newName + "\"/>"; 418 MoMLChangeRequest request = new MoMLChangeRequest(this, _target, 419 moml); 420 request.setUndoable(true); 421 _target.requestChange(request); 422 } 423 } 424 425 private Exception _exception; 426 } 427 428 /////////////////////////////////////////////////////////////////// 429 //// protected variable //// 430 431 /** The owner window. */ 432 protected Frame _owner; 433 434 /** The query window for adding parameters. */ 435 protected Query _query; 436 437 /** The target object whose parameters are being edited. */ 438 protected NamedObj _target; 439 440 /////////////////////////////////////////////////////////////////// 441 //// private methods //// 442 443 /** Returns the URL of the help file to be displayed when the user 444 * clicks the Help button. The help file defaults to the expression 445 * language documentation, but can be overridden by attaching a 446 * parameter {@code _helpURL} to the target object. 447 * @return URL of the help file to be displayed, parsable by the 448 * Ptolemy class loader. 449 */ 450 private String _getHelpURL() { 451 // Look for a _helpURL parameter attached to the target object 452 List<StringParameter> attributeList = _target 453 .attributeList(StringParameter.class); 454 for (StringParameter attribute : attributeList) { 455 if (attribute.getName().equals("_helpURL")) { 456 try { 457 return attribute.stringValue(); 458 } catch (IllegalActionException ex) { 459 try { 460 MessageHandler.warning( 461 "Couldn't access help URL parameter.", ex); 462 } catch (CancelException exception) { 463 // Ignore the cancel. 464 } 465 } 466 } 467 } 468 469 // We couldn't find one, so return the default path to the 470 // expression language help 471 return "doc/expressions.htm"; 472 } 473 474 /** Open a new dialog in a change request that defers 475 * to the Swing thread. This ensures no race conditions 476 * when we are re-opening a dialog to display the result 477 * of an edit change. 478 */ 479 // private void _reOpen() { 480 // ChangeRequest reOpen = new ChangeRequest(this, 481 // "Re-open configure dialog") { 482 // protected void _execute() throws Exception { 483 // SwingUtilities.invokeLater(new Runnable() { 484 // public void run() { 485 // new EditParametersDialog(_owner, _target); 486 // } 487 // }); 488 // } 489 // }; 490 // 491 // _target.requestChange(reOpen); 492 // } 493 494 /////////////////////////////////////////////////////////////////// 495 //// private variables //// 496 // Button labels. 497 private static String[] _moreButtons = { "Commit", "Add", "Remove", 498 "Defaults", "Preferences", "Help", "Cancel" }; 499}