001/* A top-level dialog window for configuring the ports of an entity.
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
028// In addition, ValidatingJTextFieldCellEditor is based on
029// IntegerEditor from
030// http://download.oracle.com/javase/tutorial/uiswing/examples/components/TableFTFEditDemoProject/src/components/IntegerEditor.java
031// so the following copyright applies:
032
033/*
034* Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
035*
036* Redistribution and use in source and binary forms, with or without
037* modification, are permitted provided that the following conditions
038* are met:
039*
040*   - Redistributions of source code must retain the above copyright
041*     notice, this list of conditions and the following disclaimer.
042*
043*   - Redistributions in binary form must reproduce the above copyright
044*     notice, this list of conditions and the following disclaimer in the
045*     documentation and/or other materials provided with the distribution.
046*
047*   - Neither the name of Oracle or the names of its
048*     contributors may be used to endorse or promote products derived
049*     from this software without specific prior written permission.
050*
051* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
052* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
053* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
054* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
055* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
056* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
057* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
058* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
059* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
060* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
061* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
062*/
063
064package ptolemy.actor.gui;
065
066import java.awt.Color;
067import java.awt.Component;
068import java.awt.Dimension;
069import java.awt.Frame;
070import java.awt.Point;
071import java.awt.Rectangle;
072import java.awt.Toolkit;
073import java.awt.event.ActionEvent;
074import java.awt.event.FocusEvent;
075import java.awt.event.FocusListener;
076import java.awt.event.KeyAdapter;
077import java.awt.event.KeyEvent;
078import java.awt.event.MouseAdapter;
079import java.awt.event.MouseEvent;
080import java.net.URL;
081import java.util.ArrayList;
082import java.util.Arrays;
083import java.util.Collections;
084import java.util.Hashtable;
085import java.util.Iterator;
086import java.util.LinkedList;
087import java.util.List;
088import java.util.Locale;
089import java.util.Vector;
090
091import javax.swing.AbstractAction;
092import javax.swing.DefaultCellEditor;
093import javax.swing.JButton;
094import javax.swing.JCheckBox;
095import javax.swing.JComboBox;
096import javax.swing.JFormattedTextField;
097import javax.swing.JLabel;
098import javax.swing.JOptionPane;
099import javax.swing.JPanel;
100import javax.swing.JTable;
101import javax.swing.JTextField;
102import javax.swing.KeyStroke;
103import javax.swing.SwingConstants;
104import javax.swing.SwingUtilities;
105import javax.swing.event.ChangeEvent;
106import javax.swing.table.AbstractTableModel;
107import javax.swing.table.JTableHeader;
108import javax.swing.table.TableCellRenderer;
109import javax.swing.table.TableColumn;
110
111import ptolemy.actor.Actor;
112import ptolemy.actor.IOPort;
113import ptolemy.actor.TypeAttribute;
114import ptolemy.actor.TypedActor;
115import ptolemy.actor.TypedIOPort;
116import ptolemy.data.BooleanToken;
117import ptolemy.data.Token;
118import ptolemy.data.expr.ASTPtRootNode;
119import ptolemy.data.expr.Parameter;
120import ptolemy.data.expr.ParseTreeEvaluator;
121import ptolemy.data.expr.PtParser;
122import ptolemy.data.type.TypeLattice;
123import ptolemy.graph.DirectedAcyclicGraph;
124import ptolemy.gui.PtGUIUtilities;
125import ptolemy.kernel.Entity;
126import ptolemy.kernel.Port;
127import ptolemy.kernel.undo.UndoChangeRequest;
128import ptolemy.kernel.util.Attribute;
129import ptolemy.kernel.util.ChangeListener;
130import ptolemy.kernel.util.ChangeRequest;
131import ptolemy.kernel.util.IllegalActionException;
132import ptolemy.kernel.util.InternalErrorException;
133import ptolemy.kernel.util.NamedObj;
134import ptolemy.kernel.util.StringAttribute;
135import ptolemy.moml.MoMLChangeRequest;
136import ptolemy.moml.unit.ParseException;
137import ptolemy.moml.unit.UnitAttribute;
138import ptolemy.moml.unit.UnitLibrary;
139import ptolemy.util.MessageHandler;
140import ptolemy.util.StringUtilities;
141
142///////////////////////////////////////////////////////////////////
143//// PortConfigurerDialog
144
145/**
146 This class is a non-modal dialog for configuring the ports of an
147 entity.  The columns of the dialog displayed depend on the type of
148 the Entity (target) for which we are configuring the ports.
149
150 By default, "Name", "Direction, "Show Name", and "Hide" are
151 displayed for all target types.  We assume that the ports are of
152 type Port or ComponentPort.
153
154 If the target is an Actor, then the ports are of type IOPort and we
155 add the "Input", "Output", and "Multiport" columns.
156
157 If the target is a TypedActor, then the ports are of type
158 TypedIOPort, and we add the "Type" and "Units" columns.
159
160 NOTE: This code checks for the existence of each column that may be
161 used, but it sometimes assumes the existence of the "Name" column.
162
163 @author Rowland R Johnson, Elaine Cheong
164 @version $Id$
165 @since Ptolemy II 1.0
166 @Pt.ProposedRating Yellow (eal)
167 @Pt.AcceptedRating Red (eal)
168 */
169@SuppressWarnings("serial")
170public class PortConfigurerDialog extends PtolemyDialog
171        implements ChangeListener {
172    /**
173     * Construct a dialog that presents the ports as a table. Each row of the
174     * table corresponds to one port. The user modifies the table to specify
175     * changes in the ports. When the apply button is pressed the contents of
176     * the table is used to update the ports. When Commit is pressed an apply
177     * is done before exiting.
178     *
179     * <p>This dialog is is not modal. In particular, changes can be undone by
180     * clicking Edit-&gt;Undo, and the help screen can be manipulated while this
181     * dialog exists. The dialog is placed relative to the owner.</p>
182     *
183     * @param tableau The DialogTableau.
184     * @param owner The object that, per the user, appears to be generating the
185     * dialog.
186     * @param target The object whose ports are being configured.
187     * @param configuration The configuration to use to open the help screen
188     * (or null if help is not supported).
189     */
190    public PortConfigurerDialog(DialogTableau tableau, Frame owner,
191            Entity target, Configuration configuration) {
192        super("Configure ports for " + target.getName(), tableau, owner, target,
193                configuration);
194
195        // Listen for changes that may need to be reflected in the table.
196        getTarget().addChangeListener(this);
197
198        // Create the JComboBox that used to select the location of the port
199        _portLocationComboBox = new JComboBox();
200        _portLocationComboBox.addItem("DEFAULT");
201        _portLocationComboBox.addItem("NORTH");
202        _portLocationComboBox.addItem("EAST");
203        _portLocationComboBox.addItem("SOUTH");
204        _portLocationComboBox.addItem("WEST");
205
206        _portTable = new JTable();
207        // If you change the height, then check that a few rows can be added.
208        // Also, check the setRowHeight call below.
209        _portTable.setPreferredScrollableViewportSize(new Dimension(600, 100));
210
211        _portTable.addMouseListener(new MouseAdapter() {
212            @Override
213            public void mouseClicked(MouseEvent mouseEvent) {
214                if (PtGUIUtilities.macOSLookAndFeel()
215                        && (mouseEvent.isPopupTrigger() || mouseEvent
216                                .getButton() == MouseEvent.BUTTON1
217                                && (mouseEvent.getModifiersEx()
218                                        | java.awt.event.InputEvent.CTRL_MASK) == java.awt.event.InputEvent.CTRL_MASK)
219                        || !PtGUIUtilities.macOSLookAndFeel() && mouseEvent
220                                .getButton() == MouseEvent.BUTTON3) {
221                    Point point = mouseEvent.getPoint();
222                    int row = _portTable.rowAtPoint(point);
223                    _setSelectedRow(row);
224                }
225            }
226        });
227
228        _portTable.addKeyListener(new KeyAdapter() {
229            @Override
230            public void keyTyped(KeyEvent ke) {
231                if (ke.getKeyChar() == '\n') {
232                    if (_apply()) {
233                        _cancel();
234                    }
235                }
236            }
237        });
238        _portTable.addFocusListener(new FocusListener() {
239            @Override
240            public void focusGained(FocusEvent event) {
241                // Set the selected row so the remove key gets updated
242                _setSelectedRow(_portTable.getSelectionModel()
243                        .getAnchorSelectionIndex());
244            }
245
246            @Override
247            public void focusLost(FocusEvent event) {
248            }
249        });
250
251        // Initialize which columns will be visible for this target.
252        _initColumnNames();
253
254        // Create the TableModel and set certain cell editors and renderers
255        _setupTableModel();
256
257        // Initialize the displayed column widths.
258        _initColumnSizes();
259
260        // Make the contents of the table scrollable
261        setScrollableContents(_portTable);
262
263        // The following sets up a listener for mouse clicks on the
264        // header cell of the Show Name column. A click causes the
265        // values in this column to all change.
266        // FIXME: this doesn't seem to work if you click multiple
267        // times in a session
268        _jth = _portTable.getTableHeader();
269
270        if (_columnNames.contains(ColumnNames.COL_SHOW_NAME)
271                || _columnNames.contains(ColumnNames.COL_HIDE)) {
272            _jth.addMouseListener(new MouseAdapter() {
273                @Override
274                public void mouseClicked(MouseEvent me) {
275                    // indexOf() returns -1 if element is not in ArrayList
276                    int showName = _columnNames
277                            .indexOf(ColumnNames.COL_SHOW_NAME);
278
279                    if (showName != -1) {
280                        Rectangle headerShowNameRect = _jth
281                                .getHeaderRect(showName);
282
283                        if (headerShowNameRect.contains(me.getPoint())) {
284                            _portTableModel.toggleShowAllNames();
285                        }
286                    }
287
288                    int hide = _columnNames.indexOf(ColumnNames.COL_HIDE);
289
290                    if (hide != 1) {
291                        Rectangle headerHidePortRect = _jth.getHeaderRect(hide);
292
293                        if (headerHidePortRect.contains(me.getPoint())) {
294                            _portTableModel.toggleHidePorts();
295                        }
296                    }
297                }
298            });
299        }
300
301        pack();
302        setVisible(true);
303    }
304
305    ///////////////////////////////////////////////////////////////////
306    ////                         public variables                  ////
307
308    /** The background color of an uneditable cell. */
309    public static final Color UNEDITABLE_CELL_COLOR = new Color(255, 128, 128);
310
311    ///////////////////////////////////////////////////////////////////
312    ////                         public methods                    ////
313
314    /**
315     * Notify the listener that a change has been successfully executed.
316     *
317     * @param change The change that has been executed.
318     */
319    @Override
320    public void changeExecuted(ChangeRequest change) {
321        // Ignore if this is the originator or if this is a change
322        // from above that is anything other than an undo. Detecting
323        // that it is an undo from above seems awkward. A better way
324        // would be to extend the ChangeRequest system to include
325        // ChangeRequest types so that an undo would be explicitly
326        // represented.
327        if (change == null || change.getSource() == this
328                || !(change instanceof UndoChangeRequest)) {
329            return;
330        }
331
332        // The ports of the _target may have changed.
333        _setupTableModel();
334    }
335
336    /**
337     * Notify the listener that a change has resulted in an exception.
338     *
339     * @param change The change that was attempted.
340     * @param exception The exception that resulted.
341     */
342    @Override
343    public void changeFailed(ChangeRequest change, Exception exception) {
344        // TODO Determine best way to handle failed change
345        // request. This method _should_ never be invoked if the
346        // source is this.  For now, at least, test to see if the
347        // source is this, and report it.
348        if (change == null) {
349            return;
350        }
351
352        if (!change.isErrorReported()) {
353            MessageHandler.error("Change failed: ", exception);
354        }
355    }
356
357    /** Close this dialog.  If the state has not be saved, prompt
358     *  the user to save the modifications.
359     *  @return false if the user selects cancel, otherwise return true.
360     */
361    public boolean close() {
362        if (_isDirty()) {
363            int option = JOptionPane.showConfirmDialog(getOwner(),
364                    "Save port modifications on " + getTarget().getFullName(),
365                    "Unsaved Port Modifications",
366                    JOptionPane.YES_NO_CANCEL_OPTION);
367
368            switch (option) {
369            case JOptionPane.YES_OPTION: {
370                _apply();
371                return true;
372            }
373
374            case JOptionPane.NO_OPTION:
375                return true;
376
377            case JOptionPane.CANCEL_OPTION:
378                return false;
379            }
380        }
381
382        return true;
383    }
384
385    @Override
386    public void saveIfRequired() {
387        if (_isDirty()) {
388            int option = JOptionPane.showConfirmDialog(getOwner(),
389                    "Save port modifications on " + getTarget().getFullName()
390                            + "?",
391                    "Unsaved Port Modifications", JOptionPane.YES_NO_OPTION);
392
393            switch (option) {
394            case JOptionPane.YES_OPTION:
395                _apply();
396            }
397        }
398    }
399
400    ///////////////////////////////////////////////////////////////////
401    ////                         protected methods                 ////
402
403    /** Apply any changes that may have been made in the table.
404     *  @return true if the change was successfully applied
405     */
406    protected boolean _apply() {
407        // The port names in the table will be used many times, so extract
408        // them here.
409        String[] portNameInTable = new String[_portTableModel.getRowCount()];
410
411        for (int i = 0; i < _portTableModel.getRowCount(); i++) {
412            portNameInTable[i] = (String) _portTableModel.getValueAt(i,
413                    _columnNames.indexOf(ColumnNames.COL_NAME));
414        }
415
416        // Do some basic checks on table for things that are obviously
417        // incorrect. First, make sure all the new ports have names
418        // other than the empty string.
419        for (int i = 0; i < _portTableModel.getRowCount(); i++) {
420            if (portNameInTable[i].equals("")) {
421                JOptionPane.showMessageDialog(this,
422                        "All Ports need to have a name.");
423                return false;
424            }
425        }
426
427        //  Now, make sure all port names are unique.
428        for (int i = 0; i < _portTableModel.getRowCount(); i++) {
429            for (int j = i + 1; j < _portTableModel.getRowCount(); j++) {
430                if (portNameInTable[i].equals(portNameInTable[j])) {
431                    JOptionPane.showMessageDialog(this,
432                            portNameInTable[i] + " is a duplicate port name.\n"
433                                    + "Please remove all but one");
434                    return false;
435                }
436            }
437        }
438
439        // Determine which ports have been removed. If a port exists on the
440        // target but is not represented by a row in the table then it needs
441        // to be removed.
442        Vector portsToBeRemoved = new Vector();
443        Iterator portIterator = getTarget().portList().iterator();
444        Port actualPort = null;
445
446        while (portIterator.hasNext()) {
447            Object candidate = portIterator.next();
448
449            if (candidate instanceof Port) {
450                boolean foundPort = false;
451                actualPort = (Port) candidate;
452
453                for (int i = 0; i < _ports.size(); i++) {
454                    Hashtable portInfo = (Hashtable) _ports.elementAt(i);
455
456                    if (actualPort == (Port) portInfo
457                            .get(ColumnNames.COL_ACTUAL_PORT)) {
458                        foundPort = true;
459                        break;
460                    }
461                }
462
463                if (!foundPort) {
464                    portsToBeRemoved.add(actualPort);
465                }
466            } else {
467                Exception exception = new InternalErrorException(
468                        "The target portList contains" + " an object \""
469                                + candidate + "\"that is not of type Port.");
470
471                MessageHandler.error("Internal Error while removing a port.",
472                        exception);
473            }
474        }
475
476        Iterator actualPorts = portsToBeRemoved.iterator();
477
478        while (actualPorts.hasNext()) {
479            StringBuffer moml = new StringBuffer();
480            actualPort = (Port) actualPorts.next();
481
482            // The context for the MoML should be the first container
483            // above this port in the hierarchy that defers its MoML
484            // definition, or the immediate parent if there is none.
485            NamedObj container = actualPort.getContainer();
486            NamedObj composite = container.getContainer();
487
488            if (composite != null) {
489                moml.append("<deletePort name=\""
490                        + StringUtilities.escapeForXML(actualPort.getName())
491                        + "\" entity=\"" + container.getName() + "\" />");
492            } else {
493                moml.append(
494                        "<deletePort name=\""
495                                + StringUtilities.escapeForXML(
496                                        actualPort.getName(container))
497                                + "\" />");
498            }
499
500            // NOTE: the context is the composite entity containing
501            // the entity if possible
502            MoMLChangeRequest request = null;
503
504            if (composite != null) {
505                request = new MoMLChangeRequest(this, composite,
506                        moml.toString());
507            } else {
508                request = new MoMLChangeRequest(this, container,
509                        moml.toString());
510            }
511
512            request.setUndoable(true);
513            container.addChangeListener(this);
514
515            container.requestChange(request);
516        }
517
518        // Iterate over the table rows that represent ports.  If a row
519        // corresponds to an actual port then look to see if that row
520        // is different from the actual port.  If it is, then update
521        // that actual port.  If a row does not correspond to an
522        // actual port then that row represents a new actual port
523        // which must be created.
524        StringBuffer moml = new StringBuffer("<group>");
525        boolean haveSomeUpdate = false;
526
527        for (int i = 0; i < _ports.size(); i++) {
528            Hashtable portInfo = (Hashtable) _ports.elementAt(i);
529            portIterator = getTarget().portList().iterator();
530
531            // actualPort will be the Port found on the _target, if
532            // there is one.
533            actualPort = (Port) portInfo.get(ColumnNames.COL_ACTUAL_PORT);
534
535            Hashtable updates = new Hashtable();
536
537            // FIXME is it necessary to add unchanged fields to hashtable ?
538            if (actualPort != null) {
539                // actualPort is a Port found on the _target. Check to
540                // see if the actualPort is different and needs to be
541                // updated.
542                boolean havePortUpdate = false;
543
544                if (_columnNames.contains(ColumnNames.COL_NAME)) {
545                    String tableValue = (String) portInfo
546                            .get(ColumnNames.COL_NAME);
547
548                    if (!actualPort.getName().equals(tableValue)) {
549                        if (tableValue.contains(".")) {
550                            MessageHandler.error("Failed to rename port "
551                                    + actualPort.getName()
552                                    + "; port names are not allowed to contain periods.",
553                                    new InternalErrorException(null, null,
554                                            "Instead, alias the port by setting display name. "
555                                                    + "Right-click on the port,\nchoose Rename, then enter "
556                                                    + "the desired alias into the field Display Name."));
557                            _applyChangeRequestFailed = true;
558                        } else {
559                            havePortUpdate = true;
560                            updates.put(ColumnNames.COL_NAME, Boolean.TRUE);
561                        }
562                    }
563                }
564
565                if (actualPort instanceof IOPort) {
566                    IOPort iop = (IOPort) actualPort;
567
568                    if (_columnNames.contains(ColumnNames.COL_INPUT)) {
569                        Boolean tableValue = (Boolean) portInfo
570                                .get(ColumnNames.COL_INPUT);
571
572                        if (iop.isInput() != tableValue.booleanValue()) {
573                            havePortUpdate = true;
574                            updates.put(ColumnNames.COL_INPUT, Boolean.TRUE);
575                        }
576                    }
577
578                    if (_columnNames.contains(ColumnNames.COL_OUTPUT)) {
579                        Boolean tableValue = (Boolean) portInfo
580                                .get(ColumnNames.COL_OUTPUT);
581
582                        if (iop.isOutput() != tableValue.booleanValue()) {
583                            havePortUpdate = true;
584                            updates.put(ColumnNames.COL_OUTPUT, Boolean.TRUE);
585                        }
586                    }
587
588                    if (_columnNames.contains(ColumnNames.COL_MULTIPORT)) {
589                        Boolean tableValue = (Boolean) portInfo
590                                .get(ColumnNames.COL_MULTIPORT);
591
592                        if (iop.isMultiport() != tableValue.booleanValue()) {
593                            havePortUpdate = true;
594                            updates.put(ColumnNames.COL_MULTIPORT,
595                                    Boolean.TRUE);
596                        }
597                    }
598                }
599
600                if (_columnNames.contains(ColumnNames.COL_SHOW_NAME)) {
601                    boolean isShowSet = _isPropertySet(actualPort, "_showName");
602                    Boolean tableValue = (Boolean) portInfo
603                            .get(ColumnNames.COL_SHOW_NAME);
604
605                    if (isShowSet != tableValue.booleanValue()) {
606                        havePortUpdate = true;
607                        updates.put(ColumnNames.COL_SHOW_NAME, Boolean.TRUE);
608                    }
609                }
610
611                if (_columnNames.contains(ColumnNames.COL_HIDE)) {
612                    boolean isHideSet = _isPropertySet(actualPort, "_hide");
613                    Boolean tableValue = (Boolean) portInfo
614                            .get(ColumnNames.COL_HIDE);
615
616                    if (isHideSet != tableValue.booleanValue()) {
617                        havePortUpdate = true;
618                        updates.put(ColumnNames.COL_HIDE, Boolean.TRUE);
619                    }
620                }
621
622                if (actualPort instanceof TypedIOPort) {
623                    TypedIOPort tiop = (TypedIOPort) actualPort;
624
625                    if (_columnNames.contains(ColumnNames.COL_TYPE)) {
626                        String type = null;
627                        // NOTE: It is possible for there to be a type attribute
628                        // with some other name than "_type". In theory, there
629                        // should only be one, but just in case, always use the last
630                        // one.
631                        List<TypeAttribute> attributes = tiop
632                                .attributeList(TypeAttribute.class);
633                        if (attributes.size() > 0) {
634                            TypeAttribute typeAttribute = attributes
635                                    .get(attributes.size() - 1);
636                            type = typeAttribute.getExpression();
637                        }
638
639                        String tableValue = (String) portInfo
640                                .get(ColumnNames.COL_TYPE);
641
642                        // NOTE: bypassing the MoML parser and setting the type
643                        // directly, in order to propagate the type change
644                        // required when the input field is set to blank.
645                        // See bug #518 in Bugzilla
646                        // This is no longer necessary as it is handled by TypeAttribute.
647                        /*
648                        if (tableValue.equals("")) {
649                            tiop.setTypeEquals(BaseType.UNKNOWN);
650                        }
651                         */
652                        if (type == null && !tableValue.equals("")
653                                || type != null && !tableValue.equals(type)) {
654                            havePortUpdate = true;
655                            updates.put(ColumnNames.COL_TYPE, Boolean.TRUE);
656                        }
657                    }
658                }
659
660                if (_columnNames.contains(ColumnNames.COL_DIRECTION)) {
661                    // Look for a change in direction
662                    String _direction = null;
663                    String direction = (String) portInfo
664                            .get(ColumnNames.COL_DIRECTION);
665                    StringAttribute _cardinal = (StringAttribute) actualPort
666                            .getAttribute("_cardinal");
667
668                    if (_cardinal != null) {
669                        _direction = _cardinal.getExpression()
670                                .toUpperCase(Locale.getDefault());
671                    }
672
673                    if (_direction == null && !direction.equals("DEFAULT")
674                            || _direction != null
675                                    && !direction.equals(_direction)) {
676                        havePortUpdate = true;
677                        updates.put(ColumnNames.COL_DIRECTION, Boolean.TRUE);
678                    }
679                }
680
681                if (_columnNames.contains(ColumnNames.COL_UNITS)) {
682                    String units = null;
683                    UnitAttribute _unitsAttribute = (UnitAttribute) actualPort
684                            .getAttribute("_units");
685
686                    if (_unitsAttribute != null) {
687                        units = _unitsAttribute.getExpression();
688                    }
689
690                    String tableValue = (String) portInfo
691                            .get(ColumnNames.COL_UNITS);
692
693                    // tableValue will not be null because we put ""
694                    // into portInfo in the constructor of
695                    // PortTableModel.
696                    if (units == null && !tableValue.equals("")
697                            || units != null && !tableValue.equals(units)) {
698                        havePortUpdate = true;
699                        updates.put(ColumnNames.COL_UNITS, Boolean.TRUE);
700                    }
701                }
702
703                if (havePortUpdate) {
704                    String currentPortName = ((Port) portInfo
705                            .get(ColumnNames.COL_ACTUAL_PORT)).getName();
706                    String newPortName = (String) portInfo
707                            .get(ColumnNames.COL_NAME);
708
709                    String momlString = _createMoMLUpdate(updates, portInfo,
710                            currentPortName, newPortName);
711
712                    moml.append(momlString);
713                    haveSomeUpdate = true;
714                }
715            } else {
716                // actualPort is not found on the _target so make a new one.
717                // Initialize all columns to be updated for this port entry.
718                Iterator it = _columnNames.iterator();
719
720                while (it.hasNext()) {
721                    String element = (String) it.next();
722                    updates.put(element, Boolean.TRUE);
723                }
724
725                // FIXME is it necessary to remove unchanged fields
726                // from updates hashtable?
727                // Make this false, since this is a new port that does
728                // not have a pre-existing name.  Note that "rename"
729                // is used for pre-existing ports with new names.
730                if (_columnNames.contains(ColumnNames.COL_NAME)) {
731                    String tableValue = (String) portInfo
732                            .get(ColumnNames.COL_NAME);
733                    if (tableValue.contains(".")) {
734                        MessageHandler.error("Failed to add port \""
735                                + tableValue
736                                + "\"; port names are not allowed to contain periods.",
737                                new InternalErrorException(null, null,
738                                        "Instead, provide a name without periods and then alias the port by setting display name.\n"
739                                                + "Right-click on the port, choose Rename, then enter "
740                                                + "the desired alias into the field Display Name."));
741                        break;
742                    } else {
743                        updates.put(ColumnNames.COL_NAME, Boolean.FALSE);
744                    }
745
746                }
747
748                // Put this in the MoMLChangeRequest if the value is
749                // not the default of false.
750                if (_columnNames.contains(ColumnNames.COL_SHOW_NAME)) {
751                    updates.put(ColumnNames.COL_SHOW_NAME,
752                            portInfo.get(ColumnNames.COL_SHOW_NAME));
753                }
754
755                // Put this in the MoMLChangeRequest if the value is
756                // not the default of false.
757                if (_columnNames.contains(ColumnNames.COL_HIDE)) {
758                    updates.put(ColumnNames.COL_HIDE,
759                            portInfo.get(ColumnNames.COL_HIDE));
760                }
761
762                // FIXME: should we compare against "unknown" instead of ""?
763                if (_columnNames.contains(ColumnNames.COL_TYPE)) {
764                    String type = (String) portInfo.get(ColumnNames.COL_TYPE);
765
766                    if (!type.equals("")) {
767                        updates.put(ColumnNames.COL_TYPE, Boolean.TRUE);
768                        _portTableModel.fireTableDataChanged();
769                    } else {
770                        // Do not make this part of the
771                        // MoMLChangeRequest if the value is equal to
772                        // "".
773                        updates.put(ColumnNames.COL_TYPE, Boolean.FALSE);
774                    }
775                }
776
777                // Put this in the MoMLChangeRequest if the value is
778                // not the default.
779                if (_columnNames.contains(ColumnNames.COL_DIRECTION)) {
780                    String direction = (String) portInfo
781                            .get(ColumnNames.COL_DIRECTION);
782
783                    if (!direction.equals("DEFAULT")) {
784                        updates.put(ColumnNames.COL_DIRECTION, Boolean.TRUE);
785                        _portTableModel.fireTableDataChanged();
786                    } else {
787                        updates.put(ColumnNames.COL_DIRECTION, Boolean.FALSE);
788                    }
789                }
790
791                if (_columnNames.contains(ColumnNames.COL_UNITS)) {
792                    String unit = (String) portInfo.get(ColumnNames.COL_UNITS);
793
794                    if (!unit.equals("")) {
795                        updates.put(ColumnNames.COL_UNITS, Boolean.TRUE);
796                        _portTableModel.fireTableDataChanged();
797                    } else {
798                        // Do not make this part of the
799                        // MoMLChangeRequest if the value is equal to
800                        // "".
801                        updates.put(ColumnNames.COL_UNITS, Boolean.FALSE);
802                    }
803                }
804
805                moml.append(_createMoMLUpdate(updates, portInfo,
806                        (String) portInfo.get(ColumnNames.COL_NAME), null));
807
808                haveSomeUpdate = true;
809            }
810        }
811
812        if (haveSomeUpdate) {
813            moml.append("</group>");
814
815            MoMLChangeRequest request = new MoMLChangeRequest(this, getTarget(),
816                    moml.toString(), null);
817            request.setUndoable(true);
818
819            // NOTE: There is no need to listen for completion or
820            // errors in this change request, since, in theory, it
821            // will just work. Will someone report the error if one
822            // occurs? I hope so...
823            _applyChangeRequestFailed = false;
824            try {
825                getTarget().requestChange(request);
826            } catch (Throwable throwable) {
827                MessageHandler.error("Failed to apply changes",
828                        new InternalErrorException(getTarget(), throwable,
829                                moml.toString()));
830                _applyChangeRequestFailed = true;
831                return false;
832            }
833            _populateActualPorts();
834        }
835
836        _setDirty(false);
837        _enableApplyButton(false);
838
839        // Update the remove button label.
840        _setSelectedRow(
841                _portTable.getSelectionModel().getAnchorSelectionIndex());
842        return true;
843    }
844
845    @Override
846    protected void _cancel() {
847        getTarget().removeChangeListener(this);
848        super._cancel();
849    }
850
851    @Override
852    protected void _createExtendedButtons(JPanel _buttons) {
853        Button = new JButton("Commit");
854        _buttons.add(Button);
855        _applyButton = new JButton("Apply");
856        _buttons.add(_applyButton);
857        _addButton = new JButton("Add");
858        _buttons.add(_addButton);
859        _removeButton = new JButton("Remove           ");
860        _removeButton.setEnabled(false);
861        _buttons.add(_removeButton);
862    }
863
864    /** Return a URL that points to the help page.
865     *  @return A URL that points to the help page
866     */
867    @Override
868    protected URL _getHelpURL() {
869        URL helpURL = getClass().getClassLoader()
870                .getResource("ptolemy/actor/gui/doc/portConfigurerDialog.htm");
871        return helpURL;
872    }
873
874    /** Process a button press.
875     *  @param button The button.
876     */
877    @Override
878    protected void _processButtonPress(String button) {
879        // If the user has typed in a port name, but not
880        // moved the focus, we want to tell the model the
881        // data has changed.
882        if (_portTable.isEditing()) {
883            _portTable.editingStopped(new ChangeEvent(button));
884        }
885
886        _portTableModel.fireTableDataChanged();
887
888        // The button semantics are
889        // Add - Add a new port.
890        if (button.equals("Apply")) {
891            if (_apply()) {
892                if (_applyChangeRequestFailed) {
893                    // If the change request fails, the close the dialog,
894                    // as there are problems which we cannot recover from.
895                    // See http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4478
896                    _cancel();
897                }
898            }
899        } else if (button.equals("Commit")) {
900            if (_apply()) {
901                // We always close the window after commit.
902                _cancel();
903            }
904        } else if (button.equals("Add")) {
905            _portTableModel.addNewPort();
906        } else if (
907        // FIXME this depends on button name string length.
908        button.length() > 5 && button.substring(0, 6).equals("Remove")) {
909            _portTableModel.removePort();
910            _setSelectedRow(-1);
911        } else {
912            super._processButtonPress(button);
913        }
914    }
915
916    ///////////////////////////////////////////////////////////////////
917    ////                         inner classes                     ////
918
919    /** The table model for the table. */
920    class PortTableModel extends AbstractTableModel {
921        /** Populates the _ports Vector. Each element of _ports is a
922         * Hashtable that represents a Port on the Entity that is
923         * having its ports configured.  If the Port exists on the
924         * Entity, a reference to it is stored in the Hashtable with
925         * key = ColumnNames.COL_ACTUAL_PORT.
926         * @param portList The list of ports.
927         */
928        public PortTableModel(List portList) {
929            Iterator ports = portList.iterator();
930            _ports = new Vector();
931
932            while (ports.hasNext()) {
933                Port p = (Port) ports.next();
934                Hashtable portInfo = new Hashtable();
935
936                if (_columnNames.contains(ColumnNames.COL_NAME)) {
937                    portInfo.put(ColumnNames.COL_NAME, p.getName());
938                }
939
940                if (_columnNames.contains(ColumnNames.COL_DIRECTION)) {
941                    String _direction;
942                    StringAttribute _cardinal = (StringAttribute) p
943                            .getAttribute("_cardinal");
944
945                    if (_cardinal != null) {
946                        _direction = _cardinal.getExpression()
947                                .toUpperCase(Locale.getDefault());
948                    } else {
949                        _direction = "DEFAULT";
950                    }
951
952                    portInfo.put(ColumnNames.COL_DIRECTION, _direction);
953                }
954
955                if (_columnNames.contains(ColumnNames.COL_SHOW_NAME)) {
956                    boolean isShowSet = _isPropertySet(p, "_showName");
957
958                    if (isShowSet) {
959                        portInfo.put(ColumnNames.COL_SHOW_NAME, Boolean.TRUE);
960                    } else {
961                        portInfo.put(ColumnNames.COL_SHOW_NAME, Boolean.FALSE);
962                    }
963                }
964
965                if (_columnNames.contains(ColumnNames.COL_HIDE)) {
966                    boolean isHideSet = _isPropertySet(p, "_hide");
967
968                    if (isHideSet) {
969                        portInfo.put(ColumnNames.COL_HIDE, Boolean.TRUE);
970                    } else {
971                        portInfo.put(ColumnNames.COL_HIDE, Boolean.FALSE);
972                    }
973                }
974
975                if (p instanceof IOPort) {
976                    IOPort iop = (IOPort) p;
977
978                    if (_columnNames.contains(ColumnNames.COL_INPUT)) {
979                        portInfo.put(ColumnNames.COL_INPUT,
980                                Boolean.valueOf(iop.isInput()));
981                    }
982
983                    if (_columnNames.contains(ColumnNames.COL_OUTPUT)) {
984                        portInfo.put(ColumnNames.COL_OUTPUT,
985                                Boolean.valueOf(iop.isOutput()));
986                    }
987
988                    if (_columnNames.contains(ColumnNames.COL_MULTIPORT)) {
989                        portInfo.put(ColumnNames.COL_MULTIPORT,
990                                Boolean.valueOf(iop.isMultiport()));
991                    }
992                }
993
994                if (p instanceof TypedIOPort) {
995                    TypedIOPort tiop = (TypedIOPort) p;
996
997                    if (_columnNames.contains(ColumnNames.COL_TYPE)) {
998                        // NOTE: It is possible for there to be a type attribute
999                        // with some other name than "_type". In theory, there
1000                        // should only be one, but just in case, always use the last
1001                        // one.
1002                        List<TypeAttribute> attributes = tiop
1003                                .attributeList(TypeAttribute.class);
1004                        if (attributes.size() > 0) {
1005                            TypeAttribute type = attributes
1006                                    .get(attributes.size() - 1);
1007                            portInfo.put(ColumnNames.COL_TYPE,
1008                                    type.getExpression());
1009                        } else {
1010                            portInfo.put(ColumnNames.COL_TYPE, "");
1011                        }
1012                    }
1013                }
1014
1015                if (_columnNames.contains(ColumnNames.COL_UNITS)) {
1016                    String units = "";
1017                    UnitAttribute _unitsAttribute = (UnitAttribute) p
1018                            .getAttribute("_units");
1019
1020                    if (_unitsAttribute != null) {
1021                        units = _unitsAttribute.getExpression();
1022
1023                        if (units != null) {
1024                            portInfo.put(ColumnNames.COL_UNITS, units);
1025                        } else {
1026                            portInfo.put(ColumnNames.COL_UNITS, "");
1027                        }
1028                    } else {
1029                        // Set units to "" anyways.  If the user
1030                        // doesn't change the value, nothing will be
1031                        // added to the MoMLChangeRequest for units in
1032                        // _apply().
1033                        portInfo.put(ColumnNames.COL_UNITS, "");
1034                    }
1035                }
1036
1037                portInfo.put(ColumnNames.COL_ACTUAL_PORT, p);
1038
1039                _ports.add(portInfo);
1040            }
1041        }
1042
1043        /**
1044         * Add a port The new port gets added with a name of "". It is
1045         * assumed that the user will change this to the real name at
1046         * some point.
1047         */
1048        public void addNewPort() {
1049            Hashtable portInfo = new Hashtable();
1050
1051            if (_columnNames.contains(ColumnNames.COL_NAME)) {
1052                portInfo.put(ColumnNames.COL_NAME, "");
1053            }
1054
1055            if (_columnNames.contains(ColumnNames.COL_DIRECTION)) {
1056                portInfo.put(ColumnNames.COL_DIRECTION, "DEFAULT");
1057            }
1058
1059            if (_columnNames.contains(ColumnNames.COL_SHOW_NAME)) {
1060                portInfo.put(ColumnNames.COL_SHOW_NAME, Boolean.FALSE);
1061            }
1062
1063            if (_columnNames.contains(ColumnNames.COL_HIDE)) {
1064                portInfo.put(ColumnNames.COL_HIDE, Boolean.FALSE);
1065            }
1066
1067            if (_columnNames.contains(ColumnNames.COL_INPUT)) {
1068                portInfo.put(ColumnNames.COL_INPUT, Boolean.FALSE);
1069            }
1070
1071            if (_columnNames.contains(ColumnNames.COL_OUTPUT)) {
1072                portInfo.put(ColumnNames.COL_OUTPUT, Boolean.FALSE);
1073            }
1074
1075            if (_columnNames.contains(ColumnNames.COL_MULTIPORT)) {
1076                portInfo.put(ColumnNames.COL_MULTIPORT, Boolean.FALSE);
1077            }
1078
1079            if (_columnNames.contains(ColumnNames.COL_TYPE)) {
1080                portInfo.put(ColumnNames.COL_TYPE, "");
1081            }
1082
1083            if (_columnNames.contains(ColumnNames.COL_UNITS)) {
1084                portInfo.put(ColumnNames.COL_UNITS, "");
1085            }
1086
1087            _ports.add(portInfo);
1088
1089            // Now tell the GUI so that it can update itself.
1090            fireTableRowsInserted(getRowCount(), getRowCount());
1091
1092            // FIXME: Move the focus to the last row
1093        }
1094
1095        /**
1096         * Removes a port.
1097         */
1098        public void removePort() {
1099            // First remove it from the _ports, and then tell the GUI
1100            // that it is gone so that it can update itself.
1101            _ports.remove(_selectedRow);
1102            fireTableRowsDeleted(_selectedRow, _selectedRow);
1103            _enableApplyButton(true);
1104            _setDirty(true);
1105        }
1106
1107        /**
1108         * Get the number of columns.
1109         *
1110         * @see javax.swing.table.TableModel#getColumnCount()
1111         */
1112        @Override
1113        public int getColumnCount() {
1114            return _columnNames.size();
1115        }
1116
1117        /** Get the number of rows.
1118         * @see javax.swing.table.TableModel#getRowCount()
1119         */
1120        @Override
1121        public int getRowCount() {
1122            return _ports.size();
1123        }
1124
1125        /** Get the column header name.
1126         * @see javax.swing.table.TableModel#getColumnName(int)
1127         */
1128        @Override
1129        public String getColumnName(int col) {
1130            return (String) _columnNames.get(col);
1131        }
1132
1133        /** Get the value at a particular row and column.
1134         * @param row The row.
1135         * @param col The column.
1136         * @see javax.swing.table.TableModel#getValueAt(int, int)
1137         */
1138        @Override
1139        public Object getValueAt(int row, int col) {
1140            Hashtable portInfo = (Hashtable) _ports.elementAt(row);
1141            return portInfo.get(getColumnName(col));
1142        }
1143
1144        /** Set the value at a particular row and column.
1145         * @param value The value to be set.
1146         * @param row The row.
1147         * @param col The column.
1148         * @see javax.swing.table.TableModel#setValueAt(Object, int, int)
1149         */
1150        @Override
1151        public void setValueAt(Object value, int row, int col) {
1152            Hashtable portInfo = (Hashtable) _ports.elementAt(row);
1153            portInfo.put(getColumnName(col), value);
1154            _enableApplyButton(true);
1155            _setDirty(true);
1156        }
1157
1158        /** Get the Java Class associated with a column param column.
1159         * @return class
1160         * @see javax.swing.table.TableModel#getColumnClass(int)
1161         */
1162        @Override
1163        public Class getColumnClass(int c) {
1164            return getValueAt(0, c).getClass();
1165        }
1166
1167        /** Return true if a a cell editable.
1168         *
1169         * @param row The row.
1170         * @param col The column.
1171         * @return true if editable
1172         * @see javax.swing.table.TableModel#isCellEditable(int, int)
1173         */
1174        @Override
1175        public boolean isCellEditable(int row, int col) {
1176            Hashtable portInfo = (Hashtable) _ports.elementAt(row);
1177            Port port = (Port) portInfo.get(ColumnNames.COL_ACTUAL_PORT);
1178
1179            if (port != null) {
1180                if (port.getDerivedLevel() < Integer.MAX_VALUE) {
1181                    if (col == _columnNames.indexOf(ColumnNames.COL_NAME)
1182                            || col == _columnNames
1183                                    .indexOf(ColumnNames.COL_INPUT)
1184                            || col == _columnNames
1185                                    .indexOf(ColumnNames.COL_OUTPUT)
1186                            || col == _columnNames
1187                                    .indexOf(ColumnNames.COL_MULTIPORT)) {
1188                        return false;
1189                    }
1190                }
1191            }
1192
1193            return true;
1194        }
1195
1196        /**
1197         * Make the "Show Name" column values be either all true or
1198         * all false.
1199         */
1200        public void toggleShowAllNames() {
1201            _showAllNames = !_showAllNames;
1202
1203            Boolean show = Boolean.valueOf(_showAllNames);
1204
1205            for (int i = 0; i < getRowCount(); i++) {
1206                setValueAt(show, i,
1207                        _columnNames.indexOf(ColumnNames.COL_SHOW_NAME));
1208            }
1209        }
1210
1211        /**
1212         * Make the "Hide" column values be either all true or
1213         * all false.
1214         */
1215        public void toggleHidePorts() {
1216            _hideAllPorts = !_hideAllPorts;
1217
1218            Boolean _hide = Boolean.valueOf(_hideAllPorts);
1219
1220            for (int i = 0; i < getRowCount(); i++) {
1221                setValueAt(_hide, i,
1222                        _columnNames.indexOf(ColumnNames.COL_HIDE));
1223            }
1224        }
1225    }
1226
1227    /** Render a boolean cell. */
1228    static class PortBooleanCellRenderer extends JCheckBox
1229            implements TableCellRenderer {
1230
1231        // FindBugs suggests making this class static so as to decrease
1232        // the size of instances and avoid dangling references.
1233
1234        public PortBooleanCellRenderer() {
1235            super();
1236        }
1237
1238        @Override
1239        public Component getTableCellRendererComponent(JTable table,
1240                Object value, boolean isSelected, boolean hasFocus, int row,
1241                int col) {
1242            // FindBugs: Use equals, not == and avoid RC: Suspicious
1243            // reference comparison of Boolean values.
1244            if (value != null && value.equals(Boolean.TRUE)) {
1245                setSelected(true);
1246            } else {
1247                setSelected(false);
1248            }
1249
1250            setHorizontalAlignment(SwingConstants.CENTER);
1251
1252            if (!table.isCellEditable(row, col)) {
1253                setBackground(UNEDITABLE_CELL_COLOR);
1254            } else {
1255                setBackground(Color.white);
1256            }
1257
1258            return this;
1259        }
1260    }
1261
1262    /**
1263     * Default renderer for _portTable.
1264     *
1265     * see _setupTableModel()
1266     */
1267    static class StringCellRenderer extends JLabel
1268            implements TableCellRenderer {
1269        // FindBugs suggests making this class static so as to decrease
1270        // the size of instances and avoid dangling references.
1271
1272        public StringCellRenderer() {
1273            super();
1274        }
1275
1276        @Override
1277        public Component getTableCellRendererComponent(JTable table,
1278                Object value, boolean isSelected, boolean hasFocus, int row,
1279                int col) {
1280            setOpaque(true);
1281            setText((String) value);
1282
1283            if (!table.isCellEditable(row, col)) {
1284                setBackground(UNEDITABLE_CELL_COLOR);
1285            } else {
1286                setBackground(Color.white);
1287            }
1288
1289            return this;
1290        }
1291    }
1292
1293    /** Validate a cell. */
1294    abstract class CellValidator {
1295        /** Return true if the value is valid.
1296         *  @param value The value to validate.
1297         *  @return True if the value is valid.
1298         */
1299        public abstract boolean isValid(String value);
1300
1301        /** Set the message.
1302         *  @param message The message.
1303         *  @see #getMessage()
1304         */
1305        public void setMessage(String message) {
1306            _message = message;
1307        }
1308
1309        /** Get the message.
1310         *  @return The message
1311         *  @see #setMessage(String)
1312         */
1313        public String getMessage() {
1314            return _message;
1315        }
1316
1317        /** The message. */
1318        private String _message = null;
1319    }
1320
1321    /**
1322     A validating JTextField table cell editor for use with JTable.
1323     To determine if a selection is valid, this class uses the
1324     CellValidator class.
1325
1326     <p>Based on IntegerEditor from
1327     http://download.oracle.com/javase/tutorial/uiswing/examples/components/TableFTFEditDemoProject/src/components/IntegerEditor.java
1328
1329     @author Christopher Brooks, Sun Microsystems
1330     @version $Id$
1331     @since Ptolemy II 5.1
1332     @Pt.ProposedRating Red (eal)
1333     @Pt.AcceptedRating Red (eal)
1334     */
1335    public class ValidatingJTextFieldCellEditor extends DefaultCellEditor {
1336        /** Construct a validating JTextField JTable Cell editor.
1337         */
1338        public ValidatingJTextFieldCellEditor() {
1339            super(new JFormattedTextField());
1340        }
1341
1342        /** Construct a validating JTextField JTable Cell editor.
1343         *  @param jFormattedTextField The JTextField that provides choices.
1344         */
1345        public ValidatingJTextFieldCellEditor(
1346                final JFormattedTextField jFormattedTextField) {
1347            super(jFormattedTextField);
1348
1349            _jFormattedTextField = (JFormattedTextField) getComponent();
1350
1351            // React when the user presses Enter while the editor is
1352            // active.  (Tab is handled as specified by
1353            // JFormattedTextField's focusLostBehavior property.)
1354            jFormattedTextField.getInputMap()
1355                    .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "check");
1356            jFormattedTextField.getActionMap().put("check",
1357                    new AbstractAction() {
1358                        @Override
1359                        public void actionPerformed(ActionEvent e) {
1360                            boolean valid = true;
1361
1362                            if (_validator != null) {
1363                                valid = _validator
1364                                        .isValid(jFormattedTextField.getText());
1365                            }
1366
1367                            if (!valid) {
1368                                userSaysRevert(jFormattedTextField.getText());
1369                            } else {
1370                                jFormattedTextField.postActionEvent(); //stop editing
1371                            }
1372                        }
1373                    });
1374            _jFormattedTextField.addKeyListener(new KeyAdapter() {
1375                @Override
1376                public void keyTyped(KeyEvent ke) {
1377                    _setDirty(true);
1378                    _enableApplyButton(true);
1379
1380                    if (ke.getKeyChar() == '\n') {
1381                        if (_apply()) {
1382                            _cancel();
1383                        }
1384                    }
1385                }
1386            });
1387            _jFormattedTextField.addFocusListener(new FocusListener() {
1388                @Override
1389                public void focusGained(FocusEvent event) {
1390                    // Set the selected row so the remove key gets updated
1391                    _setSelectedRow(_portTable.getSelectionModel()
1392                            .getAnchorSelectionIndex());
1393                }
1394
1395                @Override
1396                public void focusLost(FocusEvent event) {
1397                }
1398            });
1399        }
1400
1401        ///////////////////////////////////////////////////////////////////
1402        ////                         public methods                    ////
1403
1404        /**
1405         */
1406        @Override
1407        public Component getTableCellEditorComponent(JTable table, Object value,
1408                boolean isSelected, int row, int column) {
1409            JTextField jTextField = (JTextField) super.getTableCellEditorComponent(
1410                    table, value, isSelected, row, column);
1411            _oldValue = jTextField.getText();
1412            jTextField.setText((String) value);
1413            return jTextField;
1414        }
1415
1416        /** Get the cell editor value.
1417         *  @return The string value of the selected item in the combobox.
1418         */
1419        @Override
1420        public Object getCellEditorValue() {
1421            // FIXME: do we need to get jTextField like this each time?
1422            JTextField jTextField = (JTextField) getComponent();
1423            Object o = jTextField.getText();
1424            return o.toString();
1425        }
1426
1427        /** Set the validator.
1428         *  @param validator The validator.
1429         */
1430        public void setValidator(CellValidator validator) {
1431            _validator = validator;
1432        }
1433
1434        /** Check the selection and determine whether we should stop editing.
1435         *  If the selection is invalid, ask the user if they want to revert.
1436         *  If the selection is valid, then call stopCellEditing in the super
1437         *  class
1438         *  @return False if the selection is invalid.  Otherwise,
1439         *  return whatever super.stopCellEditing() returns.
1440         */
1441        @Override
1442        public boolean stopCellEditing() {
1443            // FIXME: do we need to get jTextField like this each time?
1444            JFormattedTextField jFormattedTextField = (JFormattedTextField) getComponent();
1445
1446            if (jFormattedTextField.getText() == null) {
1447                // FIXME: why does the selected item get set to null sometimes?
1448                jFormattedTextField.setText("");
1449            }
1450
1451            boolean valid = true;
1452
1453            if (_validator != null) {
1454                valid = _validator.isValid(jFormattedTextField.getText());
1455            }
1456
1457            if (!valid) {
1458                if (_userWantsToEdit) {
1459                    // User already selected edit, don't ask twice.
1460                    _userWantsToEdit = false;
1461                    return false;
1462                } else {
1463                    if (!userSaysRevert(jFormattedTextField.getText())) {
1464                        _userWantsToEdit = true;
1465                        return false; //don't let the editor go away
1466                    }
1467                }
1468            }
1469
1470            return super.stopCellEditing();
1471        }
1472
1473        ///////////////////////////////////////////////////////////////////
1474        ////                         protected methods                 ////
1475
1476        /** Return true if the user wants to revert to the original value.
1477         *  A dialog box pops up that tells the user that their selection
1478         *  is invalid.
1479         *  @param selectedItem The selected item.
1480         *  @return True if the user elects to revert to the last good
1481         *  value.  Otherwise, returns false, indicating that the user
1482         *  wants to continue editing.
1483         */
1484        protected boolean userSaysRevert(String selectedItem) {
1485            Toolkit.getDefaultToolkit().beep();
1486            _jFormattedTextField.selectAll();
1487
1488            Object[] options = { "Edit", "Revert" };
1489            int answer = JOptionPane.showOptionDialog(
1490                    SwingUtilities.getWindowAncestor(_jFormattedTextField),
1491                    "The value \"" + selectedItem + "\" is not valid:\n"
1492                            + _validator.getMessage()
1493                            + "\nYou can either continue editing "
1494                            + "or revert to the last valid value \"" + _oldValue
1495                            + "\".",
1496                    "Invalid Text Entered", JOptionPane.YES_NO_OPTION,
1497                    JOptionPane.ERROR_MESSAGE, null, options, options[1]);
1498
1499            if (answer == 1) { //Revert!
1500                _jFormattedTextField.setText((String) _oldValue);
1501                return true;
1502            }
1503
1504            return false;
1505        }
1506
1507        ///////////////////////////////////////////////////////////////////
1508        ////                         private variables                 ////
1509
1510        /** The JTextField. */
1511        private JFormattedTextField _jFormattedTextField;
1512
1513        /** Old value of the JTextField. */
1514        private Object _oldValue;
1515
1516        /** True if the user wants to edit after having an invalid selection.*/
1517        private boolean _userWantsToEdit;
1518
1519        /** Class that validates the cell. */
1520        private CellValidator _validator;
1521    }
1522
1523    /**
1524     A validating ComboBox table cell editor for use with JTable.
1525     To determine if a selection is valid, this class uses the
1526     CellValidator class.
1527
1528     <p>Based on IntegerEditor from
1529     http://download.oracle.com/javase/tutorial/uiswing/examples/components/TableFTFEditDemoProject/src/components/IntegerEditor.java
1530
1531     @author Christopher Brooks, Sun Microsystems
1532     @version $Id$
1533     @since Ptolemy II 5.1
1534     @Pt.ProposedRating Red (eal)
1535     @Pt.AcceptedRating Red (eal)
1536     */
1537    public static class ValidatingComboBoxCellEditor extends DefaultCellEditor {
1538        // FindBugs suggested refactoring this into a static class.
1539
1540        /** Construct a validating combo box JTable Cell editor.
1541         *  @param comboBox The combo box that provides choices.
1542         */
1543        public ValidatingComboBoxCellEditor(final JComboBox comboBox) {
1544            super(comboBox);
1545            _comboBox = (JComboBox) getComponent();
1546
1547            // React when the user presses Enter while the editor is
1548            // active.  (Tab is handled as specified by
1549            // JFormattedTextField's focusLostBehavior property.)
1550            comboBox.getInputMap()
1551                    .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "check");
1552            comboBox.getActionMap().put("check", new AbstractAction() {
1553                @Override
1554                public void actionPerformed(ActionEvent e) {
1555                    boolean valid = true;
1556
1557                    if (_validator != null) {
1558                        valid = _validator
1559                                .isValid((String) comboBox.getSelectedItem());
1560                    }
1561
1562                    if (!valid) {
1563                        userSaysRevert((String) comboBox.getSelectedItem());
1564                    }
1565                }
1566            });
1567        }
1568
1569        ///////////////////////////////////////////////////////////////////
1570        ////                         public methods                    ////
1571
1572        /**
1573         */
1574        @Override
1575        public Component getTableCellEditorComponent(JTable table, Object value,
1576                boolean isSelected, int row, int column) {
1577            JComboBox comboBox = (JComboBox) super.getTableCellEditorComponent(
1578                    table, value, isSelected, row, column);
1579            _oldValue = comboBox.getSelectedItem();
1580            comboBox.setSelectedItem(value);
1581            return comboBox;
1582        }
1583
1584        /** Get the cell editor value.
1585         *  @return The string value of the selected item in the combobox.
1586         */
1587        @Override
1588        public Object getCellEditorValue() {
1589            // FIXME: do we need to get comboBox like this each time?
1590            JComboBox comboBox = (JComboBox) getComponent();
1591            Object o = comboBox.getSelectedItem();
1592            return o.toString();
1593        }
1594
1595        /** Set the validator.
1596         *  @param validator The validator.
1597         */
1598        public void setValidator(CellValidator validator) {
1599            _validator = validator;
1600        }
1601
1602        /** Check the selection and determine whether we should stop editing.
1603         *  If the selection is invalid, ask the user if they want to revert.
1604         *  If the selection is valid, then call stopCellEditing in the super
1605         *  class
1606         *  @return False if the selection is invalid.  Otherwise,
1607         *  return whatever super.stopCellEditing() returns.
1608         */
1609        @Override
1610        public boolean stopCellEditing() {
1611            // FIXME: do we need to get comboBox like this each time?
1612            JComboBox comboBox = (JComboBox) getComponent();
1613
1614            if (comboBox.getSelectedItem() == null) {
1615                // FIXME: why does the selected item get set to null sometimes?
1616                comboBox.setSelectedItem("");
1617            }
1618
1619            boolean valid = true;
1620
1621            if (_validator != null) {
1622                valid = _validator.isValid((String) comboBox.getSelectedItem());
1623            }
1624
1625            if (!valid) {
1626                if (_userWantsToEdit) {
1627                    // User already selected edit, don't ask twice.
1628                    _userWantsToEdit = false;
1629                    return false;
1630                } else {
1631                    if (!userSaysRevert((String) comboBox.getSelectedItem())) {
1632                        _userWantsToEdit = true;
1633                        return false; //don't let the editor go away
1634                    }
1635                }
1636            }
1637
1638            return super.stopCellEditing();
1639        }
1640
1641        ///////////////////////////////////////////////////////////////////
1642        ////                         protected methods                 ////
1643
1644        /** Return true if the user wants to revert to the original value.
1645         *  A dialog box pops up that tells the user that their selection
1646         *  is invalid.
1647         *  @param selectedItem The selected item.
1648         *  @return True if the user elects to revert to the last good
1649         *  value.  Otherwise, returns false, indicating that the user
1650         *  wants to continue editing.
1651         */
1652        protected boolean userSaysRevert(String selectedItem) {
1653            Toolkit.getDefaultToolkit().beep();
1654
1655            //_comboBox.selectAll();
1656            Object[] options = { "Edit", "Revert" };
1657            int answer = JOptionPane.showOptionDialog(
1658                    SwingUtilities.getWindowAncestor(_comboBox),
1659                    "The value \"" + selectedItem + "\" is not valid:\n"
1660                            + _validator.getMessage()
1661                            + "\nYou can either continue editing "
1662                            + "or revert to the last valid value \"" + _oldValue
1663                            + "\".",
1664                    "Invalid Text Entered", JOptionPane.YES_NO_OPTION,
1665                    JOptionPane.ERROR_MESSAGE, null, options, options[1]);
1666
1667            if (answer == 1) { //Revert!
1668                _comboBox.setSelectedItem(_oldValue);
1669                return true;
1670            }
1671
1672            return false;
1673        }
1674
1675        ///////////////////////////////////////////////////////////////////
1676        ////                         private variables                 ////
1677
1678        /** The combo box. */
1679        private JComboBox _comboBox;
1680
1681        /** Old value of the combo box. */
1682        private Object _oldValue;
1683
1684        /** True if the user wants to edit after having an invalid selection.*/
1685        private boolean _userWantsToEdit;
1686
1687        /** Class that validates the cell. */
1688        private CellValidator _validator;
1689    }
1690
1691    ///////////////////////////////////////////////////////////////////
1692    ////                         private methods                   ////
1693
1694    /** Create the MoML expression that represents the update. */
1695    private String _createMoMLUpdate(Hashtable updates, Hashtable portInfo,
1696            String currentPortName, String newPortName) {
1697        StringBuffer momlUpdate = new StringBuffer("<port name=\""
1698                + StringUtilities.escapeForXML(currentPortName) + "\">");
1699
1700        // Assumes that updates only contains keys that are in _columnNames.
1701        // Assumes that updates only contains COL_NAME as key if the
1702        // pre-existing port needs to be renamed.
1703        if (updates.containsKey(ColumnNames.COL_NAME)) {
1704            Boolean updateValue = (Boolean) updates.get(ColumnNames.COL_NAME);
1705
1706            if (updateValue.booleanValue()) {
1707                momlUpdate.append("<rename name=\""
1708                        + StringUtilities.escapeForXML(newPortName) + "\"/>");
1709            }
1710        }
1711
1712        if (updates.containsKey(ColumnNames.COL_INPUT)) {
1713            Boolean updateValue = (Boolean) updates.get(ColumnNames.COL_INPUT);
1714
1715            if (updateValue.booleanValue()) {
1716                if (((Boolean) portInfo.get(ColumnNames.COL_INPUT))
1717                        .booleanValue()) {
1718                    momlUpdate.append(_momlProperty("input"));
1719                } else {
1720                    momlUpdate.append(_momlProperty("input", null, "false"));
1721                }
1722            }
1723        }
1724
1725        if (updates.containsKey(ColumnNames.COL_OUTPUT)) {
1726            Boolean updateValue = (Boolean) updates.get(ColumnNames.COL_OUTPUT);
1727
1728            if (updateValue.booleanValue()) {
1729                if (((Boolean) portInfo.get(ColumnNames.COL_OUTPUT))
1730                        .booleanValue()) {
1731                    momlUpdate.append(_momlProperty("output"));
1732                } else {
1733                    momlUpdate.append(_momlProperty("output", null, "false"));
1734                }
1735            }
1736        }
1737
1738        if (updates.containsKey(ColumnNames.COL_MULTIPORT)) {
1739            Boolean updateValue = (Boolean) updates
1740                    .get(ColumnNames.COL_MULTIPORT);
1741
1742            if (updateValue.booleanValue()) {
1743                if (((Boolean) portInfo.get(ColumnNames.COL_MULTIPORT))
1744                        .booleanValue()) {
1745                    momlUpdate.append(_momlProperty("multiport"));
1746                } else {
1747                    momlUpdate
1748                            .append(_momlProperty("multiport", null, "false"));
1749                }
1750            }
1751        }
1752
1753        if (updates.containsKey(ColumnNames.COL_TYPE)) {
1754            Boolean updateValue = (Boolean) updates.get(ColumnNames.COL_TYPE);
1755
1756            if (updateValue.booleanValue()) {
1757                String type = (String) portInfo.get(ColumnNames.COL_TYPE);
1758
1759                if (type.equals("")) {
1760                    momlUpdate.append(_momlDeleteProperty("_type"));
1761                } else {
1762                    momlUpdate.append(_momlProperty("_type",
1763                            "ptolemy.actor.TypeAttribute",
1764                            StringUtilities.escapeForXML(type)));
1765                }
1766            }
1767        }
1768
1769        if (updates.containsKey(ColumnNames.COL_DIRECTION)) {
1770            Boolean updateValue = (Boolean) updates
1771                    .get(ColumnNames.COL_DIRECTION);
1772
1773            if (updateValue.booleanValue()) {
1774                String direction = (String) portInfo
1775                        .get(ColumnNames.COL_DIRECTION);
1776
1777                if (direction.equals("DEFAULT")) {
1778                    momlUpdate.append(_momlDeleteProperty("_cardinal"));
1779                } else {
1780                    momlUpdate.append(_momlProperty("_cardinal",
1781                            _STRING_ATTRIBUTE, direction));
1782                }
1783            }
1784        }
1785
1786        if (updates.containsKey(ColumnNames.COL_SHOW_NAME)) {
1787            Boolean updateValue = (Boolean) updates
1788                    .get(ColumnNames.COL_SHOW_NAME);
1789
1790            if (updateValue.booleanValue()) {
1791                if (((Boolean) portInfo.get(ColumnNames.COL_SHOW_NAME))
1792                        .booleanValue()) {
1793                    momlUpdate.append(_momlProperty("_showName",
1794                            _SINGLETON_PARAMETER, "true"));
1795                } else {
1796                    // NOTE: If there is already a property that is not
1797                    // a boolean-valued parameter, then remove it rather
1798                    // than setting it to false.  This is done for more
1799                    // robust backward compatibility.
1800                    boolean removed = false;
1801                    Port port = (Port) portInfo
1802                            .get(ColumnNames.COL_ACTUAL_PORT);
1803
1804                    if (port != null) {
1805                        Attribute attribute = port.getAttribute("_showName");
1806
1807                        if (!(attribute instanceof Parameter)) {
1808                            momlUpdate.append(_momlDeleteProperty("_showName"));
1809                            removed = true;
1810                        }
1811                    }
1812
1813                    if (!removed) {
1814                        momlUpdate.append(_momlProperty("_showName",
1815                                _SINGLETON_PARAMETER, "false"));
1816                    }
1817                }
1818            }
1819        }
1820
1821        if (updates.containsKey(ColumnNames.COL_HIDE)) {
1822            Boolean updateValue = (Boolean) updates.get(ColumnNames.COL_HIDE);
1823
1824            if (updateValue.booleanValue()) {
1825                if (((Boolean) portInfo.get(ColumnNames.COL_HIDE))
1826                        .booleanValue()) {
1827                    momlUpdate.append(_momlProperty("_hide",
1828                            _SINGLETON_PARAMETER, "true"));
1829                } else {
1830                    // NOTE: If there is already a property that is not
1831                    // a boolean-valued parameter, then remove it rather
1832                    // than setting it to false.  This is done for more
1833                    // robust backward compatibility.
1834                    boolean removed = false;
1835                    Port port = (Port) portInfo
1836                            .get(ColumnNames.COL_ACTUAL_PORT);
1837
1838                    if (port != null) {
1839                        Attribute attribute = port.getAttribute("_hide");
1840
1841                        if (!(attribute instanceof Parameter)) {
1842                            momlUpdate.append(_momlDeleteProperty("_hide"));
1843                            removed = true;
1844                        }
1845                    }
1846
1847                    if (!removed) {
1848                        momlUpdate.append(_momlProperty("_hide",
1849                                _SINGLETON_PARAMETER, "false"));
1850                    }
1851                }
1852            }
1853        }
1854
1855        if (updates.containsKey(ColumnNames.COL_UNITS)) {
1856            Boolean updateValue = (Boolean) updates.get(ColumnNames.COL_UNITS);
1857
1858            if (updateValue.booleanValue()) {
1859                momlUpdate.append(_momlProperty("_units", _UNIT_ATTRIBUTE,
1860                        (String) portInfo.get(ColumnNames.COL_UNITS)));
1861            }
1862        }
1863
1864        momlUpdate.append("</port>");
1865        return momlUpdate.toString();
1866    }
1867
1868    /** Create a JComboBox with the appropriate listeners. */
1869    private JComboBox _createComboBox() {
1870        JComboBox jComboBox = new JComboBox();
1871
1872        // If the user types in the comboBox, enable Apply.
1873        jComboBox.getEditor().getEditorComponent()
1874                .addKeyListener(new KeyAdapter() {
1875                    @Override
1876                    public void keyTyped(KeyEvent ke) {
1877                        _setDirty(true);
1878                        _enableApplyButton(true);
1879                    }
1880                });
1881        jComboBox.getEditor().getEditorComponent()
1882                .addFocusListener(new FocusListener() {
1883                    @Override
1884                    public void focusGained(FocusEvent event) {
1885                        // Set the selected row so the remove key gets updated
1886                        _setSelectedRow(_portTable.getSelectionModel()
1887                                .getAnchorSelectionIndex());
1888                    }
1889
1890                    @Override
1891                    public void focusLost(FocusEvent event) {
1892                    }
1893                });
1894        jComboBox.setEditable(true);
1895
1896        // Add this item first so it is first on the list.
1897        jComboBox.addItem("");
1898        return jComboBox;
1899    }
1900
1901    /** Generate a combo box based on the type names. */
1902    private JComboBox _createPortTypeComboBox() {
1903        JComboBox jComboBox = _createComboBox();
1904
1905        //         // Add the types from data.expr.Constants
1906        //         TreeMap typeMap = Constants.types();
1907        //         Iterator types = typeMap.keySet().iterator();
1908
1909        //         while (types.hasNext()) {
1910        //             String type = (String) (types.next());
1911        //             jComboBox.addItem(type);
1912        //         }
1913
1914        // Get the types from the TypeLattice.
1915        // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5627
1916        Object[] types = ((DirectedAcyclicGraph) TypeLattice.basicLattice())
1917                .topologicalSort();
1918        List<String> typeList = new LinkedList<String>();
1919        for (Object type : types) {
1920            typeList.add(type.toString());
1921        }
1922        // Add some common types
1923        typeList.add("arrayType(int)");
1924        typeList.add("arrayType(int,5)");
1925        typeList.add("{x=double, y=double}");
1926        typeList.add("record");
1927
1928        Collections.sort(typeList);
1929
1930        for (String typeName : typeList) {
1931            jComboBox.addItem(typeName);
1932        }
1933        return jComboBox;
1934    }
1935
1936    /** Generate a combo box based on the unit names. */
1937    private JComboBox _createPortUnitComboBox() {
1938        JComboBox jComboBox = _createComboBox();
1939
1940        ArrayList unitsArrayList = ptolemy.data.unit.UnitUtilities
1941                .categoryList();
1942        Collections.sort(unitsArrayList);
1943
1944        Iterator units = unitsArrayList.iterator();
1945
1946        while (units.hasNext()) {
1947            String unit = (String) units.next();
1948            jComboBox.addItem(unit);
1949        }
1950
1951        // Add these items last so they are at the bottom.
1952        jComboBox.addItem("meter second ^-1");
1953        return jComboBox;
1954    }
1955
1956    private void _enableApplyButton(boolean e) {
1957        _applyButton.setEnabled(e);
1958    }
1959
1960    // Initialize which columns will be visible for this target.
1961    private void _initColumnNames() {
1962        // Get the Entity for which we are configuring the ports.
1963        Entity target = getTarget();
1964
1965        // Set up the column names that will be visible.
1966        String[] tempColumnNames = null;
1967
1968        if (target instanceof TypedActor) {
1969            String[] temp = { ColumnNames.COL_NAME, ColumnNames.COL_INPUT,
1970                    ColumnNames.COL_OUTPUT, ColumnNames.COL_MULTIPORT,
1971                    ColumnNames.COL_TYPE, ColumnNames.COL_DIRECTION,
1972                    ColumnNames.COL_SHOW_NAME, ColumnNames.COL_HIDE,
1973                    ColumnNames.COL_UNITS, };
1974            tempColumnNames = temp;
1975        } else if (target instanceof Actor) {
1976            String[] temp = { ColumnNames.COL_NAME, ColumnNames.COL_INPUT,
1977                    ColumnNames.COL_OUTPUT, ColumnNames.COL_MULTIPORT,
1978                    ColumnNames.COL_DIRECTION, ColumnNames.COL_SHOW_NAME,
1979                    ColumnNames.COL_HIDE, };
1980            tempColumnNames = temp;
1981        } else {
1982            String[] temp = { ColumnNames.COL_NAME, ColumnNames.COL_DIRECTION,
1983                    ColumnNames.COL_SHOW_NAME, ColumnNames.COL_HIDE, };
1984            tempColumnNames = temp;
1985        }
1986
1987        // Store the column names as an ArrayList.
1988        List columnList = Arrays.asList(tempColumnNames);
1989        _columnNames = new ArrayList(columnList);
1990    }
1991
1992    // Initialize the displayed column widths.
1993    private void _initColumnSizes() {
1994        TableColumn column = null;
1995
1996        if (_columnNames.contains(ColumnNames.COL_INPUT)) {
1997            int index = _columnNames.indexOf(ColumnNames.COL_INPUT);
1998            column = _portTable.getColumnModel().getColumn(index);
1999            column.setPreferredWidth(30);
2000        }
2001
2002        if (_columnNames.contains(ColumnNames.COL_OUTPUT)) {
2003            int index = _columnNames.indexOf(ColumnNames.COL_OUTPUT);
2004            column = _portTable.getColumnModel().getColumn(index);
2005            column.setPreferredWidth(30);
2006        }
2007
2008        if (_columnNames.contains(ColumnNames.COL_MULTIPORT)) {
2009            int index = _columnNames.indexOf(ColumnNames.COL_MULTIPORT);
2010            column = _portTable.getColumnModel().getColumn(index);
2011            column.setPreferredWidth(40);
2012        }
2013
2014        if (_columnNames.contains(ColumnNames.COL_TYPE)) {
2015            int index = _columnNames.indexOf(ColumnNames.COL_TYPE);
2016            column = _portTable.getColumnModel().getColumn(index);
2017            // Set the preferred with of the type column to 100.
2018            // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5545
2019            // "The default horizontal space is too short to show a number of the default
2020            // options."
2021            column.setPreferredWidth(100);
2022            // Increase the row height.
2023            // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5545
2024            // "On OS X, when choosing or typing in a Type, there isn't enough vertical space
2025            // and characters can be hard to read."
2026            // If you change the height, then check that a few rows can be added,
2027            // see the _portTable.setPreferredScrollableViewportSize(new Dimension(... call above
2028            _portTable.setRowHeight(
2029                    (int) Math.round(_portTable.getRowHeight() * 1.20));
2030        }
2031
2032        if (_columnNames.contains(ColumnNames.COL_DIRECTION)) {
2033            int index = _columnNames.indexOf(ColumnNames.COL_DIRECTION);
2034            column = _portTable.getColumnModel().getColumn(index);
2035            column.setPreferredWidth(50);
2036        }
2037
2038        if (_columnNames.contains(ColumnNames.COL_SHOW_NAME)) {
2039            int index = _columnNames.indexOf(ColumnNames.COL_SHOW_NAME);
2040            column = _portTable.getColumnModel().getColumn(index);
2041            column.setPreferredWidth(70);
2042        }
2043
2044        if (_columnNames.contains(ColumnNames.COL_HIDE)) {
2045            int index = _columnNames.indexOf(ColumnNames.COL_HIDE);
2046            column = _portTable.getColumnModel().getColumn(index);
2047            column.setPreferredWidth(30);
2048        }
2049    }
2050
2051    /** Return true if the property of the specified name is set for
2052     *  the specified object. A property is specified if the specified
2053     *  object contains an attribute with the specified name and that
2054     *  attribute is either not a boolean-valued parameter, or it is a
2055     *  boolean-valued parameter with value true.
2056     *  @param object The object.
2057     *  @param name The property name.
2058     *  @return True if the property is set.
2059     */
2060    private boolean _isPropertySet(NamedObj object, String name) {
2061        Attribute attribute = object.getAttribute(name);
2062
2063        if (attribute == null) {
2064            return false;
2065        }
2066
2067        if (attribute instanceof Parameter) {
2068            try {
2069                Token token = ((Parameter) attribute).getToken();
2070
2071                if (token instanceof BooleanToken) {
2072                    if (!((BooleanToken) token).booleanValue()) {
2073                        return false;
2074                    }
2075                }
2076            } catch (IllegalActionException e) {
2077                // Ignore, using default of true.
2078            }
2079        }
2080
2081        return true;
2082    }
2083
2084    private String _momlDeleteProperty(String name) {
2085        return "<deleteProperty name=\"" + name + "\"/>";
2086    }
2087
2088    private String _momlProperty(String name) {
2089        return "<property name=\"" + name + "\"/>";
2090    }
2091
2092    private String _momlProperty(String name, String clz, String value) {
2093        if (clz != null) {
2094            return "<property name=\"" + name + "\" " + "class = \"" + clz
2095                    + "\" " + "value = \"" + value + "\"/>";
2096        }
2097
2098        return "<property name=\"" + name + "\" " + "value = \"" + value
2099                + "\"/>";
2100    }
2101
2102    private void _populateActualPorts() {
2103        for (int i = 0; i < _ports.size(); i++) {
2104            Hashtable portInfo = (Hashtable) _ports.elementAt(i);
2105            String portName = (String) portInfo.get(ColumnNames.COL_NAME);
2106            Iterator portIterator = getTarget().portList().iterator();
2107
2108            Port actualPort;
2109            boolean foundActualPort = false;
2110
2111            while (portIterator.hasNext()) {
2112                Object candidate = portIterator.next();
2113
2114                if (candidate instanceof Port) {
2115                    actualPort = (Port) candidate;
2116
2117                    if (actualPort.getName().equals(portName)) {
2118                        portInfo.put(ColumnNames.COL_ACTUAL_PORT, actualPort);
2119                        foundActualPort = true;
2120                        break;
2121                    }
2122                }
2123            }
2124
2125            if (!foundActualPort) {
2126                Exception exception = new InternalErrorException("Port \""
2127                        + portName + "\"stored in _ports " + "not found in \""
2128                        + getTarget().getFullName() + "\". "
2129                        + "This can occur when two port names are being swapped. "
2130                        + "The workaround when swapping A and B is to first set A to C, then C to A, then C to B. "
2131                        + "See http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4478");
2132                MessageHandler.error("Failed to find \"" + portName + "\",",
2133                        exception);
2134            }
2135        }
2136    }
2137
2138    private void _setSelectedRow(int row) {
2139        _selectedRow = row;
2140
2141        if (row < 0) {
2142            _removeButton.setText("Remove");
2143            _removeButton.setEnabled(false);
2144        } else {
2145            Hashtable portInfo = (Hashtable) _ports.elementAt(row);
2146            String portName = (String) portInfo.get(ColumnNames.COL_NAME);
2147
2148            // FIXME this depends on button name string length.
2149            if (portName.length() == 0) {
2150                portName = "#" + (row + 1);
2151            }
2152
2153            if (portName.length() < 10) {
2154                portName += "          ";
2155                portName = portName.substring(0, 9);
2156            } else if (portName.length() > 10) {
2157                portName = portName.substring(0, 7) + "...";
2158            }
2159
2160            _removeButton.setText("Remove " + portName);
2161
2162            // Some ports cannot be removed.
2163            _removeButton.setEnabled(_portTable.isCellEditable(row, 0));
2164        }
2165    }
2166
2167    /** Creates and sets the TableModel. Also arranges for some columns
2168     * to have their particular renderers and/or editors. This method
2169     * will be invoked when the dialog is created, and every time a
2170     * change request from above causes the table to change.
2171     */
2172    private void _setupTableModel() {
2173        _portTableModel = new PortTableModel(getTarget().portList());
2174        _portTable.setModel(_portTableModel);
2175        _portTable.setDefaultRenderer(Boolean.class,
2176                new PortBooleanCellRenderer());
2177        _portTable.setDefaultRenderer(String.class, new StringCellRenderer());
2178        _portTable.setDefaultEditor(String.class,
2179                new ValidatingJTextFieldCellEditor());
2180        _enableApplyButton(false);
2181
2182        if (_columnNames.contains(ColumnNames.COL_NAME)) {
2183            int col = _columnNames.indexOf(ColumnNames.COL_NAME);
2184            TableColumn _portNameColumn = _portTable.getColumnModel()
2185                    .getColumn(col);
2186            final ValidatingJTextFieldCellEditor portNameEditor = new ValidatingJTextFieldCellEditor(
2187                    new JFormattedTextField());
2188            _portNameColumn.setCellEditor(portNameEditor);
2189            portNameEditor.setValidator(new CellValidator() {
2190                /////////////////////////////////////////
2191                //////////// inner class/////////////////
2192                @Override
2193                public boolean isValid(String cellValue) {
2194                    int index = cellValue.indexOf(".");
2195
2196                    if (index >= 0) {
2197                        setMessage(cellValue + " contains a period in col "
2198                                + (index + 1));
2199                        return false;
2200                    }
2201
2202                    if (cellValue.equals("")) {
2203                        setMessage("Ports cannot have the empty string "
2204                                + "as a name.");
2205                        return false;
2206                    }
2207
2208                    return true;
2209                }
2210            });
2211        }
2212
2213        if (_columnNames.contains(ColumnNames.COL_DIRECTION)) {
2214            int col = _columnNames.indexOf(ColumnNames.COL_DIRECTION);
2215            TableColumn _portLocationColumn = _portTable.getColumnModel()
2216                    .getColumn(col);
2217            _portLocationColumn.setCellEditor(
2218                    new DefaultCellEditor(_portLocationComboBox));
2219        }
2220
2221        if (_columnNames.contains(ColumnNames.COL_TYPE)) {
2222            int col = _columnNames.indexOf(ColumnNames.COL_TYPE);
2223            TableColumn _portTypeColumn = _portTable.getColumnModel()
2224                    .getColumn(col);
2225
2226            final ValidatingComboBoxCellEditor portTypeEditor = new ValidatingComboBoxCellEditor(
2227                    _createPortTypeComboBox());
2228
2229            _portTypeColumn.setCellEditor(portTypeEditor);
2230            portTypeEditor.setValidator(new CellValidator() {
2231                /////////////////////////////////////////
2232                //////////// inner class/////////////////
2233                @Override
2234                public boolean isValid(String cellValue) {
2235                    try {
2236                        if (cellValue.equals("")) {
2237                            return true;
2238                        } else if (cellValue.equals("pointer")) {
2239                            return true;
2240                        }
2241
2242                        ASTPtRootNode tree = _typeParser
2243                                .generateParseTree(cellValue);
2244                        /* Token result = */_parseTreeEvaluator
2245                                .evaluateParseTree(tree, null);
2246                    } catch (IllegalActionException e) {
2247                        setMessage(e.getMessage());
2248                        return false;
2249                    }
2250
2251                    return true;
2252                }
2253            });
2254        }
2255
2256        if (_columnNames.contains(ColumnNames.COL_UNITS)) {
2257            int col = _columnNames.indexOf(ColumnNames.COL_UNITS);
2258            TableColumn _portUnitColumn = _portTable.getColumnModel()
2259                    .getColumn(col);
2260            final ValidatingComboBoxCellEditor portUnitEditor = new ValidatingComboBoxCellEditor(
2261                    _createPortUnitComboBox());
2262            _portUnitColumn.setCellEditor(portUnitEditor);
2263
2264            portUnitEditor.setValidator(new CellValidator() {
2265                /////////////////////////////////////////
2266                //////////// inner class/////////////////
2267                @Override
2268                public boolean isValid(String cellValue) {
2269                    try {
2270                        UnitLibrary.getParser().parseUnitExpr(cellValue);
2271                    } catch (ParseException e) {
2272                        setMessage(e.getMessage());
2273                        return false;
2274                    }
2275
2276                    return true;
2277                }
2278            });
2279        }
2280    }
2281
2282    ///////////////////////////////////////////////////////////////////
2283    ////                         private variables                 ////
2284
2285    /** List of names of columns that will be used for this target. */
2286    private ArrayList _columnNames;
2287
2288    /** When you click on the "Hide" column header, toggle this value.
2289     * @see ptolemy.actor.gui.PortConfigurerDialog.PortTableModel#toggleHidePorts()
2290     */
2291    private boolean _hideAllPorts = false;
2292
2293    /** The combination box used to select the location of a port. */
2294    private JComboBox _portLocationComboBox;
2295
2296    JTable _portTable;
2297
2298    PortTableModel _portTableModel = null;
2299
2300    /** JTableHeader of _portTable.  MouseListener is added to this. */
2301    JTableHeader _jth;
2302
2303    static ParseTreeEvaluator _parseTreeEvaluator = new ParseTreeEvaluator();
2304
2305    Vector _ports = null;
2306
2307    private int _selectedRow = -1;
2308
2309    private static String _SINGLETON_PARAMETER = "ptolemy.data.expr.SingletonParameter";
2310
2311    /** When you click on the "Show Name" column header, toggle this value.
2312     * @see ptolemy.actor.gui.PortConfigurerDialog.PortTableModel#toggleShowAllNames()
2313     */
2314    private boolean _showAllNames = false;
2315
2316    private static String _STRING_ATTRIBUTE = "ptolemy.kernel.util.StringAttribute";
2317
2318    static PtParser _typeParser = new PtParser();
2319
2320    private static String _UNIT_ATTRIBUTE = "ptolemy.data.unit.UnitAttribute";
2321
2322    /** The various buttons. */
2323    private JButton _applyButton;
2324
2325    /** The various buttons. */
2326    private JButton Button;
2327
2328    /** The various buttons. */
2329    private JButton _addButton;
2330
2331    /** True if the change request in _apply() failed */
2332    private boolean _applyChangeRequestFailed;
2333
2334    /** The various buttons. */
2335    private JButton _removeButton;
2336
2337    /** Strings that are available for the column names. */
2338    private static class ColumnNames {
2339        public final static String COL_NAME = "Name";
2340
2341        public final static String COL_INPUT = "Input";
2342
2343        public final static String COL_OUTPUT = "Output";
2344
2345        public final static String COL_MULTIPORT = "Multiport";
2346
2347        public final static String COL_TYPE = "Type";
2348
2349        public final static String COL_DIRECTION = "Direction";
2350
2351        public final static String COL_SHOW_NAME = "Show Name";
2352
2353        public final static String COL_HIDE = "Hide";
2354
2355        public final static String COL_UNITS = "Units";
2356
2357        public final static String COL_ACTUAL_PORT = "9";
2358    }
2359}