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->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}