瀏覽代碼

Merge pull request #642 from neph1/fix_multi_select

Allow node actions on multiple selected nodes (Delete, etc)
Rickard Edén 3 月之前
父節點
當前提交
14d2632da1

+ 24 - 38
jme3-core/src/com/jme3/gde/core/sceneexplorer/SceneExplorerTopComponent.java

@@ -79,12 +79,13 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
     private static final Logger logger = Logger.getLogger(SceneExplorerTopComponent.class.getName());
     private static SceneExplorerTopComponent instance;
     private static final String PREFERRED_ID = "SceneExplorerTopComponent";
-//    private final Result<AbstractSceneExplorerNode> nodeSelectionResult;
-    private AbstractSceneExplorerNode selectedSpatial;
-    private AbstractSceneExplorerNode lastSelected;
+
+    private AbstractSceneExplorerNode[] selectedSpatials;
+    private AbstractSceneExplorerNode[] lastSelected;
     private final Map<String, MaterialChangeProvider> materialChangeProviders = new HashMap<>();
     private final Map<String, List<MaterialChangeListener>> materialChangeListeners = new HashMap<>();
-    private transient ExplorerManager explorerManager = new ExplorerManager();
+    
+    private final transient ExplorerManager explorerManager = new ExplorerManager();
 
     public SceneExplorerTopComponent() {
         initComponents();
@@ -149,18 +150,12 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
     }// </editor-fold>//GEN-END:initComponents
 
     private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed
-        if (selectedSpatial == null) {
+        if (selectedSpatials == null) {
             return;
         }
-        SwingUtilities.invokeLater(() -> {
-            Node rootNode = SceneExplorerTopComponent.findInstance().getExplorerManager().getRootContext();
-            if (rootNode instanceof JmeNode jmeNode) {
-                SceneApplication.getApplication().enqueue(new RefreshJmeSpatial(jmeNode, selectedSpatial.getName()));
-            } else {
-                selectedSpatial.refresh(false);
-            }
-        });
-        
+        for (AbstractSceneExplorerNode node: selectedSpatials) {
+            node.refresh(false);
+        }
     }//GEN-LAST:event_jButton1ActionPerformed
     // Variables declaration - do not modify//GEN-BEGIN:variables
     private javax.swing.JScrollPane explorerScrollPane;
