001/*
002 * Copyright (c) 2009-2010 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;
031
032import java.awt.Adjustable;
033import java.awt.BorderLayout;
034import java.awt.Color;
035import java.awt.Dimension;
036import java.awt.KeyboardFocusManager;
037import java.awt.event.ActionEvent;
038import java.awt.event.ActionListener;
039import java.awt.event.AdjustmentEvent;
040import java.awt.event.AdjustmentListener;
041import java.awt.event.KeyEvent;
042import java.awt.geom.AffineTransform;
043import java.awt.geom.Point2D;
044import java.awt.geom.Rectangle2D;
045import java.io.File;
046import java.io.IOException;
047import java.net.URL;
048import java.util.Collections;
049import java.util.Iterator;
050import java.util.SortedSet;
051import java.util.TreeSet;
052import java.util.Vector;
053import java.util.regex.Matcher;
054
055import javax.swing.BoxLayout;
056import javax.swing.JButton;
057import javax.swing.JComponent;
058import javax.swing.JMenu;
059import javax.swing.JMenuBar;
060import javax.swing.JMenuItem;
061import javax.swing.JOptionPane;
062import javax.swing.JPanel;
063import javax.swing.JScrollBar;
064import javax.swing.JToolBar;
065import javax.swing.KeyStroke;
066
067import org.apache.commons.logging.Log;
068import org.apache.commons.logging.LogFactory;
069import org.kepler.gui.kar.ExportArchiveAction;
070import org.kepler.kar.KARFile;
071import org.kepler.kar.KARManager;
072import org.kepler.objectmanager.ObjectManager;
073import org.kepler.objectmanager.cache.LocalRepositoryManager;
074import org.kepler.objectmanager.library.LibraryManager;
075import org.kepler.util.FileUtil;
076import org.kepler.util.RenameUtil;
077import org.kepler.util.ShutdownNotifier;
078
079import diva.graph.GraphController;
080import diva.gui.toolbox.JCanvasPanner;
081import ptolemy.actor.gui.Configuration;
082import ptolemy.actor.gui.Effigy;
083import ptolemy.actor.gui.ModelDirectory;
084import ptolemy.actor.gui.PtolemyEffigy;
085import ptolemy.actor.gui.PtolemyPreferences;
086import ptolemy.actor.gui.SizeAttribute;
087import ptolemy.actor.gui.Tableau;
088import ptolemy.actor.gui.TableauFrame;
089import ptolemy.gui.MemoryCleaner;
090import ptolemy.kernel.ComponentEntity;
091import ptolemy.kernel.CompositeEntity;
092import ptolemy.kernel.util.IllegalActionException;
093import ptolemy.kernel.util.NameDuplicationException;
094import ptolemy.kernel.util.NamedObj;
095import ptolemy.moml.LibraryAttribute;
096import ptolemy.util.CancelException;
097import ptolemy.util.MessageHandler;
098import ptolemy.vergil.actor.ActorGraphFrame;
099import ptolemy.vergil.basic.BasicGraphFrame;
100import ptolemy.vergil.basic.BasicGraphFrameExtension;
101
102/** Kepler-specific overrides for the graph frame.
103 * 
104 */
105public class KeplerGraphFrame extends ActorGraphFrame {
106
107    /**
108     * Constructor.
109     * 
110     * @param entity
111     * @param tableau
112     */
113    public KeplerGraphFrame(CompositeEntity entity, Tableau tableau) {
114        this(entity, tableau, null);
115    }
116
117    /**
118     * Constructor
119     * 
120     * @param entity
121     * @param tableau
122     * @param defaultLibrary
123     */
124    public KeplerGraphFrame(CompositeEntity entity, Tableau tableau,
125            LibraryAttribute defaultLibrary) {
126        super(entity, tableau, defaultLibrary);
127        _initKeplerGraphFrame();
128
129        // get the model file from the effigy and update the recent menu history
130        // and set the last directory for the File->Open and Save dialogs.
131        // this is performed here for opening XML or KAR files from the command
132        // line. when a model is opened via File->Open, the recent menu and
133        // last directory are updated elsewhere.
134        Effigy effigy = getEffigy();
135        File modelFile = effigy.getWritableFile();
136        if(modelFile != null) {
137            try {
138                updateHistory(modelFile.getAbsolutePath());
139            } catch (IOException e) {
140                MessageHandler.error("Unable to update history menu.", e);
141            }
142            
143            setLastDirectory(modelFile.getParentFile());
144        }        
145    }
146
147    /**
148     * Override the dispose method to unattach any listeners that may keep this
149     * model from getting garbage collected. Also remove KARManager's mapping of
150     * this JFrame to KARfile if necessary.
151     */
152    @Override
153    public void dispose() {
154
155        if (_topPack instanceof KeplerMenuHandler) {
156            KeplerMenuHandler topPack = (KeplerMenuHandler) _topPack;
157            MenuMapper mm = topPack.menuMapper;
158            mm.clear();
159            /*
160             * mm.printDebugInfo(); Map<String,Action> m =
161             * mm.getPTIIMenuActionsMap(); m.clear(); KeplerMenuHandler kmh =
162             * (KeplerMenuHandler) _topPack; kmh.clear(); _topPack = null;
163             */
164        }
165
166        JMenuBar keplerMenuBar = getJMenuBar();
167        /* int removed = */MemoryCleaner.removeActionListeners(keplerMenuBar);
168        // System.out.println("KeplerGraphFrame menubar action listeners removed: "
169        // + removed);
170
171        CanvasDropTargetListener listener = CanvasDropTargetListener
172                .getInstance();
173        if (listener != null && _dropTarget != null) {
174            _dropTarget.deRegisterAdditionalListener(listener);
175        }
176
177        if (_horizontalScrollBarListener != null
178                && _horizontalScrollBar != null) {
179            _horizontalScrollBar
180                    .removeAdjustmentListener(_horizontalScrollBarListener);
181            _horizontalScrollBarListener = null;
182        }
183        if (_verticalScrollBarListener != null
184                && _verticalScrollBarListener != null) {
185            _verticalScrollBar
186                    .removeAdjustmentListener(_verticalScrollBarListener);
187            _verticalScrollBarListener = null;
188        }
189
190        // remove JFrame => KARFile mapping from KARManager
191        KARManager karManager = KARManager.getInstance();
192        karManager.remove(this);
193
194        TabManager tabManager = TabManager.getInstance();
195        tabManager.removeAllFrameTabs(this);
196
197        // this isn't safe. see:
198        // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5095#c14
199        // it is safe now after changes in r26444.
200        ObjectManager.getInstance().removeNamedObjs(this.getModel());
201        ViewManager.getInstance().removeOpenFrame(this);
202        LibraryManager.getInstance().removeAllFrameTabs(this);
203
204        // now call dispose on updaters in order
205        synchronized (_updaterSet) {
206            Iterator<KeplerGraphFrameUpdater> itr = _updaterSet.iterator();
207            while (itr.hasNext()) {
208                KeplerGraphFrameUpdater updater = itr.next();
209                updater.dispose(this);
210            }
211        }
212
213        ModelToFrameManager m2fm = ModelToFrameManager.getInstance();
214        m2fm.remove(this);
215
216        // see if this was the last window
217        if (m2fm.numberOfOpenFrames() == 0) {
218            ShutdownNotifier.shutdown();
219        }
220
221        KeyboardFocusManager focusManager = KeyboardFocusManager
222                .getCurrentKeyboardFocusManager();
223        focusManager.clearGlobalFocusOwner();
224        focusManager.downFocusCycle();
225
226        super.dispose();
227    }
228
229    /**
230     * Go to full screen.
231     */
232    @Override
233    public void fullScreen() {
234
235        // FIXME: do nothing since returning from full screen currently crashes
236        // kepler. this is due to cancelFullScreen() dereferencing _splitPane,
237        // which is never initialized.
238    }
239
240    // commented out since zoom() is commented out, and hides a super class _zoomFlag
241    //protected boolean _zoomFlag = false;
242
243    /** Get the graph controller. */
244    public GraphController getGraphController() {
245        return _controller;
246    }
247
248    public JToolBar getToolBar() {
249        return this._toolbar;
250    }
251    
252    /** Set the model for this frame. */
253    @Override
254    public void setModel(NamedObj model) {
255        super.setModel(model);
256
257        // FIXME: ModelToFrameManager is probably unnecessary, this
258        // information is in the Configuration.
259        ModelToFrameManager m2fm = ModelToFrameManager.getInstance();
260        m2fm.remove(this);
261        m2fm.add(getModel(), this);
262    }
263
264    /** Update the History menu. */
265    public void updateHistory(String absolutePath) throws IOException {
266        _updateHistory(absolutePath, false);
267    }
268
269    /**
270     * Create the menus that are used by this frame. It is essential that
271     * _createGraphPane() be called before this.
272     */
273    @Override
274    protected void _addMenus() {
275        super._addMenus();
276
277        // remove Open File... O accelerator.
278        // Open... (for KARs) uses this accelerator now.
279        for (int i = 0; i < _fileMenuItems.length; i++) {
280            JMenuItem menuItem = _fileMenuItems[i];
281            if (menuItem != null) {
282                String menuItemText = menuItem.getText();
283                if (menuItemText != null && menuItemText.equals("Open File")) {
284                    // int removed =
285                    // MemoryCleaner.removeActionListeners(menuItem);
286                    // System.out.println("KeplerGraphFrame _fileMenuItems["+i+"] action listeners removed: "
287                    // + removed);
288                    menuItem.setAccelerator(null);
289                }
290            }
291        }
292
293        // see if the effigy for the top level workflow is called "Unnamed"
294        // if so, renamed it to "Unnamed1"
295
296        NamedObj namedObj = getModel();
297        if (namedObj.getContainer() == null && namedObj.getName().length() == 0) {
298            Effigy effigy = getEffigy();
299            if (effigy != null) {
300                String name = effigy.identifier.getExpression();
301                if (name.equals("Unnamed")) {
302                    String newName = name + _nextUnnamed;
303                    _nextUnnamed++;
304                    try {
305                        // set the identifier, which is what shows up at the top
306                        // of the window
307                        effigy.identifier.setExpression(newName);
308                    } catch (Exception e) {
309                        report("Error setting effigy name.", e);
310                    }
311
312                    try {
313                        namedObj.setName(newName);
314                    } catch (Exception e) {
315                        report("Error setting workflow name to " + newName
316                                + ".", e);
317                    }
318
319                }
320            }
321        }
322
323        // let any KeplerGraphFrameUpdaters perform updates.
324        Components components = new Components();
325
326        // now call updateFrameComponents on updaters in order
327        synchronized (_updaterSet) {
328            Iterator<KeplerGraphFrameUpdater> itr = _updaterSet.iterator();
329            while (itr.hasNext()) {
330                KeplerGraphFrameUpdater updater = itr.next();
331                updater.updateFrameComponents(components);
332            }
333        }
334
335    }
336
337    /**
338     * Get the name of this object. If the parent class returns "Unnamed", use
339     * the effigy's identifier as the name.
340     * 
341     * @return The name.
342     */
343    @Override
344    protected String _getName() {
345        String retval = super._getName();
346        if (retval.equals("Unnamed")) {
347            Effigy effigy = getEffigy();
348            if (effigy != null) {
349                retval = effigy.identifier.getExpression();
350            }
351        }
352        return retval;
353    }
354
355    /**
356     * Get the component whose size is to be recorded in the model when it is
357     * saved into an output file. In this class, the return of this function is
358     * the same as that of {@link #_getRightComponent()}. A subclass may
359     * override this function to return a different component, such as a tab of
360     * a tabbed pane, whose size is to be recorded instead.
361     * 
362     * @return The component whose size is to be recorded.
363     */
364    /* TODO this is not used, but should be moved to and called from BasicGraphFrame.
365    protected JComponent _getSizeComponent() {
366        return _getRightComponent();
367    }
368    */
369
370    /**
371     * Override BasicGraphFrame._initBasicGraphFrame()
372     */
373    @Override
374    protected void _initBasicGraphFrame() {
375
376        /**
377         * @todo - FIXME - Need to move this further up the hierarchy, so other
378         *       types of frames use it too. Don't put it in a launcher class
379         *       like KeplerApplication, because it then gets overridden later,
380         *       elsewhere in PTII
381         */
382        StaticGUIResources.setLookAndFeel();
383
384        _initBasicGraphFrameInitialization();
385
386        _dropTarget = BasicGraphFrameExtension.getDropTarget(_jgraph);
387
388        // add a CanvasDropTargetListener so that other classes can get
389        CanvasDropTargetListener listener = CanvasDropTargetListener
390                .getInstance();
391        _dropTarget.registerAdditionalListener(listener);
392
393        ActionListener deletionListener = new DeletionListener();
394
395        _rightComponent.registerKeyboardAction(deletionListener, "Delete",
396                KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),
397                JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
398        _rightComponent.registerKeyboardAction(deletionListener, "BackSpace",
399                KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0),
400                JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
401
402        _initBasicGraphFrameRightComponent();
403
404        _jgraph.setRequestFocusEnabled(true);
405
406        // Background color is parameterizable by preferences.
407        Configuration configuration = getConfiguration();
408
409        if (configuration != null) {
410            try {
411                // Set the PtolemyPreference to the desired background.
412                // See
413                // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=2321#c14
414                PtolemyPreferences preferences = PtolemyPreferences
415                        .getPtolemyPreferencesWithinConfiguration(configuration);
416                if (_isDebugging) {
417                    _log.debug("bg: " + BACKGROUND_COLOR);
418                }
419                if (preferences != null) {
420                    float[] components = new float[4];
421                    // Make sure we get only 4 elements in case the color space
422                    // is bigger than 4
423                    components = BACKGROUND_COLOR.getComponents(components);
424                    preferences.backgroundColor.setExpression("{"
425                            + components[0] + "," + components[1] + ","
426                            + components[2] + "," + components[3] + "}");
427                    _rightComponent.setBackground(preferences.backgroundColor
428                            .asColor());
429                    if (_isDebugging) {
430                        _log.debug("desired background: " + BACKGROUND_COLOR
431                                + " actual background:  "
432                                + preferences.backgroundColor.asColor());
433                    }
434                }
435            } catch (IllegalActionException e1) {
436                // Ignore the exception and use the default color.
437            }
438        }
439
440        _initBasicGraphFrameRightComponentMouseListeners();
441
442        try {
443            // The SizeAttribute property is used to specify the size
444            // of the JGraph component. Unfortunately, with Swing's
445            // mysterious and undocumented handling of component sizes,
446            // there appears to be no way to control the size of the
447            // JGraph from the size of the Frame, which is specified
448            // by the WindowPropertiesAttribute.
449            SizeAttribute size = (SizeAttribute) getModel().getAttribute(
450                    "_vergilSize", SizeAttribute.class);
451            if (size != null) {
452                size.setSize(_jgraph);
453            } else {
454                // Set the default size.
455                // Note that the location is of the frame, while the size
456                // is of the scrollpane.
457                _jgraph.setPreferredSize(new Dimension(600, 400));
458            }
459
460            _initBasicGraphFrameSetZoomAndPan();
461        } catch (Exception ex) {
462            // Ignore problems here. Errors simply result in a default
463            // size and location.
464        }
465
466        // Create the panner.
467        _graphPanner = new JCanvasPanner(_jgraph);
468
469        _horizontalScrollBar = new JScrollBar(Adjustable.HORIZONTAL);
470        _verticalScrollBar = new JScrollBar(Adjustable.VERTICAL);
471
472        // see if we want scrollbars on the canvas or not
473        // the answer defaults to 'no'
474        CanvasNavigationModifierFactory CNMfactory = (CanvasNavigationModifierFactory) getConfiguration()
475                .getAttribute("canvasNavigationModifier");
476        if (CNMfactory != null) { // get the scrollbar flag from the factory if
477            // it exists in the config
478            ScrollBarModifier modifier = CNMfactory.createScrollBarModifier();
479            _scrollBarFlag = modifier.getScrollBarModifier();
480        }
481
482        _canvasPanel = new JPanel();
483
484        _canvasPanel.setBorder(null);
485        _canvasPanel.setLayout(new BorderLayout());
486
487        if (_scrollBarFlag) {
488            _canvasPanel.add(_horizontalScrollBar, BorderLayout.SOUTH);
489            _canvasPanel.add(_verticalScrollBar, BorderLayout.EAST);
490            _horizontalScrollBar.setModel(_jgraph.getGraphPane().getCanvas()
491                    .getHorizontalRangeModel());
492            _verticalScrollBar.setModel(_jgraph.getGraphPane().getCanvas()
493                    .getVerticalRangeModel());
494            _horizontalScrollBarListener = new ScrollBarListener(
495                    _horizontalScrollBar);
496            _verticalScrollBarListener = new ScrollBarListener(
497                    _verticalScrollBar);
498            _horizontalScrollBar
499                    .addAdjustmentListener(_horizontalScrollBarListener);
500            _verticalScrollBar
501                    .addAdjustmentListener(_verticalScrollBarListener);
502        }
503
504        // NOTE: add _rightComponent instead of _jgraph since _rightComponent
505        // may be sub-divided into tabbed panes.
506        // see http://bugzilla.ecoinformatics.org/show_bug.cgi?id=3708
507        _canvasPanel.add(_rightComponent, BorderLayout.CENTER);
508
509        TabManager tabman = TabManager.getInstance();
510        tabman.initializeTabs(this);
511
512        ViewManager viewman = ViewManager.getInstance();
513        viewman.initializeViews(this);
514        try {
515            viewman.addCanvasToLocation(_canvasPanel, this);
516        } catch (Exception e) {
517            throw new RuntimeException("Could not add canvas panel: "
518                    + e.getMessage());
519        }
520
521        // _jgraph.setMinimumSize(new Dimension(0, 0));
522
523        getContentPane().add(viewman.getViewArea(this), BorderLayout.CENTER);
524
525        // The toolbar panel is the container that contains the main toolbar and
526        // any additional toolbars
527        JPanel toolbarPanel = new JPanel();
528        toolbarPanel.setLayout(new BoxLayout(toolbarPanel, BoxLayout.Y_AXIS)); // They
529        // stack
530        _toolbar = new JToolBar(); // The main Kepler toolbar
531        toolbarPanel.add(_toolbar);
532        getContentPane().add(toolbarPanel, BorderLayout.NORTH); // Place the
533        // toolbar panel
534        _initBasicGraphFrameToolBarZoomButtons();
535
536        _initBasicGraphFrameActions();
537
538        // Add a weak reference to this to keep track of all
539        // the graph frames that have been created.
540        _openGraphFrames.add(this);
541
542        System.gc();
543    }
544
545    /**
546     * KeplerGraphFrame Initializer method
547     */
548    protected void _initKeplerGraphFrame() {
549        if (_isDebugging) {
550            _log.debug("_initKeplerGraphFrame()");
551        }
552        ModelToFrameManager m2fm = ModelToFrameManager.getInstance();
553        m2fm.add(getModel(), this);
554    }
555
556    /**
557     * Open a dialog to prompt the user to save a KAR. Return false if the user
558     * clicks "cancel", and otherwise return true.
559     * 
560     * Overrides Top._queryForSave()
561     * 
562     * @return _SAVED if the file is saved, _DISCARDED if the modifications are
563     *         discarded, _CANCELED if the operation is canceled by the user,
564     *         and _FAILED if the user selects save and the save fails.
565     */
566    @Override
567    protected int _queryForSave() {
568        Object[] options = { "Save", "Discard changes", "Cancel" };
569
570        // making more generic since other items to go in the KAR
571        // may be the reason for querying to save
572        // String query = "Save changes to " + StringUtilities.split(_getName())
573        // + "?";
574        String query = "Save changes to KAR?";
575
576        // Show the MODAL dialog
577        int selected = JOptionPane.showOptionDialog(this, query,
578                "Save Changes?", JOptionPane.YES_NO_CANCEL_OPTION,
579                JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
580
581        if (selected == JOptionPane.YES_OPTION) {
582
583            JButton saveButton = new JButton("Save Kar");
584            ExportArchiveAction eaa = new ExportArchiveAction(this);
585            eaa.setRefreshFrameAfterSave(false);
586            // if the file already exists, call ExportArchiveAction.setSave()
587            // so that the save as file dialog does is not used.
588            KARFile karFile = KARManager.getInstance().get(this);
589            if (karFile != null) {
590                File file = karFile.getFileLocation();
591                if (file.canWrite()) {
592                    eaa.setSaveFile(file);
593                }
594            }
595            saveButton.addActionListener(eaa);
596            saveButton.doClick();
597
598            if (eaa.saveSucceeded()) {
599                setModified(false);
600                return _SAVED;
601            } else {
602                return _FAILED;
603            }
604
605        }
606
607        if (selected == JOptionPane.NO_OPTION) {
608            return _DISCARDED;
609        }
610
611        return _CANCELED;
612    }
613
614    /**
615     * Query the user for a filename, save the model to that file, and open a
616     * new window to view the model. This overrides the base class to close the
617     * old effigy if its name matches "UnnamedN". It also assigns a new LSID to
618     * the new workflow unless it is unnamed.
619     * 
620     * @param extension
621     *            If non-null, then the extension that is appended to the file
622     *            name if there is no extension.
623     * @return True if the save succeeds.
624     */
625    @Override
626    protected boolean _saveAs(String extension) {
627        boolean isUnnamed = false;
628
629        // If the tableau was unnamed before, then we need
630        // to close this window after doing the save.
631        Effigy effigy = getEffigy();
632        if (effigy != null) {
633            String id = effigy.identifier.getExpression();
634
635            // see if effigy id matches unnamed regex.
636            Matcher matcher = RenameUtil.unnamedIdPattern.matcher(id);
637            if (matcher.matches()) {
638                isUnnamed = true;
639            }
640        }
641
642        NamedObj model = getModel();
643
644        // When we do a File -> Save As, the new and original
645        // workflows need to have different LSIDs. This is taken
646        // care of by RenameUtil.renameComponentEntity below.
647
648        // NOTE: get the model directory before calling _saveAsHelper
649        // since _saveAsHelper may dispose the effigy and thereby close
650        // all associated tableaux
651        ModelDirectory directory = (ModelDirectory) getConfiguration()
652                .getEntity(Configuration._DIRECTORY_NAME);
653
654        // print a warning the first time this is called
655        if (!_printedWarningFirstTime) {
656            try {
657                MessageHandler
658                        .warning("Exporting the workflow to XML does not save any associated workflow "
659                                + "artifacts such as the report layout or module dependencies.");
660            } catch (CancelException e) {
661                // user clicked cancel button, so do not save.
662                return false;
663            }
664            _printedWarningFirstTime = true;
665        }
666
667        // perform the save
668        URL newModelURL = super._saveAsHelper(extension);
669
670        // see if we successfully saved
671        if (newModelURL != null) {
672
673            // get the new model
674            File newModelFile = new File(newModelURL.getFile());
675            String newName = FileUtil.getFileNameWithoutExtension(newModelFile
676                    .getName());
677            PtolemyEffigy newEffigy = (PtolemyEffigy) directory
678                    .getEffigy(newModelURL.toString());
679            NamedObj newModel = newEffigy.getModel();
680
681            // set newModel name back to the original name so we can
682            // send it through renameComponentEntity
683            try {
684                newModel.setName(model.getName());
685            } catch (IllegalActionException e1) {
686                // TODO Auto-generated catch block
687                e1.printStackTrace();
688            } catch (NameDuplicationException e1) {
689                // TODO Auto-generated catch block
690                e1.printStackTrace();
691            }
692
693            if (isUnnamed) {
694                try {
695                    // This will have the effect of closing all the
696                    // tableaux associated with the unnamed model.
697                    effigy.setContainer(null);
698
699                    // remove the workflow from the object manager since
700                    // its window went away.
701                    ObjectManager objectManager = ObjectManager.getInstance();
702                    objectManager.removeNamedObj(model);
703                } catch (Exception e) {
704                    report("Error closing effigy.", e);
705                    return false;
706                }
707            }
708
709            try {
710                RenameUtil.renameComponentEntity((ComponentEntity) newModel,
711                        newName);
712            } catch (Exception e) {
713                // TODO Auto-generated catch block
714                e.printStackTrace();
715            }
716
717            // After the MoML has been saved to disk we add it to the library
718            // and refresh the JTrees if it is saved in a local repository update the library
719            LocalRepositoryManager lrm = LocalRepositoryManager.getInstance();
720            if (lrm.isInLocalRepository(newModelFile)) {
721
722                //_savekar.saveToCache();
723
724                LibraryManager lm = LibraryManager.getInstance();
725                try {
726                    lm.addXML(newModelFile);
727                } catch (Exception e2) {
728                    MessageHandler.error("Error adding workflow to library.",
729                            e2);
730                }
731                try {
732                    lm.refreshJTrees();
733                } catch (IllegalActionException e2) {
734                    MessageHandler.error("Error updating tree.", e2);
735                }
736            }
737
738        }
739
740        return true;
741    }
742
743    /**
744     * Add the name of the last file open or set the name to the first position
745     * if already in the list
746     * 
747     * @param file
748     *            name of the file to add
749     * @param delete
750     *            If true, remove from the history list, otherwise the file is
751     *            added to the beginning.
752     */
753    @Override
754    protected void _updateHistory(String file, boolean delete)
755            throws IOException {
756        super._updateHistory(file, delete);
757        // reload the menus in all open windows.
758        MenuMapper.reloadAllMenubars();
759    }
760
761    /** A class to give KeplerGraphFrameUpdaters access to GUI components. */
762    public class Components {
763        /** Construct a new Component. Only KeplerGraphFrame may do this. */
764        private Components() {
765        }
766
767        /** Get the KeplerGraphFrame. */
768        public KeplerGraphFrame getFrame() {
769            return KeplerGraphFrame.this;
770        }
771
772        /** Get the menu. */
773        public JMenu getMenu() {
774            return _graphMenu;
775        }
776
777        /** Get the toolbar. */
778        public JToolBar getToolBar() {
779            return _toolbar;
780        }
781    }
782
783    /**
784     * Listener for scrollbar events.
785     */
786    public class ScrollBarListener implements AdjustmentListener {
787        /**
788         * constructor
789         * 
790         * @param sb
791         *            JScrollBar
792         */
793        public ScrollBarListener(JScrollBar sb) {
794            _orientation = sb.getOrientation();
795        }
796
797        /**
798         * translate the model when the scrollbars move
799         * 
800         * @param e
801         *            AdjustmentEvent
802         */
803        @Override
804        public void adjustmentValueChanged(AdjustmentEvent e) {
805
806            // do not scroll the canvas if the canvas is being zoomed
807            // or we are already scrolling it.
808            if(!_zoomFlag && !_amUpdating) {
809
810                int val = e.getValue();
811
812                Rectangle2D visibleRect = getVisibleCanvasRectangle();
813                AffineTransform newTransform = getJGraph().getCanvasPane().getTransformContext()
814                        .getTransform();
815
816                if(_orientation == Adjustable.HORIZONTAL) {
817                    Point2D newLeft = new Point2D.Double(val, 0);
818                    newTransform.translate(visibleRect.getX() - newLeft.getX(), 0);
819                } else {
820                    Point2D newTop = new Point2D.Double(0, val);
821                    newTransform.translate(0, visibleRect.getY() - newTop.getY());
822                }
823                
824                // set the _amUpdating flag before scrolling the canvas to prevent
825                // recursive calls to this method.
826                _amUpdating = true;
827                try {
828                    getJGraph().getCanvasPane()
829                        .setTransform(newTransform);
830                } finally {
831                    _amUpdating = false;
832                }
833
834                if (_graphPanner != null) {
835                    _graphPanner.repaint();
836                }
837            }
838        }
839
840        private int _orientation;
841        private boolean _amUpdating = false;
842    }
843
844    /** An ActionListener for handling deletion events. */
845    private class DeletionListener implements ActionListener {
846        /**
847         * Delete any nodes or edges from the graph that are currently selected.
848         * In addition, delete any edges that are connected to any deleted
849         * nodes.
850         */
851        @Override
852        public void actionPerformed(ActionEvent e) {
853            delete();
854        }
855    }
856
857    /** Override the BasicGraphFrame Background color **/
858    public static final Color BACKGROUND_COLOR = new Color(255, 255, 255);
859
860    private static final Log _log = LogFactory.getLog(KeplerGraphFrame.class
861            .getName());
862
863    private static final boolean _isDebugging = _log.isDebugEnabled();
864
865    /** The number of the next "Unnamed" effigy. */
866    private static int _nextUnnamed = 1;
867
868    /** If true, we've printed the warning about exporting to MoML. */
869    private static Boolean _printedWarningFirstTime = false;
870
871    /** A synchronizedSortedSet TreeSet of controller updaters. */
872    private static SortedSet<KeplerGraphFrameUpdater> _updaterSet = Collections
873            .synchronizedSortedSet(new TreeSet<KeplerGraphFrameUpdater>());
874
875    /**
876     * Add a KeplerGraphFrameUpdater to the set of updaters that can make
877     * changes to components, e.g., the toolbar or menus. The ordering of
878     * updates can be specified by implementing the Comparable interface.
879     */
880    public static void addUpdater(KeplerGraphFrameUpdater updater) {
881        boolean addUpdater = _updaterSet.add(updater);
882        if (!addUpdater) {
883            // if this happens even when the set doesn't contain
884            // your updater, check your updater's compareTo value
885            _log.warn("KeplerGraphFrame addUpdater(" + updater.getClass()
886                    + ") returned false.");
887        }
888    }
889
890    /** Clear the set of KeplerGraphFrameUpdaters. */
891    public static void clearUpdaters() {
892        _updaterSet.clear();
893    }
894
895    public static Vector<TableauFrame> getOpenFrames() {
896        Vector<TableauFrame> frames = new Vector<TableauFrame>(
897                _openGraphFrames.size());
898        for (BasicGraphFrame bgf : _openGraphFrames) {
899            frames.add(bgf);
900        }
901        return frames;
902    }
903
904    /** Remove an KeplerGraphFrameUpdater from the set of updaters. */
905    public static void removeUpdater(KeplerGraphFrameUpdater updater) {
906        // _updaterSet.remove doesn't move the
907        // org.kepler.reporting.gui.ReportViewerPanel object correctly
908        // when the first window is closed. Switching to use iterator remove.
909        // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5095#c19
910        // _updaterSet.remove(updater);
911        synchronized (_updaterSet) {
912            Iterator<KeplerGraphFrameUpdater> itr = _updaterSet.iterator();
913            while (itr.hasNext()) {
914                KeplerGraphFrameUpdater _updater = itr.next();
915                if (_updater == updater) {
916                    itr.remove();
917                }
918            }
919        }
920    }
921
922    // Listeners
923
924    /** A panel for the canvas. */
925    protected JPanel _canvasPanel;
926
927    /** Horizontal scrollbar. */
928    protected JScrollBar _horizontalScrollBar;
929
930    /** Listener for scrolling on the horizontal scroll bar. */
931    private ScrollBarListener _horizontalScrollBarListener;
932
933    /** Flag to determine whether to render scrollbars on the canvas. */
934    private boolean _scrollBarFlag = false;
935
936    /** Vertical scrollbar. */
937    protected JScrollBar _verticalScrollBar;
938
939    /** Listener for scrolling on the vertical scroll bar. */
940    private ScrollBarListener _verticalScrollBarListener;
941
942
943    //  private class ExecutionMoMLChangeRequest extends OffsetMoMLChangeRequest {
944    // // FIXME: This was only called from within createHierarchy()?  It may have been called within copy() as well.
945    //          private String _name;
946
947    //          public ExecutionMoMLChangeRequest(Object o, NamedObj n, String s) {
948    //                  super(o, n, s);
949    //                  _name = new String();
950    //          }
951
952    //          public void setName(String name) {
953    //                  _name = name;
954    //          }
955
956    //          protected void _execute() throws Exception {
957    //                  super._execute();
958    //                  CompositeEntity container = (CompositeEntity) getContext();
959    //                  NamedObj newObject = container.getEntity(_name);
960
961    //                  // _setLocation(compositeActor, point);
962
963    //                  // Kepler wants a different icon.
964    //                  // FIXME: It seems a little strange to do this inside
965    //                  // a change request, but the change request inner class
966    //                  // was already here, and perhaps this helps undo work
967    //                  // better or something?
968    //                  IconLoader _iconLoader = MoMLParser.getIconLoader();
969    //                  if (_iconLoader != null) {
970    //                          _iconLoader.loadIconForClass(
971    //                                          "ptolemy.actor.TypedCompositeActor", newObject);
972    //                  }
973    //          }
974    //  }
975}