001/* 002 * Copyright (c) 2012 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2015-08-24 22:44:14 +0000 (Mon, 24 Aug 2015) $' 007 * '$Revision: 33630 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029 030package org.kepler.gui.frame; 031 032import java.awt.BorderLayout; 033import java.awt.Component; 034import java.awt.Dimension; 035import java.awt.event.ActionEvent; 036import java.awt.event.ActionListener; 037import java.awt.event.InputEvent; 038import java.awt.event.KeyEvent; 039import java.awt.event.MouseEvent; 040import java.awt.event.MouseListener; 041import java.util.HashSet; 042import java.util.List; 043import java.util.Set; 044import java.util.regex.Matcher; 045import java.util.regex.Pattern; 046 047import javax.swing.JComponent; 048import javax.swing.JMenu; 049import javax.swing.JPanel; 050import javax.swing.JTabbedPane; 051import javax.swing.KeyStroke; 052import javax.swing.event.ChangeEvent; 053import javax.swing.event.ChangeListener; 054 055import org.kepler.gui.KeplerGraphFrame; 056import org.kepler.gui.ModelToFrameManager; 057import org.kepler.gui.TabManager; 058import org.kepler.gui.TabPane; 059import org.kepler.gui.TabbedCompositeTabComponent; 060import org.kepler.gui.WorkflowOutlineTabPane; 061import org.kepler.gui.state.StateChangeMonitor; 062import org.kepler.moml.NamedObjIdChangeRequest; 063import org.kepler.objectmanager.ObjectManager; 064 065import diva.canvas.event.LayerAdapter; 066import diva.canvas.event.LayerEvent; 067import diva.graph.GraphModel; 068import diva.graph.GraphPane; 069import diva.graph.JGraph; 070import ptolemy.actor.CompositeActor; 071import ptolemy.actor.gui.Configuration; 072import ptolemy.actor.gui.SizeAttribute; 073import ptolemy.actor.gui.Tableau; 074import ptolemy.actor.lib.hoc.MultiCompositeActor; 075import ptolemy.kernel.CompositeEntity; 076import ptolemy.kernel.util.ChangeRequest; 077import ptolemy.kernel.util.IllegalActionException; 078import ptolemy.kernel.util.NameDuplicationException; 079import ptolemy.kernel.util.NamedObj; 080import ptolemy.moml.LibraryAttribute; 081import ptolemy.moml.MoMLChangeRequest; 082import ptolemy.util.MessageHandler; 083import ptolemy.vergil.actor.ActorEditorGraphController; 084import ptolemy.vergil.actor.ActorGraphModel; 085import ptolemy.vergil.basic.AbstractBasicGraphModel; 086import ptolemy.vergil.basic.EditorDropTarget; 087 088/** This is a graph editor frame that uses a tabbed pane to display 089 multiple composite actors. 090 091 TODO: 092 093 go to actor in exception dialog does not switch tabs 094 fix NamedObjId errors when closing (KGF calls dispose() twice, second time getModel() returns null) 095 096 DONE: 097 zoom and window position not saved in composite tabs 098 fix: override of updateWindowAttributes() along with refactoring methods 099 in BasicGraphFrame for getting the center, etc. 100 support nested tabs for case/execution choice actors 101 double-click on tab to make separate window 102 frame close button closes tab instead of frame 103 fix: remove _close() override 104 add non-composites to tab: atomic actor source code 105 difficult since not opened in graph frame 106 does not save window size? see bug http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5637 107 do not place in tabbed pane when no other composites open 108 canvas scroll bars missing 109 fix: update scrollbars to use selected sub-workflow in stateChanged() 110 update outline tree when selected tab changes 111 fix: refresh outline pane in stateChanged() 112 change color of X for closing tab on rollover 113 new in/out port buttons on toolbar place ports in top level instead of subwf 114 fix: uncomment TabbedKeplerController.getGraphModel(), see next fix. 115 one actor in composites rendered wrong when opened the first time 116 fix: in _createGraphPane 117 resize tab component on renames 118 fix: call invalidate() in TabbedCompositeTabComponent.refreshName() 119 cmd-l on composite not already opened should open in new window, not tab 120 fix: added LookInsideTabbedComposite.actionIsRunning() 121 rename tabs when composite names are changed 122 rename tabs when parent composite is renamed 123 delete tabs when composite is deleted 124 delete tabs when composite parent is deleted 125 ports are displayed wrong (probably controller) 126 fix: updated ActorEditorGraphController to add port layout listener when using alternative actor controllers 127 when tab changes, panner is repainted, but panner does not show actor highlight changes 128 (same behavior in ptolemy; no fix) 129 file->close / cmd-w should close selected tab 130 open composite on one already opened set selected tab 131 add X button to tabs to close 132 close tab via button, then can't open again (need to close tableau?) 133 frame title is wrong: seems to be set to last opened composite 134 focus stops working (fix: use MultiCompositeController from CaseGraphFrame?) 135 cmd-l on already opened composite should switch to tab 136 clean up LookInsideAction, controller stuff 137 138 @author Daniel Crawl 139 @version $Id: TabbedKeplerGraphFrame.java 33630 2015-08-24 22:44:14Z crawl $ 140 141*/ 142public class TabbedKeplerGraphFrame extends KeplerGraphFrame implements ChangeListener { 143 /** Construct a frame associated with the specified composite actor. 144 * After constructing this, it is necessary 145 * to call setVisible(true) to make the frame appear. 146 * This is typically done by calling show() on the controlling tableau. 147 * This constructor results in a graph frame that obtains its library 148 * either from the model (if it has one) or the default library defined 149 * in the configuration. 150 * @see Tableau#show() 151 * @param entity The model to put in this frame. 152 * @param tableau The tableau responsible for this frame. 153 */ 154 public TabbedKeplerGraphFrame(CompositeActor entity, Tableau tableau) { 155 this(entity, tableau, null); 156 } 157 158 /** Construct a frame associated with the specified composite actor. 159 * After constructing this, it is necessary 160 * to call setVisible(true) to make the frame appear. 161 * This is typically done by calling show() on the controlling tableau. 162 * This constructor results in a graph frame that obtains its library 163 * either from the model (if it has one), or the <i>defaultLibrary</i> 164 * argument (if it is non-null), or the default library defined 165 * in the configuration. 166 * @see Tableau#show() 167 * @param entity The model to put in this frame. 168 * @param tableau The tableau responsible for this frame. 169 * @param defaultLibrary An attribute specifying the default library 170 * to use if the model does not have a library. 171 */ 172 public TabbedKeplerGraphFrame(CompositeActor entity, Tableau tableau, 173 LibraryAttribute defaultLibrary) { 174 super(entity, tableau, defaultLibrary); 175 } 176 177 /////////////////////////////////////////////////////////////////// 178 //// public methods //// 179 180 /** Add a new tab for a model. */ 181 public void addComposite(CompositeEntity model, 182 TabbedKeplerGraphTableau keplerGraphMultiCompositeTableau, 183 LibraryAttribute defaultLibrary) { 184 185 Component component = _addCompositeToPane(model, keplerGraphMultiCompositeTableau); 186 if(component instanceof JGraph) { 187 ((TabbedKeplerController) _controller)._addHotKeys((JGraph)component); 188 } 189 190 try { 191 if(component instanceof JGraph) { 192 _setJGraphSize((JGraph) component, model); 193 _initBasicGraphFrameSetZoomAndPane(model, (JGraph) component); 194 } else if(component instanceof JTabbedPane) { 195 for (Component subComponent : ((JTabbedPane)component).getComponents()) { 196 if (subComponent instanceof JGraph) { 197 JGraph subGraph = (JGraph) subComponent; 198 NamedObj subModel = ((AbstractBasicGraphModel) subGraph.getGraphPane().getGraphModel()).getPtolemyModel(); 199 _setJGraphSize(subGraph, subModel); 200 _initBasicGraphFrameSetZoomAndPane(subModel, subGraph); 201 } 202 } 203 } 204 } catch (IllegalActionException e) { 205 MessageHandler.error("Error setting zoom and pane.", e); 206 } 207 208 } 209 210 /** Add a menu to the menu bar. */ 211 public void addMenu(JMenu menu) { 212 _menubar.add(menu); 213 setJMenuBar(_menubar); 214 } 215 216 /** Create a new tab displaying the contents of a composite actor 217 * inside of an existing tab for a MultiCompositeActor. 218 */ 219 public void addSubComposite(CompositeEntity composite) { 220 221 final CompositeEntity container = (CompositeEntity) composite.getContainer(); 222 223 // find the tab belonging to the container 224 Component containerComponent = _findComponentForModel(container); 225 if(containerComponent == null) { 226 MessageHandler.error("ERROR: could not find container component for " + container.getFullName()); 227 return; 228 } 229 230 if(!(containerComponent instanceof JTabbedPane)) { 231 MessageHandler.error("ERROR: container component for " + container.getFullName() + 232 " is not a tabbed pane."); 233 return; 234 } 235 236 JTabbedPane tabbedPane = (JTabbedPane)containerComponent; 237 Component compositeComponent = _createComponentForComposite(composite); 238 tabbedPane.add(compositeComponent); 239 _modelManager.add(composite, this); 240 241 } 242 243 /** Listen for name changes in the model. */ 244 @Override 245 public void changeExecuted(ChangeRequest change) { 246 247 //System.out.println(change); 248 249 if(_tabbedPane != null && (change instanceof MoMLChangeRequest) && 250 !(change instanceof NamedObjIdChangeRequest)) { 251 //System.out.println("source = " + change.getSource()); 252 //System.out.println("context = " + ((MoMLChangeRequest)change).getContext()); 253 //System.out.println("description = " + change.getDescription()); 254 255 final NamedObj context = ((MoMLChangeRequest)change).getContext(); 256 final String description = change.getDescription(); 257 258 // see if something was renamed 259 Matcher matcher = _MOML_RENAME_PATTERN.matcher(description); 260 if(matcher.find()) { 261 //System.out.println("g1 = " + matcher.group(1) + " g2 = " + matcher.group(2)); 262 // get the previous name 263 final String oldName = matcher.group(1); 264 265 // construct the full name 266 final String oldRenamedFullName = context.getFullName() + "." + oldName; 267 // find the tab (if one exists) for the renamed composite. 268 for(int i = 0; i < _tabbedPane.getTabCount(); i++) { 269 final TabbedCompositeTabComponent component = 270 (TabbedCompositeTabComponent) _tabbedPane.getTabComponentAt(i); 271 final String componentFullName = component.getModelFullName(); 272 if(componentFullName.startsWith(oldRenamedFullName)) { 273 component.refreshName(); 274 } 275 } 276 } else { 277 // see if something was deleted 278 matcher = _MOML_DELETE_PATTERN.matcher(description); 279 if(matcher.find()) { 280 // get the item that was deleted 281 final String deletedName = matcher.group(1); 282 283 // construct the full name of the item deleted 284 final String deleteFullName = context.getFullName() + "." + deletedName; 285 final Set<Component> tabsToDelete = new HashSet<Component>(); 286 for(int i = 0; i < _tabbedPane.getTabCount(); i++) { 287 final TabbedCompositeTabComponent tabComponent = 288 (TabbedCompositeTabComponent) _tabbedPane.getTabComponentAt(i); 289 final String componentFullName = tabComponent.getModelFullName(); 290 // find the tab (if one exists) for the deleted composite. 291 // also find any tabs that are models contained in the delete composite. 292 if(componentFullName.startsWith(deleteFullName)) { 293 tabsToDelete.add(_tabbedPane.getComponentAt(i)); 294 } 295 } 296 297 // remove the tabs 298 for(Component component : tabsToDelete) { 299 _removeTab(component); 300 } 301 } 302 } 303 } 304 305 // tell the parent class about the change 306 // NOTE: this is necessary since KeplerGraphFrame.changeExecuted() 307 // sets the frame as modified and repaints the jgraph and panner 308 super.changeExecuted(change); 309 } 310 311 /** Free resources when closing. */ 312 @Override 313 public void dispose() { 314 315 // remove the sub-workflows in tabs from the object manager and 316 // model to frame manager. 317 if(_tabbedPane != null) { 318 for(Component component : _tabbedPane.getComponents()) { 319 _disposeComponent(component); 320 } 321 _tabbedPane.removeAll(); 322 } 323 324 super.dispose(); 325 } 326 327 /** Get the controller for a model contained in this frame. */ 328 /* FIXME: add support for tab containing a tabbed pane. 329 public GraphController getControllerForModel(NamedObj model) { 330 int tabCount = _tabbedPane.getTabCount(); 331 for(int i = 0; i < tabCount; i++) { 332 GraphPane pane = ((JGraph) _tabbedPane.getComponentAt(i)).getGraphPane(); 333 if(((AbstractBasicGraphModel)pane.getGraphModel()).getPtolemyModel() == model) { 334 return pane.getGraphController(); 335 } 336 } 337 return null; 338 } 339 */ 340 341 /** Return the JGraph instance that this view uses to represent the 342 * ptolemy model. 343 * @return the JGraph. 344 * @see #setJGraph(JGraph) 345 */ 346 @Override 347 public JGraph getJGraph() { 348 if(_tabbedPane == null) { 349 return super.getJGraph(); 350 } else { 351 Component selectedTab = _tabbedPane.getSelectedComponent(); 352 // see if nothing is selected, which means the window is closing. 353 if(selectedTab == null) { 354 return super.getJGraph(); 355 } else if(selectedTab instanceof JGraph) { 356 return (JGraph) selectedTab; 357 } else if(selectedTab instanceof JTabbedPane) { 358 Component selectedSubTab = ((JTabbedPane)selectedTab).getSelectedComponent(); 359 if(selectedSubTab instanceof JGraph) { 360 return (JGraph) selectedSubTab; 361 } 362 } 363 } 364 System.err.println("WARNING: getJGraph() could not find JGraph."); 365 return null; 366 } 367 368 /** Return the model associated with the selected tab. If no tab 369 * is selected, returns the root model associated with this frame. 370 */ 371 public NamedObj getSelectedModel() { 372 if (_tabbedPane == null) { 373 return getModel(); 374 } else { 375 Component tab = _tabbedPane.getSelectedComponent(); 376 if (tab instanceof JGraph) { 377 GraphPane pane = ((JGraph) tab).getGraphPane(); 378 return ((AbstractBasicGraphModel)pane.getGraphModel()).getPtolemyModel(); 379 } else if(tab instanceof JTabbedPane) { 380 Component subTab = ((JTabbedPane)tab).getSelectedComponent(); 381 if(subTab instanceof JGraph) { 382 GraphPane pane = ((JGraph) subTab).getGraphPane(); 383 return ((AbstractBasicGraphModel)pane.getGraphModel()).getPtolemyModel(); 384 } 385 } 386 } 387 return null; 388 } 389 390 /** Remove a workflow from the frame. */ 391 public void removeComposite(NamedObj model) { 392 Component component = _findComponentForModel(model); 393 if(component != null) { 394 _removeTab(component); 395 } 396 } 397 398 /** Remove a menu from the menu bar. */ 399 public void removeMenu(JMenu menu) { 400 _menubar.remove(menu); 401 setJMenuBar(_menubar); 402 } 403 404 /** Set the selected tab to the tab containing a specific model. */ 405 public void setSelectedTab(NamedObj model) { 406 Component component = _findComponentForModel(model); 407 if(component != null) { 408 _tabbedPane.setSelectedComponent(component); 409 } 410 } 411 412 /** React to a change in the state of the tabbed pane. 413 * @param event The event. 414 */ 415 @Override 416 public void stateChanged(ChangeEvent event) { 417 final Object source = event.getSource(); 418 419 if (source instanceof JTabbedPane) { 420 final Component selected = ((JTabbedPane) source).getSelectedComponent(); 421 422 JGraph selectedJGraph = null; 423 NamedObj modelInTab = null; 424 425 if (selected instanceof JGraph) { 426 427 selectedJGraph = (JGraph) selected; 428 modelInTab = ((AbstractBasicGraphModel)selectedJGraph.getGraphPane(). 429 getGraphModel()).getPtolemyModel(); 430 431 } else if(selected instanceof JTabbedPane) { 432 433 JTabbedPane selectedTabbedPane = (JTabbedPane)selected; 434 Component subSelected = selectedTabbedPane.getSelectedComponent(); 435 436 if(subSelected instanceof JGraph) { 437 selectedJGraph = (JGraph) subSelected; 438 modelInTab = ((AbstractBasicGraphModel)selectedJGraph.getGraphPane(). 439 getGraphModel()).getPtolemyModel().getContainer(); 440 } 441 442 } 443 444 if(selectedJGraph != null) { 445 446 setJGraph(selectedJGraph); 447 selected.requestFocus(); 448 //System.out.println("changed focus"); 449 450 //notify any listeners that we've changed selected components 451 StateChangeMonitor.getInstance().notifyStateChange( 452 new TabbedKeplerGraphFrameEvent( 453 (JTabbedPane) source, this, modelInTab)); 454 455 if (_graphPanner != null) { 456 _graphPanner.setCanvas(selectedJGraph); 457 _graphPanner.repaint(); 458 //System.out.println("repainted panner"); 459 } 460 461 // update the outline tab, if present, to be rooted at the 462 // selected sub-workflow 463 TabManager manager = TabManager.getInstance(); 464 TabPane outlinePane = manager.getTab(this, WorkflowOutlineTabPane.class); 465 if(outlinePane != null) { 466 ((WorkflowOutlineTabPane)outlinePane).setWorkflow((CompositeEntity) modelInTab); 467 } 468 469 // update the scrollbars to use the selected sub-workflow 470 if(_horizontalScrollBar != null) { 471 _horizontalScrollBar.setModel(getJGraph().getGraphPane().getCanvas() 472 .getHorizontalRangeModel()); 473 } 474 if(_verticalScrollBar != null) { 475 _verticalScrollBar.setModel(getJGraph().getGraphPane().getCanvas() 476 .getVerticalRangeModel()); 477 } 478 479 } 480 } 481 } 482 483 /** Update the nested tabs displayed for a composite actor. This method 484 * removes tabs for sub-composites that no longer exist, and renames 485 * the tab titles to match the names of the sub-composites. 486 */ 487 public void updateTabsForComposite(CompositeEntity composite) { 488 489 if(composite instanceof MultiCompositeActor) { 490 Component component = _findComponentForModel(composite); 491 if(component == null) { 492 System.err.println("ERROR: could not find component for " + composite.getFullName()); 493 return; 494 } 495 if(!(component instanceof JTabbedPane)) { 496 System.err.println("ERROR: component for " + composite.getFullName() + 497 "is not a tabbed pane."); 498 return; 499 } 500 501 JTabbedPane tabbedPane = (JTabbedPane)component; 502 503 Set<Component> subComponentsToBeRemoved = new HashSet<Component>(); 504 for(Component subComponent : tabbedPane.getComponents()) { 505 if(subComponent instanceof JGraph) { 506 507 final int index = tabbedPane.indexOfComponent(subComponent); 508 509 final NamedObj modelInTab = ((AbstractBasicGraphModel)((JGraph) subComponent). 510 getGraphPane().getGraphModel()).getPtolemyModel(); 511 String name = modelInTab.getName(); 512 if(composite.getEntity(name) == null) { 513 //System.out.println("going to remove " + name); 514 subComponentsToBeRemoved.add(subComponent); 515 } else if(!tabbedPane.getTitleAt(index).equals(name)) { 516 tabbedPane.setTitleAt(index, name); 517 //System.out.println("renaming to " + name); 518 } 519 } 520 } 521 522 for(Component subComponent : subComponentsToBeRemoved) { 523 tabbedPane.remove(subComponent); 524 525 final NamedObj modelInTab = ((AbstractBasicGraphModel)((JGraph) subComponent). 526 getGraphPane().getGraphModel()).getPtolemyModel(); 527 _modelManager.removeModel(modelInTab); 528 } 529 } 530 } 531 532 /** Update the size, zoom and position of all the open tabs. 533 * This method is typically called when closing the window 534 * or writing the moml file out. 535 * @exception IllegalActionException If there is a problem 536 * getting a parameter. 537 * @exception NameDuplicationException If there is a problem 538 * creating a parameter. 539 */ 540 @Override 541 public void updateWindowAttributes() throws IllegalActionException, 542 NameDuplicationException { 543 544 if (_tabbedPane == null) { 545 super.updateWindowAttributes(); 546 } else { 547 _updateWindowAttributesForTabs(_tabbedPane); 548 } 549 } 550 551 /////////////////////////////////////////////////////////////////// 552 //// protected methods //// 553 554 /** Create a new graph pane. Note that this method is called in 555 * constructor of the base class, so it must be careful to not reference 556 * local variables that may not have yet been created. 557 * This overrides the base class to create a specialized 558 * graph controller (an inner class). 559 * @param entity The object to be displayed in the pane. 560 * @return The pane that is created. 561 */ 562 @Override 563 protected GraphPane _createGraphPane(NamedObj entity) { 564 565 566 ActorEditorGraphController controller; 567 568 // the first time this is called, set the _controller. 569 // otherwise, use an ActorEditorGraphController for the controller. 570 // NOTE: if the controller is always set to a TabbedKeplerController, 571 // it causes one actor to always be rendered twice on the canvas the 572 // first time the tab is opened. 573 574 if(_controller == null) { 575 _controller = new TabbedKeplerController(); 576 controller = _controller; 577 } else { 578 controller = new ActorEditorGraphController(); 579 } 580 581 controller.setConfiguration(getConfiguration()); 582 controller.setFrame(this); 583 584 final ActorGraphModel graphModel = new ActorGraphModel(entity); 585 return new GraphPane(controller, graphModel); 586 } 587 588 /** Create the component that goes to the right of the library. 589 * NOTE: This is called in the base class constructor, before 590 * things have been initialized. Hence, it cannot reference 591 * local variables. 592 * @param entity The entity to display in the component. 593 * @return The component that goes to the right of the library. 594 */ 595 @Override 596 protected JComponent _createRightComponent(NamedObj entity) { 597 if (!(entity instanceof CompositeActor)) { 598 return super._createRightComponent(entity); 599 } 600 601 _panel = new JPanel(new BorderLayout()); 602 603 final Component component = 604 _addCompositeToPane((CompositeEntity) entity, (TabbedKeplerGraphTableau) getTableau()); 605 606 if(component instanceof JGraph) { 607 setJGraph((JGraph) component); 608 } else { 609 throw new RuntimeException("Did not get JGraph for composite actor."); 610 } 611 612 // listen for changes so that we can change titles when the 613 // composite name changes, or close tabs when composites are 614 // deleted from the model. 615 entity.addChangeListener(this); 616 617 return _panel; 618 } 619 620 /////////////////////////////////////////////////////////////////// 621 //// private methods //// 622 623 /** Add a tabbed pane for the specified composite. 624 * @param composite The composite. 625 * @param newPane True to add the pane prior to the last pane. 626 * @return The pane. 627 */ 628 private Component _addCompositeToPane(CompositeEntity composite, 629 TabbedKeplerGraphTableau tableau) { 630 631 final Component component = _createComponentForComposite(composite); 632 633 // see if this is the first composite to be added 634 if(_panel.getComponentCount() == 0) { 635 _panel.add(component, BorderLayout.CENTER); 636 _rootComposite = composite; 637 _rootTableau = tableau; 638 } else { 639 640 if(_tabbedPane == null) { 641 642 _tabbedPane = _createJTabbedPane(); 643 644 // remove the first workflow from the pane and put in the tab 645 final JGraph firstJgraph = (JGraph) _panel.getComponent(0); 646 _panel.remove(firstJgraph); 647 _tabbedPane.add(firstJgraph, BorderLayout.CENTER); 648 int index = _tabbedPane.indexOfComponent(firstJgraph); 649 TabbedCompositeTabComponent tabComponent = 650 new TabbedCompositeTabComponent(_rootComposite, _rootTableau); 651 _tabbedPane.setTabComponentAt(index, tabComponent); 652 _tabbedPane.setSelectedIndex(index); 653 _panel.add(_tabbedPane, BorderLayout.CENTER); 654 } 655 656 // add the new workflow 657 _tabbedPane.add(component, BorderLayout.CENTER); 658 int index = _tabbedPane.indexOfComponent(component); 659 TabbedCompositeTabComponent tabComponent = 660 new TabbedCompositeTabComponent(composite, tableau); 661 _tabbedPane.setTabComponentAt(index, tabComponent); 662 _tabbedPane.setSelectedIndex(index); 663 } 664 665 if(_modelManager == null) { 666 _modelManager = ModelToFrameManager.getInstance(); 667 } 668 _modelManager.add(composite, this); 669 670 return component; 671 } 672 673 /** Create a Component displaying the composite actor. If the composite 674 * actor is a MultiCompositeActor containing multiple Refinements, 675 * this returns a JTabbedPane containing a tab with a JGraph for each 676 * of the Refinements. Otherwise, this returns a JGraph displaying 677 * the contents of the composite. 678 */ 679 private Component _createComponentForComposite(CompositeEntity composite) { 680 681 Component component = null; 682 683 if(composite instanceof MultiCompositeActor) { 684 685 JTabbedPane tabbedPane = _createJTabbedPane(); 686 687 List<CompositeEntity> containedComposites = composite.entityList(CompositeEntity.class); 688 for(CompositeEntity containedComposite : containedComposites) { 689 Component subComponent = _createComponentForComposite(containedComposite); 690 tabbedPane.add(subComponent);//, BorderLayout.CENTER); 691 _modelManager.add(containedComposite, this); 692 } 693 694 component = tabbedPane; 695 696 } else { 697 698 GraphPane pane = _createGraphPane(composite); 699 pane.getForegroundLayer().setPickHalo(2); 700 pane.getForegroundEventLayer().setConsuming(false); 701 pane.getForegroundEventLayer().setEnabled(true); 702 pane.getForegroundEventLayer().addLayerListener(new LayerAdapter() { 703 /** Invoked when the mouse is pressed on a layer 704 * or figure. 705 */ 706 @Override 707 public void mousePressed(LayerEvent event) { 708 Component eventComponent = event.getComponent(); 709 710 if (!eventComponent.hasFocus()) { 711 eventComponent.requestFocus(); 712 //System.out.println("set focus to " + component); 713 } 714 } 715 }); 716 component = new JGraph(pane); 717 718 component.setBackground(BACKGROUND_COLOR); 719 // Create a drop target for the jgraph. 720 // FIXME: Should override _setDropIntoEnabled to modify all the drop targets created. 721 new EditorDropTarget((JGraph) component); 722 723 } 724 725 String name = composite.getName(); 726 // if the composite does not have a name, use the effigy's name 727 if(name.isEmpty()) { 728 name = _getName(); 729 } 730 component.setName(name); 731 732 return component; 733 734 } 735 736 /** Do not override _close() since we want to close the frame if the frame's 737 * close button is pushed instead of closing the tab. 738 */ 739 /* 740 @Override 741 protected boolean _close() { 742 743 if(_tabbedPane == null) { 744 return super._close(); 745 } else { 746 // remove the selected tab 747 _removeTab(_tabbedPane.getSelectedComponent()); 748 return true; 749 } 750 } 751 */ 752 753 /** Remove the tab for a component. */ 754 private void _removeTab(Component component) { 755 756 /* 757 if(component instanceof JGraph) { 758 //final JGraph jgraph = (JGraph) component; 759 //final NamedObj modelInTab = 760 //((AbstractBasicGraphModel)jgraph.getGraphPane().getGraphModel()).getPtolemyModel(); 761 //modelInTab.removeChangeListener(this); 762 */ 763 764 final int index = _tabbedPane.indexOfComponent(component); 765 final TabbedCompositeTabComponent tabComponent = 766 (TabbedCompositeTabComponent) _tabbedPane.getTabComponentAt(index); 767 final Tableau tableau = tabComponent.getTableau(); 768 try { 769 tableau.setContainer(null); 770 } catch (Exception e) { 771 MessageHandler.error("Error closing tableau.", e); 772 } 773 774 if(component instanceof JGraph) { 775 final NamedObj modelInTab = 776 ((AbstractBasicGraphModel)((JGraph) component).getGraphPane().getGraphModel()).getPtolemyModel(); 777 _modelManager.removeModel(modelInTab); 778 } else if(component instanceof JTabbedPane) { 779 JTabbedPane subPane = (JTabbedPane)component; 780 NamedObj container = null; 781 for(Component subComponent : subPane.getComponents()) { 782 if(subComponent instanceof JGraph) { 783 final NamedObj modelInTab = 784 ((AbstractBasicGraphModel)((JGraph) subComponent).getGraphPane().getGraphModel()).getPtolemyModel(); 785 _modelManager.removeModel(modelInTab); 786 container = modelInTab.getContainer(); 787 } 788 } 789 subPane.removeChangeListener(this); 790 subPane.removeAll(); 791 _modelManager.removeModel(container); 792 } 793 794 _tabbedPane.remove(component); 795 796 797 // if only one tab left, move back to panel 798 if(_tabbedPane.getTabCount() == 1) { 799 final Component firstComponent = _tabbedPane.getComponentAt(0); 800 _panel.remove(_tabbedPane); 801 _tabbedPane.removeChangeListener(this); 802 _tabbedPane = null; 803 _panel.add(firstComponent, BorderLayout.CENTER); 804 } 805 } 806 807 /** Create a tabbed pane for the composite models. */ 808 private JTabbedPane _createJTabbedPane() { 809 810 JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.WRAP_TAB_LAYOUT); 811 tabbedPane.addChangeListener(this); 812 //_tabbedPane.setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT); 813 814 // register keyboard shortcut to scroll tabs left 815 final ActionListener actionSelectLeft = new ActionListener() { 816 @Override 817 public void actionPerformed(ActionEvent event) { 818 final JTabbedPane pane = (JTabbedPane)event.getSource(); 819 final int newIndex = pane.getSelectedIndex() - 1; 820 pane.setSelectedIndex(newIndex < 0 ? pane.getTabCount() - 1 : newIndex); 821 } 822 }; 823 824 tabbedPane.registerKeyboardAction(actionSelectLeft, 825 KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.META_DOWN_MASK), 826 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 827 828 // register keyboard shortcut to scroll tabs right 829 final ActionListener actionSelectRight = new ActionListener() { 830 @Override 831 public void actionPerformed(ActionEvent event) { 832 final JTabbedPane pane = (JTabbedPane)event.getSource(); 833 final int newIndex = (pane.getSelectedIndex() + 1) % pane.getTabCount(); 834 pane.setSelectedIndex(newIndex); 835 } 836 }; 837 838 tabbedPane.registerKeyboardAction(actionSelectRight, 839 KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.META_DOWN_MASK), 840 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 841 842 /* 843 TabbedPaneUI foo = _tabbedPane.getUI(); 844 _tabbedPane.setUI(new BasicTabbedPaneUI() { 845 @Override 846 protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount, int maxTabHeight) { 847 if (_tabbedPane.getTabCount() > 1) { 848 return super.calculateTabAreaHeight(tabPlacement, horizRunCount, maxTabHeight); 849 } else { 850 return 0; 851 } 852 } 853 @Override 854 protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects, int tabIndex, Rectangle iconRect, Rectangle textRect) { 855 if (_tabbedPane.getTabCount() > 1) { 856 super.paintTab(g, tabPlacement, rects, tabIndex, iconRect, textRect); 857 } 858 } 859 }); 860 861 */ 862 863 // add a MouseListener that handles double-clicks on the tabs. 864 // when a tab is double-clicked, open the sub-workflow in a separate 865 // frame and remove from the tabbed pane. 866 tabbedPane.addMouseListener(new MouseListener() { 867 868 @Override 869 public void mouseClicked(MouseEvent event) { 870 871 if(event.getClickCount() == 2) { 872 final Object source = event.getSource(); 873 if (source instanceof JTabbedPane) { 874 final Component selected = ((JTabbedPane) source).getSelectedComponent(); 875 if (selected instanceof JGraph) { 876 final JGraph jgraph = (JGraph) selected; 877 GraphModel graphModel = jgraph.getGraphPane().getGraphModel(); 878 if(graphModel instanceof ActorGraphModel) { 879 NamedObj model = ((ActorGraphModel)graphModel).getPtolemyModel(); 880 881 // make sure it's not the top level workflow 882 if(model.toplevel() != model) { 883 TabbedKeplerGraphFrame frame = 884 (TabbedKeplerGraphFrame) _modelManager.getFrame(model); 885 if(frame != null) { 886 frame.removeComposite(model); 887 } 888 Configuration configuration = (Configuration) Configuration 889 .configurations().get(0); 890 try { 891 configuration.openModel(model); 892 } catch (IllegalActionException | NameDuplicationException e) { 893 MessageHandler.error("Error opening sub-workflow in separate window.", e); 894 } 895 } 896 } 897 } 898 } 899 } 900 } 901 902 @Override 903 public void mousePressed(MouseEvent e) { 904 } 905 906 @Override 907 public void mouseReleased(MouseEvent e) { 908 } 909 910 @Override 911 public void mouseEntered(MouseEvent e) { 912 } 913 914 @Override 915 public void mouseExited(MouseEvent e) { 916 } 917 }); 918 919 return tabbedPane; 920 } 921 922 /** Dispose of a Component in a tabbed pane. If the Component is a JGraph, 923 * this method removes the references in the ObjectManager and ModelToFrameManager. 924 * If the Component is a JTabbedPane, this method recursively removes the 925 * Components in each tab, and then disposes of the JTabbedPane. 926 */ 927 private void _disposeComponent(Component component) { 928 929 final ObjectManager objectManager = ObjectManager.getInstance(); 930 931 if(component instanceof JGraph) { 932 final JGraph jgraph = (JGraph) component; 933 final NamedObj modelInTab = ((AbstractBasicGraphModel)jgraph.getGraphPane(). 934 getGraphModel()).getPtolemyModel(); 935 // make sure it's not the top-level workflow, which is 936 // removed by KeplerGraphFrame 937 if(modelInTab.getContainer() != null) { 938 objectManager.removeNamedObjs(modelInTab); 939 _modelManager.removeModel(modelInTab); 940 } 941 } else if(component instanceof JTabbedPane) { 942 943 JTabbedPane subTabbedPane = (JTabbedPane)component; 944 NamedObj container = null; 945 while(subTabbedPane.getTabCount() > 0) { 946 Component subComponent = subTabbedPane.getComponentAt(0); 947 if(subComponent instanceof JGraph) { 948 final NamedObj modelInTab = ((AbstractBasicGraphModel)((JGraph) subComponent). 949 getGraphPane().getGraphModel()).getPtolemyModel(); 950 container = modelInTab.getContainer(); 951 } 952 _disposeComponent(subComponent); 953 subTabbedPane.remove(0); 954 } 955 if(container != null) { 956 objectManager.removeNamedObjs(container); 957 _modelManager.removeModel(container); 958 } 959 } 960 } 961 962 /** Find the component for a NamedObj; returns null if not found. */ 963 private Component _findComponentForModel(NamedObj model) { 964 // look through all the tabs 965 if(_tabbedPane != null) { 966 for(Component component : _tabbedPane.getComponents()) { 967 if(component instanceof JGraph) { 968 final JGraph jgraph = (JGraph) component; 969 final NamedObj modelInTab = 970 ((AbstractBasicGraphModel)jgraph.getGraphPane().getGraphModel()).getPtolemyModel(); 971 // see if this tab's model matches 972 if(modelInTab == model) { 973 return component; 974 } 975 } else if(component instanceof JTabbedPane) { 976 // the component is a tabbed pane containing one or more composites, 977 // e.g., refinements in a Case or ExecutionChoice actor. 978 if(((JTabbedPane)component).getComponentCount() > 0) { 979 Component subComponent = ((JTabbedPane)component).getComponentAt(0); 980 if(subComponent != null && (subComponent instanceof JGraph)) { 981 final NamedObj subModelInTab = 982 ((AbstractBasicGraphModel)((JGraph) subComponent).getGraphPane().getGraphModel()).getPtolemyModel(); 983 // see if the contained of the submodel matches the model 984 if(subModelInTab.getContainer() == model) { 985 return component; 986 } 987 } 988 } 989 } 990 } 991 } 992 return null; 993 } 994 995 /** Set the size for a JGraph, from a SizeAttribute in the model. 996 */ 997 private void _setJGraphSize(JGraph jgraph, NamedObj model) { 998 999 if(jgraph != null) { 1000 try { 1001 // The SizeAttribute property is used to specify the size 1002 // of the JGraph component. Unfortunately, with Swing's 1003 // mysterious and undocumented handling of component sizes, 1004 // there appears to be no way to control the size of the 1005 // JGraph from the size of the Frame, which is specified 1006 // by the WindowPropertiesAttribute. 1007 SizeAttribute size = (SizeAttribute) model.getAttribute( 1008 "_vergilSize", SizeAttribute.class); 1009 if (size != null) { 1010 size.setSize(jgraph); 1011 } else { 1012 // Set the default size. 1013 // Note that the location is of the frame, while the size 1014 // is of the scrollpane. 1015 jgraph.setPreferredSize(new Dimension(600, 400)); 1016 } 1017 } catch (Exception ex) { 1018 // Ignore problems here. Errors simply result in a default 1019 // size and location. 1020 } 1021 } 1022 } 1023 1024 /** Update the size, zoom, and position of all the open graphs 1025 * for a tabbed pane. 1026 * @param tabbedPane The tabbed pane. 1027 */ 1028 private void _updateWindowAttributesForTabs(JTabbedPane tabbedPane) throws IllegalActionException, NameDuplicationException { 1029 for (Component component : tabbedPane.getComponents()) { 1030 if (component instanceof JGraph) { 1031 GraphPane pane = ((JGraph) component).getGraphPane(); 1032 _updateWindowAttributes(this, ((AbstractBasicGraphModel) pane.getGraphModel()).getPtolemyModel(), 1033 (JGraph) component); 1034 } else if (component instanceof JTabbedPane) { 1035 _updateWindowAttributesForTabs((JTabbedPane) component); 1036 } 1037 } 1038 } 1039 1040 /////////////////////////////////////////////////////////////////// 1041 //// private variables //// 1042 1043 /** The root panel. This contains either a single JGraph if no 1044 * composite actors have been opened, or _tabbedPane, which 1045 * contains a tab for each open composite actor. 1046 * 1047 * NOTE: do not assign it null here since the assignment happens 1048 * after the constructor which creates it in _createRightComponent(). 1049 */ 1050 private JPanel _panel; 1051 1052 /** The tabbed pane for composites. This is null if no 1053 * composite actors have been opened. 1054 */ 1055 private JTabbedPane _tabbedPane; 1056 1057 /** The composite actor for which this frame was opened. */ 1058 private CompositeEntity _rootComposite; 1059 1060 /** The tableau for which this frame was opened. */ 1061 private TabbedKeplerGraphTableau _rootTableau; 1062 1063 /** A regex for MoML change requests that rename actors. */ 1064 private static final Pattern _MOML_RENAME_PATTERN = 1065 Pattern.compile("entity name=\"(\\w+)\"><(?:rename|display) name=\"(\\w+)\""); 1066 1067 /** A regex for MoML change requests that delete actors. */ 1068 private static final Pattern _MOML_DELETE_PATTERN = Pattern.compile("deleteEntity name=\"(\\w+)\""); 1069 1070 /** Model to frame mapping. */ 1071 private ModelToFrameManager _modelManager; 1072 1073 /////////////////////////////////////////////////////////////////// 1074 //// public inner classes //// 1075 1076 /** Specialized graph controller that handles multiple graph models. */ 1077 public class TabbedKeplerController extends ActorEditorGraphController { 1078 /** Override the base class to select the graph model associated 1079 * with the selected pane. 1080 */ 1081 @Override 1082 public GraphModel getGraphModel() { 1083 1084 if(_panel != null) { 1085 if(_tabbedPane != null) { 1086 //System.out.println("selected tab: " + _tabbedPane.getSelectedIndex()); 1087 Component tab = _tabbedPane.getSelectedComponent(); 1088 if (tab instanceof JGraph) { 1089 GraphPane pane = ((JGraph) tab).getGraphPane(); 1090 return pane.getGraphModel(); 1091 } else if(tab instanceof JTabbedPane) { 1092 Component subTab = ((JTabbedPane)tab).getSelectedComponent(); 1093 if(subTab instanceof JGraph) { 1094 GraphPane pane = ((JGraph) subTab).getGraphPane(); 1095 return pane.getGraphModel(); 1096 } 1097 } 1098 } else if(_panel.getComponentCount() > 0) { 1099 JGraph jgraph = (JGraph) _panel.getComponent(0); 1100 return jgraph.getGraphPane().getGraphModel(); 1101 } 1102 } 1103 1104 // Fallback position. 1105 //System.out.println(System.nanoTime() + " MultiCompositeController.getGraphModel could not find JGraph"); 1106 return super.getGraphModel(); 1107 } 1108 1109 1110 /** Add hot keys to the actions in the given JGraph. 1111 * This method is overridden so that we can call it in 1112 * addComposite(). 1113 * @param jgraph The JGraph to which hot keys are to be added. 1114 */ 1115 @Override 1116 protected void _addHotKeys(JGraph jgraph) { 1117 super._addHotKeys(jgraph); 1118 } 1119 } 1120}