1 /*
   2  * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  *
  23  */
  24 package com.sun.hotspot.igv.view;
  25 
  26 import com.sun.hotspot.igv.data.ChangedEvent;
  27 import com.sun.hotspot.igv.data.ChangedListener;
  28 import com.sun.hotspot.igv.data.InputNode;
  29 import com.sun.hotspot.igv.data.Properties;
  30 import com.sun.hotspot.igv.data.Properties.PropertyMatcher;
  31 import com.sun.hotspot.igv.data.services.InputGraphProvider;
  32 import com.sun.hotspot.igv.filter.FilterChain;
  33 import com.sun.hotspot.igv.filter.FilterChainProvider;
  34 import com.sun.hotspot.igv.graph.Diagram;
  35 import com.sun.hotspot.igv.graph.Figure;
  36 import com.sun.hotspot.igv.graph.services.DiagramProvider;
  37 import com.sun.hotspot.igv.svg.BatikSVG;
  38 import com.sun.hotspot.igv.util.LookupHistory;
  39 import com.sun.hotspot.igv.util.RangeSlider;
  40 import com.sun.hotspot.igv.view.actions.*;
  41 import java.awt.*;
  42 import java.awt.event.HierarchyBoundsListener;
  43 import java.awt.event.HierarchyEvent;
  44 import java.awt.event.KeyEvent;
  45 import java.awt.event.KeyListener;
  46 import java.beans.PropertyChangeEvent;
  47 import java.beans.PropertyChangeListener;
  48 import java.io.*;
  49 import java.util.List;
  50 import java.util.*;
  51 import javax.swing.*;
  52 import javax.swing.border.Border;
  53 import org.openide.DialogDisplayer;
  54 import org.openide.NotifyDescriptor;
  55 import org.openide.actions.RedoAction;
  56 import org.openide.actions.UndoAction;
  57 import org.openide.awt.Toolbar;
  58 import org.openide.awt.ToolbarPool;
  59 import org.openide.awt.UndoRedo;
  60 import org.openide.util.Lookup;
  61 import org.openide.util.NbBundle;
  62 import org.openide.util.Utilities;
  63 import org.openide.util.actions.Presenter;
  64 import org.openide.util.lookup.AbstractLookup;
  65 import org.openide.util.lookup.InstanceContent;
  66 import org.openide.util.lookup.ProxyLookup;
  67 import org.openide.windows.Mode;
  68 import org.openide.windows.TopComponent;
  69 import org.openide.windows.WindowManager;
  70 
  71 /**
  72  *
  73  * @author Thomas Wuerthinger
  74  */
  75 public final class EditorTopComponent extends TopComponent implements PropertyChangeListener {
  76 
  77     private DiagramViewer scene;
  78     private InstanceContent content;
  79     private InstanceContent graphContent;
  80     private EnableBlockLayoutAction blockLayoutAction;
  81     private OverviewAction overviewAction;
  82     private HideDuplicatesAction hideDuplicatesAction;
  83     private PredSuccAction predSuccAction;
  84     private SelectionModeAction selectionModeAction;
  85     private PanModeAction panModeAction;
  86     private boolean notFirstTime;
  87     private JComponent satelliteComponent;
  88     private JPanel centerPanel;
  89     private CardLayout cardLayout;
  90     private RangeSlider rangeSlider;
  91     private JToggleButton overviewButton;
  92     private JToggleButton hideDuplicatesButton;
  93     private static final String PREFERRED_ID = "EditorTopComponent";
  94     private static final String SATELLITE_STRING = "satellite";
  95     private static final String SCENE_STRING = "scene";
  96     private DiagramViewModel rangeSliderModel;
  97     private ExportCookie exportCookie = new ExportCookie() {
  98 
  99         @Override
 100         public void export(File f) {
 101 
 102             Graphics2D svgGenerator = BatikSVG.createGraphicsObject();
 103 
 104             if (svgGenerator == null) {
 105                 NotifyDescriptor message = new NotifyDescriptor.Message("For export to SVG files the Batik SVG Toolkit must be intalled.", NotifyDescriptor.ERROR_MESSAGE);
 106                 DialogDisplayer.getDefault().notifyLater(message);
 107             } else {
 108                 scene.paint(svgGenerator);
 109                 FileOutputStream os = null;
 110                 try {
 111                     os = new FileOutputStream(f);
 112                     Writer out = new OutputStreamWriter(os, "UTF-8");
 113                     BatikSVG.printToStream(svgGenerator, out, true);
 114                 } catch (FileNotFoundException e) {
 115                     NotifyDescriptor message = new NotifyDescriptor.Message("For export to SVG files the Batik SVG Toolkit must be intalled.", NotifyDescriptor.ERROR_MESSAGE);
 116                     DialogDisplayer.getDefault().notifyLater(message);
 117 
 118                 } catch (UnsupportedEncodingException e) {
 119                 } finally {
 120                     if (os != null) {
 121                         try {
 122                             os.close();
 123                         } catch (IOException e) {
 124                         }
 125                     }
 126                 }
 127 
 128             }
 129         }
 130     };
 131 
 132     private DiagramProvider diagramProvider = new DiagramProvider() {
 133 
 134         @Override
 135         public Diagram getDiagram() {
 136             return getModel().getDiagramToView();
 137         }
 138 
 139         @Override
 140         public ChangedEvent<DiagramProvider> getChangedEvent() {
 141             return diagramChangedEvent;
 142         }
 143     };
 144 
 145     private ChangedEvent<DiagramProvider> diagramChangedEvent = new ChangedEvent<>(diagramProvider);
 146 
 147 
 148     private void updateDisplayName() {
 149         setDisplayName(getDiagram().getName());
 150         setToolTipText(getDiagram().getGraph().getGroup().getName());
 151     }
 152 
 153     public EditorTopComponent(Diagram diagram) {
 154 
 155         LookupHistory.init(InputGraphProvider.class);
 156         LookupHistory.init(DiagramProvider.class);
 157         this.setFocusable(true);
 158         FilterChain filterChain = null;
 159         FilterChain sequence = null;
 160         FilterChainProvider provider = Lookup.getDefault().lookup(FilterChainProvider.class);
 161         if (provider == null) {
 162             filterChain = new FilterChain();
 163             sequence = new FilterChain();
 164         } else {
 165             filterChain = provider.getFilterChain();
 166             sequence = provider.getSequence();
 167         }
 168 
 169         setName(NbBundle.getMessage(EditorTopComponent.class, "CTL_EditorTopComponent"));
 170         setToolTipText(NbBundle.getMessage(EditorTopComponent.class, "HINT_EditorTopComponent"));
 171 
 172         Action[] actions = new Action[]{
 173             PrevDiagramAction.get(PrevDiagramAction.class),
 174             NextDiagramAction.get(NextDiagramAction.class),
 175             null,
 176             ExtractAction.get(ExtractAction.class),
 177             ShowAllAction.get(HideAction.class),
 178             ShowAllAction.get(ShowAllAction.class),
 179             null,
 180             ZoomInAction.get(ZoomInAction.class),
 181             ZoomOutAction.get(ZoomOutAction.class),
 182         };
 183 
 184 
 185         Action[] actionsWithSelection = new Action[]{
 186             ExtractAction.get(ExtractAction.class),
 187             ShowAllAction.get(HideAction.class),
 188             null,
 189             ExpandPredecessorsAction.get(ExpandPredecessorsAction.class),
 190             ExpandSuccessorsAction.get(ExpandSuccessorsAction.class)
 191         };
 192 
 193         initComponents();
 194 
 195         ToolbarPool.getDefault().setPreferredIconSize(16);
 196         Toolbar toolBar = new Toolbar();
 197         Border b = (Border) UIManager.get("Nb.Editor.Toolbar.border"); //NOI18N
 198         toolBar.setBorder(b);
 199         JPanel container = new JPanel();
 200         this.add(container, BorderLayout.NORTH);
 201         container.setLayout(new BorderLayout());
 202         container.add(BorderLayout.NORTH, toolBar);
 203 
 204         rangeSliderModel = new DiagramViewModel(diagram.getGraph().getGroup(), filterChain, sequence);
 205         rangeSlider = new RangeSlider();
 206         rangeSlider.setModel(rangeSliderModel);
 207         JScrollPane pane = new JScrollPane(rangeSlider, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
 208         container.add(BorderLayout.CENTER, pane);
 209 
 210         scene = new DiagramScene(actions, actionsWithSelection, rangeSliderModel);
 211         content = new InstanceContent();
 212         graphContent = new InstanceContent();
 213         this.associateLookup(new ProxyLookup(new Lookup[]{scene.getLookup(), new AbstractLookup(graphContent), new AbstractLookup(content)}));
 214         content.add(exportCookie);
 215         content.add(rangeSliderModel);
 216         content.add(diagramProvider);
 217 
 218         rangeSliderModel.getDiagramChangedEvent().addListener(diagramChangedListener);
 219         rangeSliderModel.selectGraph(diagram.getGraph());
 220         rangeSliderModel.getViewPropertiesChangedEvent().addListener(new ChangedListener<DiagramViewModel>() {
 221                 @Override
 222                 public void changed(DiagramViewModel source) {
 223                     hideDuplicatesButton.setSelected(getModel().getHideDuplicates());
 224                     hideDuplicatesAction.setState(getModel().getHideDuplicates());
 225                 }
 226             });
 227 
 228 
 229         toolBar.add(NextDiagramAction.get(NextDiagramAction.class));
 230         toolBar.add(PrevDiagramAction.get(PrevDiagramAction.class));
 231         toolBar.addSeparator();
 232         toolBar.add(ExtractAction.get(ExtractAction.class));
 233         toolBar.add(ShowAllAction.get(HideAction.class));
 234         toolBar.add(ShowAllAction.get(ShowAllAction.class));
 235         toolBar.addSeparator();
 236         toolBar.add(ShowAllAction.get(ZoomInAction.class));
 237         toolBar.add(ShowAllAction.get(ZoomOutAction.class));
 238 
 239         blockLayoutAction = new EnableBlockLayoutAction();
 240         JToggleButton button = new JToggleButton(blockLayoutAction);
 241         button.setSelected(false);
 242         toolBar.add(button);
 243         blockLayoutAction.addPropertyChangeListener(this);
 244 
 245         overviewAction = new OverviewAction();
 246         overviewButton = new JToggleButton(overviewAction);
 247         overviewButton.setSelected(false);
 248         toolBar.add(overviewButton);
 249         overviewAction.addPropertyChangeListener(this);
 250 
 251         predSuccAction = new PredSuccAction();
 252         button = new JToggleButton(predSuccAction);
 253         button.setSelected(true);
 254         toolBar.add(button);
 255         predSuccAction.addPropertyChangeListener(this);
 256 
 257         hideDuplicatesAction = new HideDuplicatesAction();
 258         hideDuplicatesButton = new JToggleButton(hideDuplicatesAction);
 259         hideDuplicatesButton.setSelected(false);
 260         toolBar.add(hideDuplicatesButton);
 261         hideDuplicatesAction.addPropertyChangeListener(this);
 262 
 263         toolBar.addSeparator();
 264         toolBar.add(UndoAction.get(UndoAction.class));
 265         toolBar.add(RedoAction.get(RedoAction.class));
 266 
 267         toolBar.addSeparator();
 268         ButtonGroup interactionButtons = new ButtonGroup();
 269 
 270         panModeAction = new PanModeAction();
 271         panModeAction.setSelected(true);
 272         button = new JToggleButton(panModeAction);
 273         button.setSelected(true);
 274         interactionButtons.add(button);
 275         toolBar.add(button);
 276         panModeAction.addPropertyChangeListener(this);
 277 
 278         selectionModeAction = new SelectionModeAction();
 279         button = new JToggleButton(selectionModeAction);
 280         interactionButtons.add(button);
 281         toolBar.add(button);
 282         selectionModeAction.addPropertyChangeListener(this);
 283 
 284         toolBar.add(Box.createHorizontalGlue());
 285         Action action = Utilities.actionsForPath("QuickSearchShadow").get(0);
 286         Component quicksearch = ((Presenter.Toolbar) action).getToolbarPresenter();
 287         try {
 288             // (aw) workaround for disappearing search bar due to reparenting one shared component instance.
 289             quicksearch = (Component) quicksearch.getClass().getConstructor(KeyStroke.class).newInstance(new Object[]{null});
 290         } catch (ReflectiveOperationException | IllegalArgumentException | SecurityException e) {
 291         }
 292         Dimension preferredSize = quicksearch.getPreferredSize();
 293         preferredSize = new Dimension((int) preferredSize.getWidth() * 2, (int) preferredSize.getHeight());
 294         quicksearch.setMinimumSize(preferredSize); // necessary for GTK LAF
 295         quicksearch.setPreferredSize(preferredSize);
 296         toolBar.add(quicksearch);
 297 
 298         centerPanel = new JPanel();
 299         this.add(centerPanel, BorderLayout.CENTER);
 300         cardLayout = new CardLayout();
 301         centerPanel.setLayout(cardLayout);
 302         centerPanel.add(SCENE_STRING, scene.getComponent());
 303         centerPanel.setBackground(Color.WHITE);
 304         satelliteComponent = scene.createSatelliteView();
 305         satelliteComponent.setSize(200, 200);
 306         centerPanel.add(SATELLITE_STRING, satelliteComponent);
 307 
 308         // TODO: Fix the hot key for entering the satellite view
 309         this.addKeyListener(keyListener);
 310 
 311         scene.getComponent().addHierarchyBoundsListener(new HierarchyBoundsListener() {
 312 
 313             @Override
 314             public void ancestorMoved(HierarchyEvent e) {
 315             }
 316 
 317             @Override
 318             public void ancestorResized(HierarchyEvent e) {
 319                 if (!notFirstTime && scene.getComponent().getBounds().width > 0) {
 320                     notFirstTime = true;
 321                     SwingUtilities.invokeLater(new Runnable() {
 322 
 323                         @Override
 324                         public void run() {
 325                             EditorTopComponent.this.scene.initialize();
 326                         }
 327                     });
 328                 }
 329             }
 330         });
 331 
 332         if (diagram.getGraph().getGroup().getGraphsCount() == 1) {
 333             rangeSlider.setVisible(false);
 334         }
 335 
 336         updateDisplayName();
 337     }
 338     private KeyListener keyListener = new KeyListener() {
 339 
 340         @Override
 341         public void keyTyped(KeyEvent e) {
 342         }
 343 
 344         @Override
 345         public void keyPressed(KeyEvent e) {
 346             if (e.getKeyCode() == KeyEvent.VK_S) {
 347                 EditorTopComponent.this.overviewButton.setSelected(true);
 348                 EditorTopComponent.this.overviewAction.setState(true);
 349             }
 350         }
 351 
 352         @Override
 353         public void keyReleased(KeyEvent e) {
 354             if (e.getKeyCode() == KeyEvent.VK_S) {
 355                 EditorTopComponent.this.overviewButton.setSelected(false);
 356                 EditorTopComponent.this.overviewAction.setState(false);
 357             }
 358         }
 359     };
 360 
 361     public DiagramViewModel getDiagramModel() {
 362         return rangeSliderModel;
 363     }
 364 
 365     private void showSatellite() {
 366         cardLayout.show(centerPanel, SATELLITE_STRING);
 367         satelliteComponent.requestFocus();
 368 
 369     }
 370 
 371     private void showScene() {
 372         cardLayout.show(centerPanel, SCENE_STRING);
 373         scene.getComponent().requestFocus();
 374     }
 375 
 376     public void zoomOut() {
 377         scene.zoomOut();
 378     }
 379 
 380     public void zoomIn() {
 381         scene.zoomIn();
 382     }
 383 
 384     public void showPrevDiagram() {
 385         int fp = getModel().getFirstPosition();
 386         int sp = getModel().getSecondPosition();
 387         if (fp != 0) {
 388             fp--;
 389             sp--;
 390             getModel().setPositions(fp, sp);
 391         }
 392     }
 393 
 394     public DiagramViewModel getModel() {
 395         return rangeSliderModel;
 396     }
 397 
 398     public FilterChain getFilterChain() {
 399         return getModel().getFilterChain();
 400     }
 401 
 402     public static EditorTopComponent getActive() {
 403         Set<? extends Mode> modes = WindowManager.getDefault().getModes();
 404         for (Mode m : modes) {
 405             TopComponent tc = m.getSelectedTopComponent();
 406             if (tc instanceof EditorTopComponent) {
 407                 return (EditorTopComponent) tc;
 408             }
 409         }
 410         return null;
 411     }
 412 
 413     /** This method is called from within the constructor to
 414      * initialize the form.
 415      * WARNING: Do NOT modify this code. The content of this method is
 416      * always regenerated by the Form Editor.
 417      */
 418         // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
 419         private void initComponents() {
 420                 jCheckBox1 = new javax.swing.JCheckBox();
 421 
 422                 org.openide.awt.Mnemonics.setLocalizedText(jCheckBox1, "jCheckBox1");
 423                 jCheckBox1.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
 424                 jCheckBox1.setMargin(new java.awt.Insets(0, 0, 0, 0));
 425 
 426                 setLayout(new java.awt.BorderLayout());
 427 
 428         }// </editor-fold>//GEN-END:initComponents
 429         // Variables declaration - do not modify//GEN-BEGIN:variables
 430         private javax.swing.JCheckBox jCheckBox1;
 431         // End of variables declaration//GEN-END:variables
 432 
 433     @Override
 434     public int getPersistenceType() {
 435         return TopComponent.PERSISTENCE_NEVER;
 436     }
 437 
 438     @Override
 439     public void componentOpened() {
 440     }
 441 
 442     @Override
 443     public void componentClosed() {
 444         rangeSliderModel.close();
 445     }
 446 
 447     @Override
 448     protected String preferredID() {
 449         return PREFERRED_ID;
 450     }
 451 
 452     private ChangedListener<DiagramViewModel> diagramChangedListener = new ChangedListener<DiagramViewModel>() {
 453 
 454         @Override
 455         public void changed(DiagramViewModel source) {
 456             updateDisplayName();
 457             Collection<Object> list = new ArrayList<>();
 458             list.add(new EditorInputGraphProvider(EditorTopComponent.this));
 459             graphContent.set(list, null);
 460             diagramProvider.getChangedEvent().fire();
 461         }
 462 
 463     };
 464 
 465     public boolean showPredSucc() {
 466         return (Boolean) predSuccAction.getValue(PredSuccAction.STATE);
 467     }
 468 
 469     public void setSelection(PropertyMatcher matcher) {
 470 
 471         Properties.PropertySelector<Figure> selector = new Properties.PropertySelector<>(getModel().getDiagramToView().getFigures());
 472         List<Figure> list = selector.selectMultiple(matcher);
 473         setSelectedFigures(list);
 474     }
 475 
 476     public void setSelectedFigures(List<Figure> list) {
 477         scene.setSelection(list);
 478         scene.centerFigures(list);
 479     }
 480 
 481     public void setSelectedNodes(Set<InputNode> nodes) {
 482 
 483         List<Figure> list = new ArrayList<>();
 484         Set<Integer> ids = new HashSet<>();
 485         for (InputNode n : nodes) {
 486             ids.add(n.getId());
 487         }
 488 
 489         for (Figure f : getModel().getDiagramToView().getFigures()) {
 490             for (InputNode n : f.getSource().getSourceNodes()) {
 491                 if (ids.contains(n.getId())) {
 492                     list.add(f);
 493                     break;
 494                 }
 495             }
 496         }
 497 
 498         setSelectedFigures(list);
 499     }
 500 
 501     @Override
 502     public void propertyChange(PropertyChangeEvent evt) {
 503         if (evt.getSource() == this.predSuccAction) {
 504             boolean b = (Boolean) predSuccAction.getValue(PredSuccAction.STATE);
 505             this.getModel().setShowNodeHull(b);
 506         } else if (evt.getSource() == this.overviewAction) {
 507             boolean b = (Boolean) overviewAction.getValue(OverviewAction.STATE);
 508             if (b) {
 509                 showSatellite();
 510             } else {
 511                 showScene();
 512             }
 513         } else if (evt.getSource() == this.blockLayoutAction) {
 514             boolean b = (Boolean) blockLayoutAction.getValue(EnableBlockLayoutAction.STATE);
 515             this.getModel().setShowBlocks(b);
 516         } else if (evt.getSource() == this.hideDuplicatesAction) {
 517             boolean b = (Boolean) hideDuplicatesAction.getValue(HideDuplicatesAction.STATE);
 518             this.getModel().setHideDuplicates(b);
 519         } else if (evt.getSource() == this.selectionModeAction || evt.getSource() == this.panModeAction) {
 520             if (panModeAction.isSelected()) {
 521                 scene.setInteractionMode(DiagramViewer.InteractionMode.PANNING);
 522             } else if (selectionModeAction.isSelected()) {
 523                 scene.setInteractionMode(DiagramViewer.InteractionMode.SELECTION);
 524             }
 525         } else {
 526             assert false : "Unknown event source";
 527         }
 528     }
 529 
 530     public void extract() {
 531         getModel().showOnly(getModel().getSelectedNodes());
 532     }
 533 
 534     public void hideNodes() {
 535         Set<Integer> selectedNodes = this.getModel().getSelectedNodes();
 536         HashSet<Integer> nodes = new HashSet<>(getModel().getHiddenNodes());
 537         nodes.addAll(selectedNodes);
 538         this.getModel().showNot(nodes);
 539     }
 540 
 541     public void expandPredecessors() {
 542         Set<Figure> oldSelection = getModel().getSelectedFigures();
 543         Set<Figure> figures = new HashSet<>();
 544 
 545         for (Figure f : this.getDiagramModel().getDiagramToView().getFigures()) {
 546             boolean ok = false;
 547             if (oldSelection.contains(f)) {
 548                 ok = true;
 549             } else {
 550                 for (Figure pred : f.getSuccessors()) {
 551                     if (oldSelection.contains(pred)) {
 552                         ok = true;
 553                         break;
 554                     }
 555                 }
 556             }
 557 
 558             if (ok) {
 559                 figures.add(f);
 560             }
 561         }
 562 
 563         getModel().showAll(figures);
 564     }
 565 
 566     public void expandSuccessors() {
 567         Set<Figure> oldSelection = getModel().getSelectedFigures();
 568         Set<Figure> figures = new HashSet<>();
 569 
 570         for (Figure f : this.getDiagramModel().getDiagramToView().getFigures()) {
 571             boolean ok = false;
 572             if (oldSelection.contains(f)) {
 573                 ok = true;
 574             } else {
 575                 for (Figure succ : f.getPredecessors()) {
 576                     if (oldSelection.contains(succ)) {
 577                         ok = true;
 578                         break;
 579                     }
 580                 }
 581             }
 582 
 583             if (ok) {
 584                 figures.add(f);
 585             }
 586         }
 587 
 588         getModel().showAll(figures);
 589     }
 590 
 591     public void showAll() {
 592         getModel().showNot(new HashSet<Integer>());
 593     }
 594 
 595     public Diagram getDiagram() {
 596         return getDiagramModel().getDiagramToView();
 597     }
 598 
 599     @Override
 600     protected void componentHidden() {
 601         super.componentHidden();
 602         scene.componentHidden();
 603 
 604     }
 605 
 606     @Override
 607     protected void componentShowing() {
 608         super.componentShowing();
 609         scene.componentShowing();
 610     }
 611 
 612     @Override
 613     public void requestActive() {
 614         super.requestActive();
 615         scene.getComponent().requestFocus();
 616     }
 617 
 618     @Override
 619     public UndoRedo getUndoRedo() {
 620         return scene.getUndoRedo();
 621     }
 622 
 623     @Override
 624     protected Object writeReplace() throws ObjectStreamException {
 625         throw new NotSerializableException();
 626 }
 627 }