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}