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}