@@ -188,13 +183,13 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
      * @return 
      */
     public static synchronized SceneExplorerTopComponent findInstance() {
-        TopComponent win = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
-        if (win == null) {
+        TopComponent window = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
+        if (window == null) {
             logger.warning(
                     "Cannot find " + PREFERRED_ID + " component. It will not be located properly in the window system.");
             return getDefault();
         }
-        if (win instanceof SceneExplorerTopComponent sceneExplorerTopComponent) {
+        if (window instanceof SceneExplorerTopComponent sceneExplorerTopComponent) {
             return sceneExplorerTopComponent;
         }
         logger.warning(
@@ -227,27 +222,20 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
         SceneApplication.getApplication().removeSceneListener(this);
         // TODO add custom code on component closing
     }
-
+    
     void writeProperties(java.util.Properties p) {
-        // better to version settings since initial version as advocated at
-        // http://wiki.apidesign.org/wiki/PropertyFiles
+        // Required. Do not remove.
         p.setProperty("version", "1.0");
-        // TODO store your settings
     }
-
+    
     Object readProperties(java.util.Properties p) {
+        // Required. Do not remove.
         if (instance == null) {
             instance = this;
         }
-        instance.readPropertiesImpl(p);
         return instance;
     }
 
-    private void readPropertiesImpl(java.util.Properties p) {
-        // TODO read your settings according to their version
-        
-    }
-
     @Override
     protected String preferredID() {
         return PREFERRED_ID;
@@ -263,18 +251,16 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
         return explorerManager;
     }
 
-    public void setSelectedNode(AbstractSceneExplorerNode node) {
-        selectedSpatial = node;
-        if (node != null) {
-            lastSelected = node;
+    public void setSelectedNode(AbstractSceneExplorerNode[] nodes) {
+        selectedSpatials = nodes;
+        if (nodes != null) {
+            lastSelected = nodes;
         }
         try {
-            if (node != null) {
-                explorerManager.setSelectedNodes(new Node[]{node});
-//                setActivatedNodes(new Node[]{node});
+            if (nodes != null) {
+                explorerManager.setSelectedNodes(nodes);
             } else {
                 explorerManager.setSelectedNodes(new Node[]{});
-//                setActivatedNodes(new Node[]{});
             }
         } catch (PropertyVetoException ex) {
             Exceptions.printStackTrace(ex);
@@ -309,11 +295,11 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
     @Override
     public void previewCreated(PreviewRequest request) {
     }
-
+    
     /**
      * @return the selectedSpatial
      */
-    public AbstractSceneExplorerNode getLastSelected() {
+    public AbstractSceneExplorerNode[] getLastSelected() {
         return lastSelected;
     }
 

+ 12 - 6
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/actions/MotionPathPopup.java

@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2009-2010 jMonkeyEngine
+ *  Copyright (c) 2009-2024 jMonkeyEngine
  *  All rights reserved.
  * 
  *  Redistribution and use in source and binary forms, with or without
@@ -107,14 +107,20 @@ public class MotionPathPopup extends AbstractAction implements Presenter.Popup {
                 Vector3f pos;
                 
                 SceneToolController controller = SceneApplication.getApplication().getStateManager().getState(SceneToolController.class);
-                if (controller != null && (!controller.getCursorLocation().equals(Vector3f.ZERO))) { // Vector3f.ZERO means not yet clicked
+                 // Vector3f.ZERO means not yet clicked
+                if (controller != null && (!controller.getCursorLocation().equals(Vector3f.ZERO))) {
                     pos = controller.getCursorLocation().clone().addLocal(0, jmeMotionPath.getDebugBoxExtents() * 3f, 0); // Shifting up so a) Netbeans isn't merging Waypoints and b) it's visible
                 } else {
-                    AbstractSceneExplorerNode node = SceneExplorerTopComponent.findInstance().getLastSelected();
-                    if (node instanceof JmeVector3f) { // null instanceof JmeVector3f == false
-                        pos = ((JmeVector3f)node).getVector3f().clone().addLocal(0, jmeMotionPath.getDebugBoxExtents() * 3f, 0);
+                    AbstractSceneExplorerNode[] nodes = SceneExplorerTopComponent.findInstance().getLastSelected();
+                    if(nodes == null || nodes.length == 0) {
+                        return;
+                    }
+                    final AbstractSceneExplorerNode node = nodes[0];
+                    if (node instanceof JmeVector3f jmeVector3f) {
+                        pos = jmeVector3f.getVector3f().clone().addLocal(0, jmeMotionPath.getDebugBoxExtents() * 3f, 0);
                     } else {
-                        pos = new Vector3f(0f, 1.0f, 0f); // Default is a bit over the Center
+                        // Default is a bit over the Center
+                        pos = new Vector3f(0f, 1.0f, 0f);
                     }
                 }
                 

+ 80 - 87
jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java

@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2009-2023 jMonkeyEngine
+ *  Copyright (c) 2009-2024 jMonkeyEngine
  *  All rights reserved.
  * 
  *  Redistribution and use in source and binary forms, with or without
@@ -1078,11 +1078,10 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
     // End of variables declaration//GEN-END:variables
 
     private void emit(Spatial root) {
-        if (root instanceof ParticleEmitter) {
-            ((ParticleEmitter) root).killAllParticles();
-            ((ParticleEmitter) root).emitAllParticles();
-        } else if (root instanceof Node) {
-            Node n = (Node) root;
+        if (root instanceof ParticleEmitter particleEmitter) {
+            particleEmitter.killAllParticles();
+            particleEmitter.emitAllParticles();
+        } else if (root instanceof Node n) {
             for (Spatial child : n.getChildren()) {
                 emit(child);
             }
@@ -1112,14 +1111,14 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
      * @return
      */
     public static synchronized SceneComposerTopComponent findInstance() {
-        TopComponent win = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
-        if (win == null) {
+        TopComponent window = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
+        if (window == null) {
             Logger.getLogger(SceneComposerTopComponent.class.getName()).warning(
                     "Cannot find " + PREFERRED_ID + " component. It will not be located properly in the window system.");
             return getDefault();
         }
-        if (win instanceof SceneComposerTopComponent) {
-            return (SceneComposerTopComponent) win;
+        if (window instanceof SceneComposerTopComponent sceneComposerTopComponent) {
+            return sceneComposerTopComponent;
         }
         Logger.getLogger(SceneComposerTopComponent.class.getName()).warning(
                 "There seem to be multiple components with the '" + PREFERRED_ID
@@ -1203,15 +1202,11 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
     }
 
     private void setSelectedObjectText(final String text) {
-        java.awt.EventQueue.invokeLater(new Runnable() {
-
-            @Override
-            public void run() {
-                if (text != null) {
-                    ((TitledBorder) jPanel4.getBorder()).setTitle("Utilities - " + text);
-                } else {
-                    ((TitledBorder) jPanel4.getBorder()).setTitle("Utilities - no spatial selected");
-                }
+        java.awt.EventQueue.invokeLater(() -> {
+            if (text != null) {
+                ((TitledBorder) jPanel4.getBorder()).setTitle("Utilities - " + text);
+            } else {
+                ((TitledBorder) jPanel4.getBorder()).setTitle("Utilities - no spatial selected");
             }
         });
     }
@@ -1255,15 +1250,15 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
         }
     }
 
-    public void openScene(Spatial spat, AssetDataObject file, ProjectAssetManager manager) {
+    public void openScene(Spatial spatial, AssetDataObject file, ProjectAssetManager manager) {
         cleanupControllers();
         SceneApplication.getApplication().addSceneListener(this);
         Node node;
-        if (spat instanceof Node) {
-            node = (Node) spat;
+        if (spatial instanceof Node node1) {
+            node = node1;
         } else {
             node = new Node();
-            node.attachChild(spat);
+            node.attachChild(spatial);
         }
         JmeNode jmeNode = NodeUtility.createNode(node, file, false);
         SceneRequest request = new SceneRequest(this, jmeNode, manager);
@@ -1318,28 +1313,30 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
             return;
         }
         Collection<AbstractSceneExplorerNode> items = (Collection<AbstractSceneExplorerNode>) result.allInstances();
-        for (AbstractSceneExplorerNode node : items) {
-            if (select(node)) {
-                return;
-            }
-        }
+
+        AbstractSceneExplorerNode[] abstractNodes = new AbstractSceneExplorerNode[items.size()];
+        SceneViewerTopComponent.findInstance().setActivatedNodes(items.toArray(org.openide.nodes.Node[]::new));
+        items.toArray(abstractNodes);
+        select(abstractNodes);
     }
 
-    private boolean select(AbstractSceneExplorerNode node) {
+    private boolean select(AbstractSceneExplorerNode[] nodes) {
+        if( nodes.length == 0) {
+            return false;
+        }
+        AbstractSceneExplorerNode first = nodes[0];
         if (editorController != null) {
-            editorController.setSelectedExplorerNode(node);
+            editorController.setSelectedExplorerNode(first);
         }
-        if (node instanceof JmeSpatial) {
-            selectSpatial(((JmeSpatial) node).getLookup().lookup(Spatial.class));
-            SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{node});
-            SceneExplorerTopComponent.findInstance().setSelectedNode(node);
+        if (first instanceof JmeSpatial jmeSpatial) {
+            selectSpatial(jmeSpatial.getLookup().lookup(Spatial.class));
+            SceneExplorerTopComponent.findInstance().setSelectedNode(nodes);
             return true;
         } else if (toolController != null) {
-            Spatial selectedGizmo = toolController.getMarker(node);
+            Spatial selectedGizmo = toolController.getMarker(first);
             if (selectedGizmo != null) {
                 selectSpatial(selectedGizmo);
-                SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{node});
-                SceneExplorerTopComponent.findInstance().setSelectedNode(node);
+                SceneExplorerTopComponent.findInstance().setSelectedNode(nodes);
                 return true;
             }
         }
@@ -1356,8 +1353,8 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
         } else if (toolController != null) {
             toolController.updateSelection(selection);
         }
-        if (selection instanceof Node) {
-            setSelectedObjectText(((Node) selection).getName());
+        if (selection instanceof Node node) {
+            setSelectedObjectText(node.getName());
         } else if (selection instanceof Spatial) {
             setSelectedObjectText(selection.getName());
         } else {
@@ -1423,57 +1420,53 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
 
             editorController.setTerrainLodCamera();
             final SpatialAssetDataObject dobj = ((SpatialAssetDataObject) currentRequest.getDataObject());
-            listener = new ProjectAssetManager.ClassPathChangeListener() {
-
-                @Override
-                public void classPathChanged(final ProjectAssetManager manager) {
-                    if (dobj.isModified()) {
-                        Confirmation msg = new NotifyDescriptor.Confirmation(
-                                "Classes have been changed, save and reload scene?",
-                                NotifyDescriptor.OK_CANCEL_OPTION,
-                                NotifyDescriptor.INFORMATION_MESSAGE);
-                        Object result = DialogDisplayer.getDefault().notify(msg);
-                        if (!NotifyDescriptor.OK_OPTION.equals(result)) {
-                            return;
-                        }
-                        try {
-                            dobj.saveAsset();
-                        } catch (IOException ex) {
-                            Exceptions.printStackTrace(ex);
-                        }
+            listener = (final ProjectAssetManager manager) -> {
+                if (dobj.isModified()) {
+                    Confirmation msg = new NotifyDescriptor.Confirmation(
+                            "Classes have been changed, save and reload scene?",
+                            NotifyDescriptor.OK_CANCEL_OPTION,
+                            NotifyDescriptor.INFORMATION_MESSAGE);
+                    Object result1 = DialogDisplayer.getDefault().notify(msg);
+                    if (!NotifyDescriptor.OK_OPTION.equals(result1)) {
+                        return;
                     }
-                    Runnable call = new Runnable() {
-
-                        @Override
-                        public void run() {
-                            ProgressHandle progressHandle = ProgressHandle.createHandle("Reloading Scene..");
-                            progressHandle.start();
-                            try {
-                                manager.clearCache();
-                                final Spatial asset = dobj.loadAsset();
-                                if (asset != null) {
-                                    java.awt.EventQueue.invokeLater(new Runnable() {
-
-                                        @Override
-                                        public void run() {
-                                            SceneComposerTopComponent composer = SceneComposerTopComponent.findInstance();
-                                            composer.openScene(asset, dobj, manager);
-                                        }
-                                    });
-                                } else {
-                                    Confirmation msg = new NotifyDescriptor.Confirmation(
-                                            "Error opening " + dobj.getPrimaryFile().getNameExt(),
-                                            NotifyDescriptor.OK_CANCEL_OPTION,
-                                            NotifyDescriptor.ERROR_MESSAGE);
-                                    DialogDisplayer.getDefault().notify(msg);
-                                }
-                            } finally {
-                                progressHandle.finish();
+                    try {
+                        dobj.saveAsset();
+                    } catch (IOException ex) {
+                        Exceptions.printStackTrace(ex);
+                    }
+                }
+                Runnable call = new Runnable() {
+                    
+                    @Override
+                    public void run() {
+                        ProgressHandle progressHandle = ProgressHandle.createHandle("Reloading Scene..");
+                        progressHandle.start();
+                        try {
+                            manager.clearCache();
+                            final Spatial asset = dobj.loadAsset();
+                            if (asset != null) {
+                                java.awt.EventQueue.invokeLater(new Runnable() {
+                                    
+                                    @Override
+                                    public void run() {
+                                        SceneComposerTopComponent composer = SceneComposerTopComponent.findInstance();
+                                        composer.openScene(asset, dobj, manager);
+                                    }
+                                });
+                            } else {
+                                Confirmation msg = new NotifyDescriptor.Confirmation(
+                                        "Error opening " + dobj.getPrimaryFile().getNameExt(),
+                                        NotifyDescriptor.OK_CANCEL_OPTION,
+                                        NotifyDescriptor.ERROR_MESSAGE);
+                                DialogDisplayer.getDefault().notify(msg);
                             }
+                        } finally {
+                            progressHandle.finish();
                         }
-                    };
-                    new Thread(call).start();
-                }
+                    }
+                };
+                new Thread(call).start();
             };
 //            currentRequest.getManager().addClassPathEventListener(listener);
         }

+ 31 - 3
jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/SelectTool.java

@@ -1,10 +1,38 @@
 /*
- * To change this template, choose Tools | Templates and open the template in
- * the editor.
+ *  Copyright (c) 2009-2024 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 package com.jme3.gde.scenecomposer.tools;
 
 import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
 import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent;
@@ -98,7 +126,7 @@ public class SelectTool extends SceneEditTool {
                             JmeSpatial n = rootNode.getChild(selec);
                             if (n != null) {
                                 SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{n});
-                                SceneExplorerTopComponent.findInstance().setSelectedNode(n);
+                                SceneExplorerTopComponent.findInstance().setSelectedNode(new AbstractSceneExplorerNode[]{n});
                             }
                         }
                     });

+ 43 - 23
jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DuplicateShortcut.java

@@ -1,12 +1,39 @@
 /*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
+ *  Copyright (c) 2009-2024 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 package com.jme3.gde.scenecomposer.tools.shortcuts;
 
 import com.jme3.asset.AssetManager;
 import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
 import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent;
 import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
@@ -57,7 +84,11 @@ public class DuplicateShortcut extends ShortcutTool {
 
     private void duplicate() {
         Spatial selected = toolController.getSelectedSpatial();
-
+        
+        if(selected == null) {
+            return;
+        }
+        
         Spatial clone = selected.clone();
         clone.move(1, 0, 1);
 
@@ -67,28 +98,17 @@ public class DuplicateShortcut extends ShortcutTool {
         final Spatial cloned = clone;
         final JmeNode rootNode = toolController.getRootNode();
         refreshSelected(rootNode, selected.getParent());
-
-        java.awt.EventQueue.invokeLater(new Runnable() {
-
-            @Override
-            public void run() {
-                if (cloned != null) {
-                    SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{rootNode.getChild(cloned)});
-                    SceneExplorerTopComponent.findInstance().setSelectedNode(rootNode.getChild(cloned));
-                }
-            }
+        
+        java.awt.EventQueue.invokeLater(() -> {
+                SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{rootNode.getChild(cloned)});
+                SceneExplorerTopComponent.findInstance().setSelectedNode(new AbstractSceneExplorerNode[]{rootNode.getChild(cloned)});
         });
-
         toolController.updateSelection(selected);
     }
 
     private void refreshSelected(final JmeNode jmeRootNode, final Node parent) {
-        java.awt.EventQueue.invokeLater(new Runnable() {
-
-            @Override
-            public void run() {
-                jmeRootNode.getChild(parent).refresh(false);
-            }
+        java.awt.EventQueue.invokeLater(() -> {
+            jmeRootNode.getChild(parent).refresh(false);
         });
     }
 
@@ -119,8 +139,8 @@ public class DuplicateShortcut extends ShortcutTool {
 
     private class DuplicateUndo extends AbstractUndoableSceneEdit {
 
-        private Spatial spatial;
-        private Node parent;
+        private final Spatial spatial;
+        private final Node parent;
 
         DuplicateUndo(Spatial spatial, Node parent) {
             this.spatial = spatial;