Browse Source

Merge branch 'master' into add_game_template

rickard 3 months ago
parent
commit
992282f615
36 changed files with 1883 additions and 851 deletions
  1. 2 1
      BasicGameTemplate/nbproject/project.properties
  2. 1 0
      JME3TestsTemplate/nbproject/project.properties
  3. 1 0
      build.gradle
  4. 1 1
      gradle.properties
  5. 97 48
      jme3-core/src/com/jme3/gde/core/appstates/NewAppStateVisualPanel1.java
  6. 13 43
      jme3-core/src/com/jme3/gde/core/assets/ExternalChangeScanner.java
  7. 22 22
      jme3-core/src/com/jme3/gde/core/assets/ProjectAssetManager.java
  8. 58 0
      jme3-core/src/com/jme3/gde/core/assets/RefreshJmeSpatial.java
  9. 233 0
      jme3-core/src/com/jme3/gde/core/assets/actions/MergeAnimationsAction.java
  10. 37 51
      jme3-core/src/com/jme3/gde/core/sceneexplorer/SceneExplorerTopComponent.java
  11. 12 6
      jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/actions/MotionPathPopup.java
  12. 83 61
      jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/actions/impl/NewCustomControlVisualPanel1.java
  13. 9 0
      jme3-glsl-highlighter/nbproject/project.xml
  14. 150 0
      jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/GlslCompletionProvider.java
  15. 123 0
      jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/DefaultCompletionItem.java
  16. 50 0
      jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/FunctionCompletionItem.java
  17. 50 0
      jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/KeywordCompletionItem.java
  18. 50 0
      jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/TypeCompletionItem.java
  19. 50 0
      jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/VariableCompletionItem.java
  20. BIN
      jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/class_16.png
  21. BIN
      jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/field_16.png
  22. BIN
      jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/method_16.png
  23. BIN
      jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/method_static_protected_16.png
  24. 459 447
      jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/lexer/GlslKeywordLibrary.java
  25. 20 26
      jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/lexer/GlslLexer.java
  26. 147 0
      jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/util/Trie.java
  27. 4 1
      jme3-materialeditor/src/com/jme3/gde/materials/MaterialPropertyEditor.java
  28. 1 0
      jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties
  29. 80 87
      jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java
  30. 42 30
      jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java
  31. 14 1
      jme3-scenecomposer/src/com/jme3/gde/scenecomposer/layer.xml
  32. 31 3
      jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/SelectTool.java
  33. 43 23
      jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DuplicateShortcut.java
  34. BIN
      nbi/antlib/nbi-ant-tasks.jar
  35. BIN
      nbi/antlib/nbi-engine.jar
  36. BIN
      nbi/antlib/nbi-registries-management.jar

+ 2 - 1
BasicGameTemplate/nbproject/project.properties

@@ -44,7 +44,8 @@ javac.classpath=\
     ${libs.jme3-terrain.classpath}:\
     ${libs.jme3-terrain.classpath}:\
     ${libs.jme3-jbullet.classpath}:\
     ${libs.jme3-jbullet.classpath}:\
     ${libs.jme3-awt-dialogs.classpath}:\
     ${libs.jme3-awt-dialogs.classpath}:\
-    ${libs.jme3-plugins-json.classpath}
+    ${libs.jme3-plugins-json.classpath}:\
+	${libs.jme3-plugins-json-gson.classpath}
 # Space-separated list of extra javac options
 # Space-separated list of extra javac options
 javac.compilerargs=
 javac.compilerargs=
 javac.deprecation=false
 javac.deprecation=false

+ 1 - 0
JME3TestsTemplate/nbproject/project.properties

@@ -27,6 +27,7 @@ jar.compress=false
 javac.classpath=\
 javac.classpath=\
     ${libs.jme3-jogg.classpath}:\
     ${libs.jme3-jogg.classpath}:\
     ${libs.jme3-plugins-json.classpath}:\
     ${libs.jme3-plugins-json.classpath}:\
+	${libs.jme3-plugins-json-gson.classpath}:\
     ${libs.jme3-networking.classpath}:\
     ${libs.jme3-networking.classpath}:\
     ${libs.jme3-plugins.classpath}:\
     ${libs.jme3-plugins.classpath}:\
     ${libs.jme3-core.classpath}:\
     ${libs.jme3-core.classpath}:\

+ 1 - 0
build.gradle

@@ -40,6 +40,7 @@ dependencies {
     corelibs dep("org.jmonkeyengine:jme3-desktop:$jmeVersion-$jmeVersionTag", true, true)
     corelibs dep("org.jmonkeyengine:jme3-desktop:$jmeVersion-$jmeVersionTag", true, true)
     corelibs dep("org.jmonkeyengine:jme3-awt-dialogs:$jmeVersion-$jmeVersionTag", true, true)
     corelibs dep("org.jmonkeyengine:jme3-awt-dialogs:$jmeVersion-$jmeVersionTag", true, true)
     corelibs dep("org.jmonkeyengine:jme3-plugins-json:$jmeVersion-$jmeVersionTag", true, true)
     corelibs dep("org.jmonkeyengine:jme3-plugins-json:$jmeVersion-$jmeVersionTag", true, true)
+	corelibs dep("org.jmonkeyengine:jme3-plugins-json-gson:$jmeVersion-$jmeVersionTag", true, true)
     corelibs dep("org.jmonkeyengine:jme3-lwjgl:$jmeVersion-$jmeVersionTag", true, true)
     corelibs dep("org.jmonkeyengine:jme3-lwjgl:$jmeVersion-$jmeVersionTag", true, true)
     corelibs dep("org.jmonkeyengine:jme3-effects:$jmeVersion-$jmeVersionTag", true, true)
     corelibs dep("org.jmonkeyengine:jme3-effects:$jmeVersion-$jmeVersionTag", true, true)
     corelibs dep("com.github.stephengold:Minie:8.2.0", true, true) // replacement for bullet-native
     corelibs dep("com.github.stephengold:Minie:8.2.0", true, true) // replacement for bullet-native

+ 1 - 1
gradle.properties

@@ -8,4 +8,4 @@ jmeVersionTag = stable
 #jmeVersionTagID = 0
 #jmeVersionTagID = 0
 
 
 # Path for downloading NetBeans Base
 # Path for downloading NetBeans Base
-netbeansUrl = https://archive.apache.org/dist/netbeans/netbeans/23/netbeans-23-bin.zip
+netbeansUrl = https://archive.apache.org/dist/netbeans/netbeans/24/netbeans-24-bin.zip

+ 97 - 48
jme3-core/src/com/jme3/gde/core/appstates/NewAppStateVisualPanel1.java

@@ -1,5 +1,5 @@
 /*
 /*
- *  Copyright (c) 2009-2010 jMonkeyEngine
+ *  Copyright (c) 2009-2024 jMonkeyEngine
  *  All rights reserved.
  *  All rights reserved.
  * 
  * 
  *  Redistribution and use in source and binary forms, with or without
  *  Redistribution and use in source and binary forms, with or without
@@ -31,13 +31,16 @@
  */
  */
 package com.jme3.gde.core.appstates;
 package com.jme3.gde.core.appstates;
 
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.EnumSet;
 import java.util.EnumSet;
-import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.List;
 import java.util.Set;
 import java.util.Set;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
 import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
 import javax.swing.JPanel;
 import javax.swing.JPanel;
 import org.netbeans.api.java.classpath.ClassPath;
 import org.netbeans.api.java.classpath.ClassPath;
 import org.netbeans.api.java.project.JavaProjectConstants;
 import org.netbeans.api.java.project.JavaProjectConstants;
@@ -48,11 +51,11 @@ import org.netbeans.api.java.source.ClasspathInfo;
 import org.netbeans.api.java.source.CompilationController;
 import org.netbeans.api.java.source.CompilationController;
 import org.netbeans.api.java.source.ElementHandle;
 import org.netbeans.api.java.source.ElementHandle;
 import org.netbeans.api.java.source.JavaSource;
 import org.netbeans.api.java.source.JavaSource;
-import org.netbeans.api.java.source.JavaSource.Phase;
-import org.netbeans.api.java.source.Task;
 import org.netbeans.api.project.Project;
 import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectUtils;
 import org.netbeans.api.project.SourceGroup;
 import org.netbeans.api.project.SourceGroup;
 import org.netbeans.api.project.Sources;
 import org.netbeans.api.project.Sources;
+import org.openide.filesystems.FileObject;
 import org.openide.util.Exceptions;
 import org.openide.util.Exceptions;
 
 
 @SuppressWarnings({"unchecked", "rawtypes"})
 @SuppressWarnings({"unchecked", "rawtypes"})
@@ -82,57 +85,103 @@ public final class NewAppStateVisualPanel1 extends JPanel {
     }
     }
 
 
     private List<String> getSources() {
     private List<String> getSources() {
-        Sources sources = proj.getLookup().lookup(Sources.class);
-        final List<String> list = new LinkedList<String>();
-        if (sources != null) {
+        Project root = ProjectUtils.rootOf(proj);
+        Set<Project> containedProjects = ProjectUtils.getContainedProjects(root, true);
+        List<Project> projects = new ArrayList<>();
+        projects.add(root);
+        if (containedProjects != null) {
+            projects.addAll(containedProjects);
+        }
+        if (projects.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        List<String> list = new ArrayList<>();
+        for (Project project : projects) {
+            Sources sources = ProjectUtils.getSources(project);
+            if (sources == null) {
+                continue;
+            }
+
             SourceGroup[] groups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
             SourceGroup[] groups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
-            if (groups != null) {
-                for (SourceGroup sourceGroup : groups) {
-                    ClasspathInfo cpInfo = ClasspathInfo.create(ClassPath.getClassPath(sourceGroup.getRootFolder(), ClassPath.BOOT),
-                            ClassPath.getClassPath(sourceGroup.getRootFolder(), ClassPath.COMPILE),
-                            ClassPath.getClassPath(sourceGroup.getRootFolder(), ClassPath.SOURCE));
-
-                    Set<SearchScope> set = EnumSet.of(ClassIndex.SearchScope.SOURCE);
-                    Set<ElementHandle<TypeElement>> types = cpInfo.getClassIndex().getDeclaredTypes("", NameKind.PREFIX, set);
-                    for (Iterator<ElementHandle<TypeElement>> it = types.iterator(); it.hasNext();) {
-                        final ElementHandle<TypeElement> elementHandle = it.next();
-                        JavaSource js = JavaSource.create(cpInfo);
-                        try {
-                            js.runUserActionTask(new Task<CompilationController>() {
-                                public void run(CompilationController control)
-                                        throws Exception {
-                                    control.toPhase(Phase.RESOLVED);
-                                    //TODO: check with proper casting check.. gotta get TypeMirror of Control interface..
-//                                    TypeUtilities util = control.getTypeUtilities();//.isCastable(Types., null)
-//                                    util.isCastable(null, null);
-                                    TypeElement elem = elementHandle.resolve(control);
-                                    if (elem != null) {
-                                        List<? extends TypeMirror> interfaces = elem.getInterfaces();
-                                        for (TypeMirror typeMirror : interfaces) {
-                                            String interfaceName = typeMirror.toString();
-                                            if ("com.jme3.app.state.AppState".equals(interfaceName)) {
-                                                list.add(elem.getQualifiedName().toString());
-                                            }
-                                        }
-                                        TypeMirror superClass = elem.getSuperclass();
-                                        String superClassName = superClass.toString();
-                                        if ("com.jme3.app.state.AbstractAppState".equals(superClassName)) {
-                                            list.add(elem.getQualifiedName().toString());
-                                        }
-                                    }
-                                }
-                            }, false);
-                        } catch (Exception ioe) {
-                            Exceptions.printStackTrace(ioe);
-                        }
-                    }
+            if (groups == null) {
+                continue;
+            }
 
 
+            for (SourceGroup sourceGroup : groups) {
+                FileObject rootFolder = sourceGroup.getRootFolder();
+                ClasspathInfo cpInfo = ClasspathInfo.create(
+                        ClassPath.getClassPath(rootFolder, ClassPath.BOOT),
+                        ClassPath.getClassPath(rootFolder, ClassPath.COMPILE),
+                        ClassPath.getClassPath(rootFolder, ClassPath.SOURCE)
+                );
+
+                Set<SearchScope> set = EnumSet.of(ClassIndex.SearchScope.SOURCE);
+                Set<ElementHandle<TypeElement>> types = cpInfo.getClassIndex().getDeclaredTypes("", NameKind.PREFIX, set);
+                for (ElementHandle<TypeElement> elementHandle : types) {
+                    JavaSource js = JavaSource.create(cpInfo);
+                    try {
+                        js.runUserActionTask((CompilationController control) -> {
+                            control.toPhase(JavaSource.Phase.RESOLVED);
+                            TypeElement elem = elementHandle.resolve(control);
+                            if (elem != null && doesInheritFromAppState(elem, control.getTypes())) {
+                                list.add(elem.getQualifiedName().toString());
+                            }
+                        }, false);
+                    } catch (IOException ioe) {
+                        Exceptions.printStackTrace(ioe);
+                    }
                 }
                 }
             }
             }
         }
         }
+
         return list;
         return list;
     }
     }
 
 
+    /**
+     * Checks recursively if a type inherits from or implements any
+     * AppState-related class/interface.
+     */
+    private boolean doesInheritFromAppState(TypeElement type, Types typeUtils) {
+        if (type == null) {
+            return false;
+        }
+
+        // Check interfaces
+        for (TypeMirror iface : type.getInterfaces()) {
+            if (isAppStateType(iface)) {
+                return true;
+            }
+            if (doesInheritFromAppState((TypeElement) typeUtils.asElement(iface), typeUtils)) {
+                return true;
+            }
+        }
+
+        // Check superclass
+        TypeMirror superClass = type.getSuperclass();
+        if (superClass != null && superClass.getKind() != TypeKind.NONE) {
+            if (isAppStateType(superClass)) {
+                return true;
+            }
+            return doesInheritFromAppState((TypeElement) typeUtils.asElement(superClass), typeUtils);
+        }
+
+        return false;
+    }
+
+    /**
+     * Determines if a TypeMirror corresponds to an AppState-related type.
+     */
+    private boolean isAppStateType(TypeMirror typeMirror) {
+        if (typeMirror == null) {
+            return false;
+        }
+        String className = typeMirror.toString();
+        return "com.jme3.app.state.AppState".equals(className)
+                || "com.jme3.app.state.AbstractAppState".equals(className)
+                || "com.jme3.app.state.BaseAppState".equals(className);
+    }
+
     public void load(Project proj) {
     public void load(Project proj) {
         this.proj = proj;
         this.proj = proj;
         scanControls();
         scanControls();

+ 13 - 43
jme3-core/src/com/jme3/gde/core/assets/ExternalChangeScanner.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2003-2012 jMonkeyEngine
+ * Copyright (c) 2003-2024 jMonkeyEngine
  * All rights reserved.
  * All rights reserved.
  *
  *
  * Redistribution and use in source and binary forms, with or without
  * Redistribution and use in source and binary forms, with or without
@@ -36,7 +36,6 @@ import com.jme3.gde.core.scene.ApplicationLogHandler;
 import com.jme3.gde.core.scene.SceneApplication;
 import com.jme3.gde.core.scene.SceneApplication;
 import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
 import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
-import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
 import com.jme3.gde.core.util.SpatialUtil;
 import com.jme3.gde.core.util.SpatialUtil;
 import com.jme3.gde.core.util.TaggedSpatialFinder;
 import com.jme3.gde.core.util.TaggedSpatialFinder;
 import com.jme3.gde.core.util.datatransfer.CopyAnimationDataFromOriginal;
 import com.jme3.gde.core.util.datatransfer.CopyAnimationDataFromOriginal;
@@ -186,9 +185,7 @@ public class ExternalChangeScanner implements AssetDataPropertyChangeListener,
     
     
     private void applyExternalData(final boolean onlyMeshData, 
     private void applyExternalData(final boolean onlyMeshData, 
             final boolean onlyAnimData) {
             final boolean onlyAnimData) {
-        final ProgressHandle handle = ProgressHandle.createHandle("Updating "
-                + "file "
-                + "data");
+        final ProgressHandle handle = ProgressHandle.createHandle("Updating file data");
         handle.start();
         handle.start();
         try {
         try {
             final Spatial original = loadOriginalSpatial();
             final Spatial original = loadOriginalSpatial();
@@ -207,13 +204,11 @@ public class ExternalChangeScanner implements AssetDataPropertyChangeListener,
                 new CopyTransformDataFromOriginal(finder).update(spat, original);
                 new CopyTransformDataFromOriginal(finder).update(spat, original);
                 new CopyMaterialDataFromOriginal(finder).update(spat, original);
                 new CopyMaterialDataFromOriginal(finder).update(spat, original);
             }
             }
-            // Do a complicated recurse refresh since AbstractSceneExplorerNode:refresh() isn't working
+            
             SwingUtilities.invokeLater(() -> {
             SwingUtilities.invokeLater(() -> {
                 Node rootNode = SceneExplorerTopComponent.findInstance().getExplorerManager().getRootContext();
                 Node rootNode = SceneExplorerTopComponent.findInstance().getExplorerManager().getRootContext();
-                if (rootNode instanceof JmeNode) {
-                    SceneApplication.getApplication().enqueue((Runnable) () -> {
-                    refreshNamedSpatial((JmeNode) rootNode, spat.getName());
-                    });
+                if (rootNode instanceof JmeNode jmeNode) {
+                    SceneApplication.getApplication().enqueue(new RefreshJmeSpatial(jmeNode, spat.getName()));
                 }
                 }
             });
             });
                 
                 
@@ -228,37 +223,6 @@ public class ExternalChangeScanner implements AssetDataPropertyChangeListener,
         }
         }
     }
     }
     
     
-    /**
-     * Look for the spatial to update using the name of the asset
-     * @param spatial
-     * @param name 
-     */
-    private void refreshNamedSpatial(JmeSpatial spatial, String name){
-        if(spatial.getName().equals(name)){
-            recurseRefresh(spatial);
-        } else {
-            for(Node s: spatial.getChildren().getNodes()){
-                if(s instanceof JmeSpatial){
-                    refreshNamedSpatial((JmeSpatial) s, name);
-                }
-                
-            }
-        }
-    }
-    
-    /**
-     * Refreshes the spatial and all children
-     * @param spatial 
-     */
-    private void recurseRefresh(JmeSpatial spatial){
-        spatial.refresh(false);
-        for(Node s: spatial.getChildren().getNodes()){
-            if(s instanceof JmeSpatial){
-                recurseRefresh((JmeSpatial) s);
-            }
-        }
-    }
-
     private Spatial loadOriginalSpatial() {
     private Spatial loadOriginalSpatial() {
         try {
         try {
             final DataObject dobj = DataObject.find(originalObject);
             final DataObject dobj = DataObject.find(originalObject);
@@ -266,8 +230,8 @@ public class ExternalChangeScanner implements AssetDataPropertyChangeListener,
                     dobj.getLookup().lookup(AssetData.class);
                     dobj.getLookup().lookup(AssetData.class);
             if (originalAssetData != null) {
             if (originalAssetData != null) {
                 final Savable sav = originalAssetData.loadAsset();
                 final Savable sav = originalAssetData.loadAsset();
-                if (sav instanceof Spatial) {
-                    return (Spatial) sav;
+                if (sav instanceof Spatial spatial) {
+                    return spatial;
                 } else {
                 } else {
                     LOGGER.log(Level.SEVERE, "Trying to load original for {0}"
                     LOGGER.log(Level.SEVERE, "Trying to load original for {0}"
                                     + " but it is not a Spatial: {1}",
                                     + " but it is not a Spatial: {1}",
@@ -352,18 +316,22 @@ public class ExternalChangeScanner implements AssetDataPropertyChangeListener,
         }
         }
     }
     }
 
 
+    @Override
     public void fileFolderCreated(FileEvent fe) {
     public void fileFolderCreated(FileEvent fe) {
     }
     }
 
 
+    @Override
     public void fileDataCreated(FileEvent fe) {
     public void fileDataCreated(FileEvent fe) {
     }
     }
 
 
+    @Override
     public void fileChanged(FileEvent fe) {
     public void fileChanged(FileEvent fe) {
         LOGGER.log(Level.INFO, "External file {0} for {1} changed!",
         LOGGER.log(Level.INFO, "External file {0} for {1} changed!",
                 new Object[]{fe.getFile(), assetDataObject.getName()});
                 new Object[]{fe.getFile(), assetDataObject.getName()});
         notifyUser();
         notifyUser();
     }
     }
 
 
+    @Override
     public void fileDeleted(FileEvent fe) {
     public void fileDeleted(FileEvent fe) {
         LOGGER.log(Level.INFO, "External file {0} for {1} deleted!",
         LOGGER.log(Level.INFO, "External file {0} for {1} deleted!",
                 new Object[]{fe.getFile(), assetDataObject.getName()});
                 new Object[]{fe.getFile(), assetDataObject.getName()});
@@ -377,6 +345,7 @@ public class ExternalChangeScanner implements AssetDataPropertyChangeListener,
         //TODO: add folder listener for when recreated
         //TODO: add folder listener for when recreated
     }
     }
 
 
+    @Override
     public void fileRenamed(FileRenameEvent fe) {
     public void fileRenamed(FileRenameEvent fe) {
         LOGGER.log(Level.INFO, "External file {0} for {1} renamed!",
         LOGGER.log(Level.INFO, "External file {0} for {1} renamed!",
                 new Object[]{fe.getFile(), assetDataObject.getName()});
                 new Object[]{fe.getFile(), assetDataObject.getName()});
@@ -388,6 +357,7 @@ public class ExternalChangeScanner implements AssetDataPropertyChangeListener,
         }
         }
     }
     }
 
 
+    @Override
     public void fileAttributeChanged(FileAttributeEvent fe) {
     public void fileAttributeChanged(FileAttributeEvent fe) {
     }
     }
 }
 }

+ 22 - 22
jme3-core/src/com/jme3/gde/core/assets/ProjectAssetManager.java

@@ -166,30 +166,30 @@ public class ProjectAssetManager extends DesktopAssetManager {
         }
         }
         SourceGroup[] groups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
         SourceGroup[] groups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
         List<URL> urls = new LinkedList<>();
         List<URL> urls = new LinkedList<>();
-        for (SourceGroup sourceGroup : groups) {
-            ClassPath path = ClassPath.getClassPath(sourceGroup.getRootFolder(), ClassPath.EXECUTE);
-            if (path == null) {
-                continue;
-            }
-
-            classPaths.add(path);
-            path.addPropertyChangeListener(classPathListener);
-            FileObject[] roots = path.getRoots();
-            for (FileObject fileObject : roots) {
-                if (!fileObject.equals(getAssetFolder())) {
-                    fileObject.addRecursiveListener(listener);
-                    logger.log(Level.FINE, "Add classpath:{0}", fileObject);
-                    classPathItems.add(new ClassPathItem(fileObject, listener));
-                    urls.add(fileObject.toURL());
+            for (SourceGroup sourceGroup : groups) {
+                ClassPath path = ClassPath.getClassPath(sourceGroup.getRootFolder(), ClassPath.EXECUTE);
+                if (path == null) {
+                    continue;
                 }
                 }
-                if (fileObject.toURL().toExternalForm().startsWith("jar")) {
-                    logger.log(Level.FINE, "Add locator:{0}", fileObject.toURL());
-                    jarItems.add(fileObject);
-                    registerLocator(fileObject.toURL().toExternalForm(),
-                            "com.jme3.asset.plugins.UrlLocator");
+
+                classPaths.add(path);
+                path.addPropertyChangeListener(classPathListener);
+                FileObject[] roots = path.getRoots();
+                for (FileObject fileObject : roots) {
+                    if (!fileObject.equals(getAssetFolder())) {
+                        fileObject.addRecursiveListener(listener);
+                        logger.log(Level.FINE, "Add classpath:{0}", fileObject);
+                        classPathItems.add(new ClassPathItem(fileObject, listener));
+                        urls.add(fileObject.toURL());
+                    }
+                    if (fileObject.toURL().toExternalForm().startsWith("jar")) {
+                        logger.log(Level.FINE, "Add locator:{0}", fileObject.toURL());
+                        jarItems.add(fileObject);
+                        registerLocator(fileObject.toURL().toExternalForm(),
+                                "com.jme3.asset.plugins.UrlLocator");
+                    }
                 }
                 }
             }
             }
-        }
 
 
         loadGradleClassLoader(urls);
         loadGradleClassLoader(urls);
 
 
@@ -223,7 +223,7 @@ public class ProjectAssetManager extends DesktopAssetManager {
         for (File file : runtimeFiles) {
         for (File file : runtimeFiles) {
             // logger.info(file.getName() + " : "  + file.getAbsolutePath());
             // logger.info(file.getName() + " : "  + file.getAbsolutePath());
             FileObject fo = FileUtil.toFileObject(file);
             FileObject fo = FileUtil.toFileObject(file);
-            if (fo != null && !fo.isFolder()) {
+            if (fo != null) {
                 logger.info(fo.toURL().toExternalForm());
                 logger.info(fo.toURL().toExternalForm());
                 if (!fo.equals(getAssetFolder())) {
                 if (!fo.equals(getAssetFolder())) {
                     fo.addRecursiveListener(listener);
                     fo.addRecursiveListener(listener);

+ 58 - 0
jme3-core/src/com/jme3/gde/core/assets/RefreshJmeSpatial.java

@@ -0,0 +1,58 @@
+
+package com.jme3.gde.core.assets;
+
+import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
+import org.openide.nodes.Node;
+
+import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
+
+/**
+ * Work around for refresh not working recursively on JmeSpatial
+ * @author rickard
+ */
+public class RefreshJmeSpatial implements Runnable {
+    
+    private final JmeNode rootNode;
+    private final String spatialName;
+    
+    public RefreshJmeSpatial(JmeNode rootNode, String spatialName) {
+        this.rootNode = rootNode;
+        this.spatialName = spatialName;
+    }
+    
+    @Override
+    public void run() {
+        refreshNamedSpatial(rootNode, spatialName);
+    }
+    /**
+     * Look for the spatial to update using the name of the asset
+     * @param spatial
+     * @param name 
+     */
+    private void refreshNamedSpatial(JmeSpatial spatial, String name){
+        if(spatial.getName().equals(name)){
+            recurseRefresh(spatial);
+        } else {
+            for(Node s: spatial.getChildren().getNodes()){
+                if(s instanceof JmeSpatial jmeSpatial){
+                    refreshNamedSpatial(jmeSpatial, name);
+                }
+                
+            }
+        }
+    }
+    
+    /**
+     * Refreshes the spatial and all children
+     * @param spatial 
+     */
+    private void recurseRefresh(JmeSpatial spatial){
+        spatial.refresh(false);
+        for(Node s: spatial.getChildren().getNodes()){
+            if(s instanceof JmeSpatial jmeSpatial){
+                recurseRefresh(jmeSpatial);
+            }
+        }
+    }
+
+}

+ 233 - 0
jme3-core/src/com/jme3/gde/core/assets/actions/MergeAnimationsAction.java

@@ -0,0 +1,233 @@
+/*
+ *  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.core.assets.actions;
+
+import com.jme3.anim.AnimClip;
+import com.jme3.anim.AnimComposer;
+import com.jme3.anim.AnimTrack;
+import com.jme3.anim.Armature;
+import com.jme3.anim.Joint;
+import com.jme3.anim.SkinningControl;
+import com.jme3.anim.TransformTrack;
+import com.jme3.anim.util.HasLocalTransform;
+import com.jme3.gde.core.assets.SpatialAssetDataObject;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.util.SafeArrayList;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.NotifyDescriptor.Confirmation;
+import org.openide.util.Exceptions;
+
+/**
+ * Action for merging one or more spatials' animation to another. Same rig
+ * required.
+ *
+ * @author rickard
+ */
+public class MergeAnimationsAction implements ActionListener {
+
+    private static final String CANCEL = "Cancel";
+    private final List<SpatialAssetDataObject> spatials;
+    private final Logger logger;
+
+    public MergeAnimationsAction(List<SpatialAssetDataObject> context) {
+        this.spatials = context;
+        logger = Logger.getLogger(MergeAnimationsAction.class.getName());
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (spatials.size() == 1) {
+            DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(
+                    "Must select more than one spatial",
+                    NotifyDescriptor.ERROR_MESSAGE));
+            return;
+        }
+
+        final Object selectedSpatial = createSelector().getValue();
+
+        if (selectedSpatial == null || selectedSpatial.equals(CANCEL)) {
+            logger.log(Level.INFO, "Operation cancelled by user.");
+            return;
+        }
+
+        final SpatialAssetDataObject targetAsset = findSpatial(selectedSpatial.toString());
+
+        if (targetAsset == null) {
+            logger.log(Level.INFO, "Operation failed. No spatial.");
+            return;
+        }
+
+        final Spatial targetSpatial = targetAsset.loadAsset();
+        final AnimComposer targetAnimComposer = findAnimComposer(targetSpatial, null);
+
+        if (targetAnimComposer == null) {
+            DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(
+                    String.format("%s has no AnimComposer.", targetSpatial),
+                    NotifyDescriptor.ERROR_MESSAGE));
+            return;
+        }
+        final Spatial targetAnimComposerSpatial = targetAnimComposer.getSpatial();
+        for (final Iterator<SpatialAssetDataObject> it = spatials.iterator(); it.hasNext();) {
+            final SpatialAssetDataObject spatialAssetDataObject = it.next();
+            if (spatialAssetDataObject.getName().equals(selectedSpatial)) {
+                continue;
+            }
+            Spatial sourceSpatial = spatialAssetDataObject.loadAsset();
+            final AnimComposer sourceAnimComposer = findAnimComposer(sourceSpatial, null);
+            if (sourceAnimComposer == null) {
+                DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(
+                        String.format("%s has no AnimComposer.", sourceSpatial),
+                        NotifyDescriptor.ERROR_MESSAGE));
+                return;
+            }
+            copyClips(sourceAnimComposer, targetAnimComposer, targetAnimComposerSpatial.getControl(SkinningControl.class).getArmature());
+        }
+        logger.log(Level.INFO, "Merging animations done. Saving.");
+        try {
+            targetAsset.saveAsset();
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    private void copyClips(final AnimComposer from, final AnimComposer to, Armature toArmature) {
+        final Collection<AnimClip> animClips = from.getAnimClips();
+        for (AnimClip animClip : animClips) {
+            to.addAnimClip(retargetClip(animClip, toArmature, animClip.getName()));
+            logger.log(Level.FINE, String.format("Added anim clip %s", animClip.getName()));
+        }
+    }
+
+    private Confirmation createSelector() {
+        final Object[] spatials = new Object[this.spatials.size() + 1];
+        int index = 0;
+        for (Iterator<SpatialAssetDataObject> it = this.spatials.iterator(); it.hasNext();) {
+            final SpatialAssetDataObject spatialAssetDataObject = it.next();
+            spatials[index++] = spatialAssetDataObject.getName();
+        }
+        spatials[index++] = CANCEL;
+        final NotifyDescriptor.Confirmation message
+                = new NotifyDescriptor.Confirmation(
+                        "Select spatial to copy animations to.");
+        message.setOptions(spatials);
+
+        DialogDisplayer.getDefault().notify(message);
+
+        return message;
+    }
+
+    private AnimComposer findAnimComposer(Spatial spatial, AnimComposer animComposer) {
+        if (spatial.getControl(AnimComposer.class) != null) {
+            return spatial.getControl(AnimComposer.class);
+        }
+        if (animComposer == null && spatial instanceof Node node) {
+            for (Spatial child : node.getChildren()) {
+                animComposer = findAnimComposer(child, animComposer);
+                if (animComposer != null) {
+                    return animComposer;
+                }
+            }
+        }
+        return animComposer;
+    }
+
+    private SpatialAssetDataObject findSpatial(String selected) {
+        for (Iterator<SpatialAssetDataObject> it = spatials.iterator(); it.hasNext();) {
+            final SpatialAssetDataObject spatialAssetDataObject = it.next();
+            if (spatialAssetDataObject.getName().equals(selected)) {
+                return spatialAssetDataObject;
+            }
+        }
+        NotifyDescriptor.Message msg = new NotifyDescriptor.Message(
+                "Main asset to copy to not found. This is likely an issue with the tool itself.",
+                NotifyDescriptor.ERROR_MESSAGE);
+        DialogDisplayer.getDefault().notify(msg);
+        return null;
+    }
+
+    private AnimClip retargetClip(AnimClip sourceClip, Armature targetArmature, String clipName) {
+
+        // Create a list to hold the new tracks
+        SafeArrayList<AnimTrack> tracks = new SafeArrayList<>(AnimTrack.class);
+
+        // Iterate through each track in the source clip
+        for (AnimTrack animTrack : sourceClip.getTracks()) {
+
+            TransformTrack sourceTrack = (TransformTrack) animTrack;
+            String targetName = getTargetName(sourceTrack.getTarget());
+            if (targetName == null) {
+                logger.log(Level.SEVERE, String.format("Unsupported target for: %s. Skipping.", animTrack));
+                continue;
+            }
+            Joint target = targetArmature.getJoint(targetName);
+
+            if (target != null) {
+                // Clone the source track and set the new target joint
+                TransformTrack newTrack = sourceTrack.jmeClone();
+                newTrack.setTarget(target);
+                tracks.add(newTrack);
+
+            } else {
+                logger.log(Level.WARNING, "Joint not found in the target Armature: {0}", targetName);
+            }
+        }
+
+        // Create a new animation clip with the specified name and set its tracks
+        AnimClip newClip = new AnimClip(clipName);
+        newClip.setTracks(tracks.getArray());
+
+        logger.log(Level.INFO, "Created new AnimClip {0} with {1} tracks out of {2} from the source clip",
+                new Object[]{clipName, tracks.size(), sourceClip.getTracks().length});
+        return newClip;
+    }
+
+    private String getTargetName(final HasLocalTransform target) {
+        if (target instanceof Node node) {
+            return node.getName();
+        }
+        if (target instanceof Joint joint) {
+            return joint.getName();
+        }
+        return null;
+    }
+
+}

+ 37 - 51
jme3-core/src/com/jme3/gde/core/sceneexplorer/SceneExplorerTopComponent.java

@@ -1,5 +1,5 @@
 /*
 /*
- *  Copyright (c) 2009-2010 jMonkeyEngine
+ *  Copyright (c) 2009-2024 jMonkeyEngine
  *  All rights reserved.
  *  All rights reserved.
  * 
  * 
  *  Redistribution and use in source and binary forms, with or without
  *  Redistribution and use in source and binary forms, with or without
@@ -31,6 +31,7 @@
  */
  */
 package com.jme3.gde.core.sceneexplorer;
 package com.jme3.gde.core.sceneexplorer;
 
 
+import com.jme3.gde.core.assets.RefreshJmeSpatial;
 import com.jme3.gde.core.icons.IconList;
 import com.jme3.gde.core.icons.IconList;
 import com.jme3.gde.core.scene.PreviewRequest;
 import com.jme3.gde.core.scene.PreviewRequest;
 import com.jme3.gde.core.scene.SceneApplication;
 import com.jme3.gde.core.scene.SceneApplication;
@@ -48,6 +49,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.logging.Logger;
+import javax.swing.SwingUtilities;
 import org.netbeans.api.settings.ConvertAsProperties;
 import org.netbeans.api.settings.ConvertAsProperties;
 import org.openide.actions.CopyAction;
 import org.openide.actions.CopyAction;
 import org.openide.actions.CutAction;
 import org.openide.actions.CutAction;
@@ -77,11 +79,13 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
     private static final Logger logger = Logger.getLogger(SceneExplorerTopComponent.class.getName());
     private static final Logger logger = Logger.getLogger(SceneExplorerTopComponent.class.getName());
     private static SceneExplorerTopComponent instance;
     private static SceneExplorerTopComponent instance;
     private static final String PREFERRED_ID = "SceneExplorerTopComponent";
     private static final String PREFERRED_ID = "SceneExplorerTopComponent";
-//    private final Result<AbstractSceneExplorerNode> nodeSelectionResult;
-    private AbstractSceneExplorerNode selectedSpatial;
-    private AbstractSceneExplorerNode lastSelected;
-    private Map<String, MaterialChangeProvider> materialChangeProviders = new HashMap<String, MaterialChangeProvider>();
-    private Map<String, List<MaterialChangeListener>> materialChangeListeners = new HashMap<String, List<MaterialChangeListener>>();
+
+    private AbstractSceneExplorerNode[] selectedSpatials;
+    private AbstractSceneExplorerNode[] lastSelected;
+    private final Map<String, MaterialChangeProvider> materialChangeProviders = new HashMap<>();
+    private final Map<String, List<MaterialChangeListener>> materialChangeListeners = new HashMap<>();
+    
+    private final transient ExplorerManager explorerManager = new ExplorerManager();
 
 
     public SceneExplorerTopComponent() {
     public SceneExplorerTopComponent() {
         initComponents();
         initComponents();
@@ -90,8 +94,6 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
         setToolTipText(NbBundle.getMessage(SceneExplorerTopComponent.class, "HINT_SceneExplorerTopComponent"));
         setToolTipText(NbBundle.getMessage(SceneExplorerTopComponent.class, "HINT_SceneExplorerTopComponent"));
         setIcon(IconList.jmeLogo.getImage());
         setIcon(IconList.jmeLogo.getImage());
         associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap()));
         associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap()));
-//        nodeSelectionResult = Utilities.actionsGlobalContext().lookupResult(AbstractSceneExplorerNode.class);
-//        nodeSelectionResult.addLookupListener(this);
     }
     }
 
 
     private void initActions() {
     private void initActions() {
@@ -148,10 +150,12 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
     }// </editor-fold>//GEN-END:initComponents
     }// </editor-fold>//GEN-END:initComponents
 
 
     private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed
     private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed
-        if (selectedSpatial == null) {
+        if (selectedSpatials == null) {
             return;
             return;
         }
         }
-        selectedSpatial.refresh(false);
+        for (AbstractSceneExplorerNode node: selectedSpatials) {
+            node.refresh(false);
+        }
     }//GEN-LAST:event_jButton1ActionPerformed
     }//GEN-LAST:event_jButton1ActionPerformed
     // Variables declaration - do not modify//GEN-BEGIN:variables
     // Variables declaration - do not modify//GEN-BEGIN:variables
     private javax.swing.JScrollPane explorerScrollPane;
     private javax.swing.JScrollPane explorerScrollPane;
@@ -164,6 +168,7 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
      * only, i.e. deserialization routines; otherwise you could get a
      * only, i.e. deserialization routines; otherwise you could get a
      * non-deserialized instance. To obtain the singleton instance, use
      * non-deserialized instance. To obtain the singleton instance, use
      * {@link #findInstance}.
      * {@link #findInstance}.
+     * @return 
      */
      */
     public static synchronized SceneExplorerTopComponent getDefault() {
     public static synchronized SceneExplorerTopComponent getDefault() {
         if (instance == null) {
         if (instance == null) {
@@ -175,16 +180,17 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
     /**
     /**
      * Obtain the SceneExplorerTopComponent instance. Never call
      * Obtain the SceneExplorerTopComponent instance. Never call
      * {@link #getDefault} directly!
      * {@link #getDefault} directly!
+     * @return 
      */
      */
     public static synchronized SceneExplorerTopComponent findInstance() {
     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(
             logger.warning(
                     "Cannot find " + PREFERRED_ID + " component. It will not be located properly in the window system.");
                     "Cannot find " + PREFERRED_ID + " component. It will not be located properly in the window system.");
             return getDefault();
             return getDefault();
         }
         }
-        if (win instanceof SceneExplorerTopComponent) {
-            return (SceneExplorerTopComponent) win;
+        if (window instanceof SceneExplorerTopComponent sceneExplorerTopComponent) {
+            return sceneExplorerTopComponent;
         }
         }
         logger.warning(
         logger.warning(
                 "There seem to be multiple components with the '" + PREFERRED_ID
                 "There seem to be multiple components with the '" + PREFERRED_ID
@@ -216,27 +222,20 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
         SceneApplication.getApplication().removeSceneListener(this);
         SceneApplication.getApplication().removeSceneListener(this);
         // TODO add custom code on component closing
         // TODO add custom code on component closing
     }
     }
-
+    
     void writeProperties(java.util.Properties p) {
     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");
         p.setProperty("version", "1.0");
-        // TODO store your settings
     }
     }
-
+    
     Object readProperties(java.util.Properties p) {
     Object readProperties(java.util.Properties p) {
+        // Required. Do not remove.
         if (instance == null) {
         if (instance == null) {
             instance = this;
             instance = this;
         }
         }
-        instance.readPropertiesImpl(p);
         return instance;
         return instance;
     }
     }
 
 
-    private void readPropertiesImpl(java.util.Properties p) {
-        String version = p.getProperty("version");
-        // TODO read your settings according to their version
-    }
-
     @Override
     @Override
     protected String preferredID() {
     protected String preferredID() {
         return PREFERRED_ID;
         return PREFERRED_ID;
@@ -246,46 +245,32 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
     public UndoRedo getUndoRedo() {
     public UndoRedo getUndoRedo() {
         return Lookup.getDefault().lookup(UndoRedo.class);
         return Lookup.getDefault().lookup(UndoRedo.class);
     }
     }
-    private transient ExplorerManager explorerManager = new ExplorerManager();
 
 
     @Override
     @Override
     public ExplorerManager getExplorerManager() {
     public ExplorerManager getExplorerManager() {
         return explorerManager;
         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 {
         try {
-            if (node != null) {
-                explorerManager.setSelectedNodes(new Node[]{node});
-//                setActivatedNodes(new Node[]{node});
+            if (nodes != null) {
+                explorerManager.setSelectedNodes(nodes);
             } else {
             } else {
                 explorerManager.setSelectedNodes(new Node[]{});
                 explorerManager.setSelectedNodes(new Node[]{});
-//                setActivatedNodes(new Node[]{});
             }
             }
-        } catch (Exception ex) {
+        } catch (PropertyVetoException ex) {
             Exceptions.printStackTrace(ex);
             Exceptions.printStackTrace(ex);
         }
         }
     }
     }
 
 
-//    public void resultChanged(LookupEvent ev) {
-//        Collection collection = nodeSelectionResult.allInstances();
-//        for (Iterator it = collection.iterator(); it.hasNext();) {
-//            Object object = it.next();
-//            if (object instanceof AbstractSceneExplorerNode) {
-//                return;
-//            }
-//        }
-//        selectedSpatial = null;
-//    }
     @Override
     @Override
     public void sceneOpened(SceneRequest request) {
     public void sceneOpened(SceneRequest request) {
         final JmeNode node = request.getJmeNode();
         final JmeNode node = request.getJmeNode();
-        for (Iterator it = materialChangeProviders.values().iterator(); it.hasNext();) {
-            MaterialChangeProvider provider = (MaterialChangeProvider) it.next();
+        for (MaterialChangeProvider provider : materialChangeProviders.values()) {
             provider.clearMaterialChangeListeners();
             provider.clearMaterialChangeListeners();
         }
         }
         if (node != null) {
         if (node != null) {
@@ -310,11 +295,11 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
     @Override
     @Override
     public void previewCreated(PreviewRequest request) {
     public void previewCreated(PreviewRequest request) {
     }
     }
-
+    
     /**
     /**
      * @return the selectedSpatial
      * @return the selectedSpatial
      */
      */
-    public AbstractSceneExplorerNode getLastSelected() {
+    public AbstractSceneExplorerNode[] getLastSelected() {
         return lastSelected;
         return lastSelected;
     }
     }
 
 
@@ -339,7 +324,7 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
             logger.log(Level.FINE, "New material listener for : {0}", listener.getKey());
             logger.log(Level.FINE, "New material listener for : {0}", listener.getKey());
             List<MaterialChangeListener> listeners = materialChangeListeners.get(listener.getKey());
             List<MaterialChangeListener> listeners = materialChangeListeners.get(listener.getKey());
             if (listeners == null) {
             if (listeners == null) {
-                listeners = new ArrayList<MaterialChangeListener>();
+                listeners = new ArrayList<>();
                 materialChangeListeners.put(listener.getKey(), listeners);
                 materialChangeListeners.put(listener.getKey(), listeners);
             }
             }
             listeners.add(listener);
             listeners.add(listener);
@@ -383,7 +368,7 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
             //  assert newKey.equals(listener.getKey());
             //  assert newKey.equals(listener.getKey());
             List<MaterialChangeListener> listeners = materialChangeListeners.get(newKey);
             List<MaterialChangeListener> listeners = materialChangeListeners.get(newKey);
             if (listeners == null) {
             if (listeners == null) {
-                listeners = new ArrayList<MaterialChangeListener>();
+                listeners = new ArrayList<>();
                 materialChangeListeners.put(newKey, listeners);
                 materialChangeListeners.put(newKey, listeners);
             }
             }
             listeners.add(listener);
             listeners.add(listener);
@@ -397,6 +382,7 @@ public final class SceneExplorerTopComponent extends TopComponent implements Exp
 
 
     /**
     /**
      * Terrain has a LOD control that requires the camera to function.
      * Terrain has a LOD control that requires the camera to function.
+     * @param jmeRootNode
      */
      */
     protected void setTerrainLodCamera(JmeNode jmeRootNode) {
     protected void setTerrainLodCamera(JmeNode jmeRootNode) {
         Camera camera = SceneApplication.getApplication().getCamera();
         Camera camera = SceneApplication.getApplication().getCamera();

+ 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.
  *  All rights reserved.
  * 
  * 
  *  Redistribution and use in source and binary forms, with or without
  *  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;
                 Vector3f pos;
                 
                 
                 SceneToolController controller = SceneApplication.getApplication().getStateManager().getState(SceneToolController.class);
                 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
                     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 {
                 } 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 {
                     } 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);
                     }
                     }
                 }
                 }
                 
                 

+ 83 - 61
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/actions/impl/NewCustomControlVisualPanel1.java

@@ -31,9 +31,11 @@
  */
  */
 package com.jme3.gde.core.sceneexplorer.nodes.actions.impl;
 package com.jme3.gde.core.sceneexplorer.nodes.actions.impl;
 
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.EnumSet;
 import java.util.EnumSet;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.List;
 import java.util.Set;
 import java.util.Set;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.TypeElement;
@@ -53,8 +55,10 @@ import org.netbeans.api.java.source.JavaSource;
 import org.netbeans.api.java.source.JavaSource.Phase;
 import org.netbeans.api.java.source.JavaSource.Phase;
 import org.netbeans.api.java.source.Task;
 import org.netbeans.api.java.source.Task;
 import org.netbeans.api.project.Project;
 import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectUtils;
 import org.netbeans.api.project.SourceGroup;
 import org.netbeans.api.project.SourceGroup;
 import org.netbeans.api.project.Sources;
 import org.netbeans.api.project.Sources;
+import org.openide.filesystems.FileObject;
 import org.openide.util.Exceptions;
 import org.openide.util.Exceptions;
 
 
 @SuppressWarnings({"unchecked", "rawtypes"})
 @SuppressWarnings({"unchecked", "rawtypes"})
@@ -84,74 +88,92 @@ public final class NewCustomControlVisualPanel1 extends JPanel {
     }
     }
 
 
     private List<String> getSources() {
     private List<String> getSources() {
-        Sources sources = proj.getLookup().lookup(Sources.class);
-        final List<String> list = new LinkedList<String>();
-        if (sources != null) {
+        Project root = ProjectUtils.rootOf(proj);
+        Set<Project> containedProjects = ProjectUtils.getContainedProjects(root, true);
+        List<Project> projects = new ArrayList<>();
+        projects.add(root);
+        if (containedProjects != null) {
+            projects.addAll(containedProjects);
+        }
+        if (projects.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        Set<String> list = new HashSet<>();
+        for (Project project : projects) {
+            Sources sources = project.getLookup().lookup(Sources.class);
+            if (sources == null) {
+                continue;
+            }
+
             SourceGroup[] groups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
             SourceGroup[] groups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
-            if (groups != null) {
-                for (SourceGroup sourceGroup : groups) {
-                    final ClasspathInfo cpInfo = ClasspathInfo.create(ClassPath.getClassPath(sourceGroup.getRootFolder(), ClassPath.BOOT),
-                            ClassPath.getClassPath(sourceGroup.getRootFolder(), ClassPath.COMPILE),
-                            ClassPath.getClassPath(sourceGroup.getRootFolder(), ClassPath.SOURCE));
-
-                    Set<SearchScope> set = EnumSet.of(ClassIndex.SearchScope.SOURCE);
-                    Set<ElementHandle<TypeElement>> types = cpInfo.getClassIndex().getDeclaredTypes("", NameKind.PREFIX, set);
-                    for (Iterator<ElementHandle<TypeElement>> it = types.iterator(); it.hasNext();) {
-                        final ElementHandle<TypeElement> elementHandle = it.next();
-                        JavaSource js = JavaSource.create(cpInfo);
-                        try {
-                            js.runUserActionTask(new Task<CompilationController>() {
-                                @Override
-                                public void run(CompilationController control)
-                                        throws Exception {
-                                    control.toPhase(Phase.RESOLVED);
-                                    //TODO: check with proper casting check.. gotta get TypeMirror of Control interface..
-//                                    TypeUtilities util = control.getTypeUtilities();//.isCastable(Types., null)
-//                                    util.isCastable(null, null);
-                                    TypeElement elem = elementHandle.resolve(control);
-                                    if (elem == null)
-                                        return;
-                                    
-                                    String elementName = elem.getQualifiedName().toString();
-                                    
-                                    if (list.contains(elementName)) /* No duplicates */
-                                        return;
-                                    
-                                    do {
-                                        //Check if it implements control interface
-                                        for (TypeMirror typeMirror : elem.getInterfaces()) {
-                                            String interfaceName = typeMirror.toString();
-                                            if ("com.jme3.scene.control.Control".equals(interfaceName)) {
-                                                if (!list.contains(elementName))
-                                                    list.add(elementName);
-                                                break;
-                                            }
-                                        }
-                                        //Check if it is an AbstractControl
-                                        String className = elem.toString();
-                                        if ("com.jme3.scene.control.AbstractControl".equals(className)) {
-                                            if (!list.contains(elementName))
-                                                list.add(elementName);
-                                        }
+            if (groups == null) {
+                continue;
+            }
 
 
-                                        TypeMirror superClass = elem.getSuperclass();
-                                        if (superClass == null || superClass.getKind() == TypeKind.NONE) {
-                                            break;
-                                        }
+            for (SourceGroup sourceGroup : groups) {
+                FileObject rootFolder = sourceGroup.getRootFolder();
+                final ClasspathInfo cpInfo = ClasspathInfo.create(ClassPath.getClassPath(rootFolder, ClassPath.BOOT),
+                        ClassPath.getClassPath(rootFolder, ClassPath.COMPILE),
+                        ClassPath.getClassPath(rootFolder, ClassPath.SOURCE));
+
+                Set<SearchScope> set = EnumSet.of(ClassIndex.SearchScope.SOURCE);
+                Set<ElementHandle<TypeElement>> types = cpInfo.getClassIndex().getDeclaredTypes("", NameKind.PREFIX, set);
+                for (Iterator<ElementHandle<TypeElement>> it = types.iterator(); it.hasNext();) {
+                    final ElementHandle<TypeElement> elementHandle = it.next();
+                    JavaSource js = JavaSource.create(cpInfo);
+                    try {
+                        js.runUserActionTask(new Task<CompilationController>() {
+                            @Override
+                            public void run(CompilationController control)
+                                    throws Exception {
+                                control.toPhase(Phase.RESOLVED);
+                                //TODO: check with proper casting check.. gotta get TypeMirror of Control interface..
+                                //                                    TypeUtilities util = control.getTypeUtilities();//.isCastable(Types., null)
+                                //                                    util.isCastable(null, null);
+                                TypeElement elem = elementHandle.resolve(control);
+                                if (elem == null) {
+                                    return;
+                                }
 
 
-                                        elem = (TypeElement)((DeclaredType)superClass).asElement(); // Iterate deeper
-                                    } while (elem != null);
+                                String elementName = elem.getQualifiedName().toString();
+
+                                if (list.contains(elementName)) /* No duplicates */ {
+                                    return;
                                 }
                                 }
-                            }, false);
-                        } catch (Exception ioe) {
-                            Exceptions.printStackTrace(ioe);
-                        }
-                    }
 
 
+                                do {
+                                    //Check if it implements control interface
+                                    for (TypeMirror typeMirror : elem.getInterfaces()) {
+                                        String interfaceName = typeMirror.toString();
+                                        if ("com.jme3.scene.control.Control".equals(interfaceName) && !list.contains(elementName)) {
+                                            list.add(elementName);
+                                            break;
+                                        }
+                                    }
+                                    //Check if it is an AbstractControl
+                                    String className = elem.toString();
+                                    if ("com.jme3.scene.control.AbstractControl".equals(className) && !list.contains(elementName)) {
+                                        list.add(elementName);
+                                    }
+
+                                    TypeMirror superClass = elem.getSuperclass();
+                                    if (superClass == null || superClass.getKind() == TypeKind.NONE) {
+                                        break;
+                                    }
+
+                                    elem = (TypeElement) ((DeclaredType) superClass).asElement(); // Iterate deeper
+                                } while (elem != null);
+                            }
+                        }, false);
+                    } catch (Exception ioe) {
+                        Exceptions.printStackTrace(ioe);
+                    }
                 }
                 }
             }
             }
         }
         }
-        return list;
+
+        return new ArrayList<>(list);
     }
     }
 
 
     public void load(Project proj) {
     public void load(Project proj) {

+ 9 - 0
jme3-glsl-highlighter/nbproject/project.xml

@@ -50,6 +50,15 @@
                         <specification-version>1.39.1.55</specification-version>
                         <specification-version>1.39.1.55</specification-version>
                     </run-dependency>
                     </run-dependency>
                 </dependency>
                 </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.editor.completion</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.71.0.2</specification-version>
+                    </run-dependency>
+                </dependency>
                 <dependency>
                 <dependency>
                     <code-name-base>org.netbeans.modules.editor.indent</code-name-base>
                     <code-name-base>org.netbeans.modules.editor.indent</code-name-base>
                     <build-prerequisite/>
                     <build-prerequisite/>

+ 150 - 0
jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/GlslCompletionProvider.java

@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2003-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.glsl.highlighter.editor;
+
+import com.jme3.gde.glsl.highlighter.editor.completion.FunctionCompletionItem;
+import com.jme3.gde.glsl.highlighter.editor.completion.KeywordCompletionItem;
+import com.jme3.gde.glsl.highlighter.editor.completion.TypeCompletionItem;
+import com.jme3.gde.glsl.highlighter.editor.completion.VariableCompletionItem;
+import com.jme3.gde.glsl.highlighter.lexer.GlslKeywordLibrary;
+import java.util.List;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.JTextComponent;
+import javax.swing.text.StyledDocument;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.spi.editor.completion.CompletionItem;
+import org.netbeans.spi.editor.completion.CompletionProvider;
+import org.netbeans.spi.editor.completion.CompletionResultSet;
+import org.netbeans.spi.editor.completion.CompletionTask;
+import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery;
+import org.netbeans.spi.editor.completion.support.AsyncCompletionTask;
+import org.openide.util.Exceptions;
+
+@MimeRegistration(mimeType = "text/x-glsl", service = CompletionProvider.class)
+public class GlslCompletionProvider implements CompletionProvider {
+
+    @Override
+    public CompletionTask createTask(int queryType, JTextComponent component) {
+        if (queryType != CompletionProvider.COMPLETION_QUERY_TYPE) {
+            return null;
+        }
+
+        return new AsyncCompletionTask(new AsyncCompletionQuery() {
+            @Override
+            protected void query(CompletionResultSet completionResultSet,
+                    Document document, int caretOffset) {
+
+                String filter = "";
+                int startOffset = caretOffset - 1;
+
+                try {
+                    final StyledDocument bDoc = (StyledDocument) document;
+                    final int lineStartOffset = getRowFirstNonWhite(bDoc, caretOffset);
+                    final char[] line = bDoc.getText(lineStartOffset, caretOffset - lineStartOffset).toCharArray();
+                    final int whiteOffset = indexOfWhite(line);
+                    filter = new String(line, whiteOffset + 1, line.length - whiteOffset - 1);
+                    if (whiteOffset > 0) {
+                        startOffset = lineStartOffset + whiteOffset + 1;
+                    } else {
+                        startOffset = lineStartOffset;
+                    }
+                } catch (BadLocationException ex) {
+                    Exceptions.printStackTrace(ex);
+                }
+
+                setCompletionItems(filter, completionResultSet, startOffset, caretOffset);
+
+                completionResultSet.finish();
+            }
+
+            private void setCompletionItems(String filter, CompletionResultSet completionResultSet, int startOffset, int caretOffset) {
+                List<GlslKeywordLibrary.Keyword> keywords = GlslKeywordLibrary.lookupAll(filter);
+                completionResultSet.addAllItems(keywords.stream().map((keyword) -> createCompletionItem(keyword, startOffset, caretOffset)).toList());
+            }
+
+            private CompletionItem createCompletionItem(GlslKeywordLibrary.Keyword keyword, int dotOffset, int caretOffset) {
+                return switch (keyword.keywordType()) {
+                    case KEYWORD ->
+                        new KeywordCompletionItem(keyword.keyword(), dotOffset, caretOffset);
+                    case BUILTIN_FUNCTION ->
+                        new FunctionCompletionItem(keyword.keyword(), dotOffset, caretOffset);
+                    case BUILTIN_VARIABLE ->
+                        new VariableCompletionItem(keyword.keyword(), dotOffset, caretOffset);
+                    case BASIC_TYPE ->
+                        new TypeCompletionItem(keyword.keyword(), dotOffset, caretOffset);
+                    case UNFINISHED ->
+                        throw new AssertionError("Keyword type invalid");
+                    default ->
+                        throw new AssertionError("Keyword type not implemented");
+                };
+            }
+        }, component);
+    }
+
+    private static int getRowFirstNonWhite(StyledDocument doc, int offset)
+            throws BadLocationException {
+        Element lineElement = doc.getParagraphElement(offset);
+        int start = lineElement.getStartOffset();
+        while (start + 1 < lineElement.getEndOffset()) {
+            try {
+                if (doc.getText(start, 1).charAt(0) != ' ') {
+                    break;
+                }
+            } catch (BadLocationException ex) {
+                throw (BadLocationException) new BadLocationException(
+                        "calling getText(" + start + ", " + (start + 1)
+                        + ") on doc of length: " + doc.getLength(), start
+                ).initCause(ex);
+            }
+            start++;
+        }
+        return start;
+    }
+
+    private static int indexOfWhite(char[] line) {
+        int i = line.length;
+        while (--i > -1) {
+            final char c = line[i];
+            if (Character.isWhitespace(c)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public int getAutoQueryTypes(JTextComponent jtc, String string) {
+        return 0;
+    }
+}

+ 123 - 0
jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/DefaultCompletionItem.java

@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2003-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.glsl.highlighter.editor.completion;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.event.KeyEvent;
+import javax.swing.ImageIcon;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.JTextComponent;
+import javax.swing.text.StyledDocument;
+import org.netbeans.api.editor.completion.Completion;
+import org.netbeans.spi.editor.completion.CompletionItem;
+import org.netbeans.spi.editor.completion.CompletionTask;
+import org.netbeans.spi.editor.completion.support.CompletionUtilities;
+import org.openide.util.Exceptions;
+
+public abstract class DefaultCompletionItem implements CompletionItem {
+
+    private final String keyword;
+    private final int caretOffset;
+    private final int dotOffset;
+
+    public DefaultCompletionItem(String keyword, int dotOffset, int caretOffset) {
+        this.keyword = keyword;
+        this.dotOffset = dotOffset;
+        this.caretOffset = caretOffset;
+    }
+
+    @Override
+    public void defaultAction(JTextComponent component) {
+        try {
+            StyledDocument doc = (StyledDocument) component.getDocument();
+            //Here we remove the characters starting at the start offset
+            //and ending at the point where the caret is currently found:
+            doc.remove(dotOffset, caretOffset - dotOffset);
+            doc.insertString(dotOffset, keyword, null);
+            Completion.get().hideAll();
+        } catch (BadLocationException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public void processKeyEvent(KeyEvent ke) {
+
+    }
+
+    @Override
+    public int getPreferredWidth(Graphics graphics, Font font) {
+        return CompletionUtilities.getPreferredWidth(keyword, null, graphics, font);
+    }
+
+    @Override
+    public void render(Graphics graphics, Font font, Color defaultColor,
+            Color backgroundColor, int width, int height, boolean selected) {
+        CompletionUtilities.renderHtml(getIcon(), keyword, null, graphics, font,
+                (selected ? Color.white : null), width, height, selected);
+    }
+
+    @Override
+    public CompletionTask createDocumentationTask() {
+        return null;
+    }
+
+    @Override
+    public CompletionTask createToolTipTask() {
+        return null;
+    }
+
+    @Override
+    public boolean instantSubstitution(JTextComponent jtc) {
+        return false;
+    }
+
+    @Override
+    public int getSortPriority() {
+        return 0;
+    }
+
+    @Override
+    public CharSequence getSortText() {
+        return keyword;
+    }
+
+    @Override
+    public CharSequence getInsertPrefix() {
+        return keyword;
+    }
+
+    protected abstract ImageIcon getIcon();
+
+}

+ 50 - 0
jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/FunctionCompletionItem.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2003-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.glsl.highlighter.editor.completion;
+
+import javax.swing.ImageIcon;
+import org.openide.util.ImageUtilities;
+
+public class FunctionCompletionItem extends DefaultCompletionItem {
+
+    private static final ImageIcon fieldIcon
+            = new ImageIcon(ImageUtilities.loadImage("com/jme3/gde/glsl/highlighter/editor/completion/method_16.png"));
+
+    public FunctionCompletionItem(String keyword, int dotOffset, int caretOffset) {
+        super(keyword, dotOffset, caretOffset);
+    }
+
+    @Override
+    protected ImageIcon getIcon() {
+        return fieldIcon;
+    }
+}

+ 50 - 0
jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/KeywordCompletionItem.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2003-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.glsl.highlighter.editor.completion;
+
+import javax.swing.ImageIcon;
+import org.openide.util.ImageUtilities;
+
+public class KeywordCompletionItem extends DefaultCompletionItem {
+
+    private static final ImageIcon fieldIcon
+            = new ImageIcon(ImageUtilities.loadImage("com/jme3/gde/glsl/highlighter/editor/completion/method_static_protected_16.png"));
+
+    public KeywordCompletionItem(String keyword, int dotOffset, int caretOffset) {
+        super(keyword, dotOffset, caretOffset);
+    }
+
+    @Override
+    protected ImageIcon getIcon() {
+        return fieldIcon;
+    }
+}

+ 50 - 0
jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/TypeCompletionItem.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2003-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.glsl.highlighter.editor.completion;
+
+import javax.swing.ImageIcon;
+import org.openide.util.ImageUtilities;
+
+public class TypeCompletionItem extends DefaultCompletionItem {
+
+    private static final ImageIcon fieldIcon
+            = new ImageIcon(ImageUtilities.loadImage("com/jme3/gde/glsl/highlighter/editor/completion/class_16.png"));
+
+    public TypeCompletionItem(String keyword, int dotOffset, int caretOffset) {
+        super(keyword, dotOffset, caretOffset);
+    }
+
+    @Override
+    protected ImageIcon getIcon() {
+        return fieldIcon;
+    }
+}

+ 50 - 0
jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/VariableCompletionItem.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2003-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.glsl.highlighter.editor.completion;
+
+import javax.swing.ImageIcon;
+import org.openide.util.ImageUtilities;
+
+public class VariableCompletionItem extends DefaultCompletionItem {
+
+    private static final ImageIcon fieldIcon
+            = new ImageIcon(ImageUtilities.loadImage("com/jme3/gde/glsl/highlighter/editor/completion/field_16.png"));
+
+    public VariableCompletionItem(String keyword, int dotOffset, int caretOffset) {
+        super(keyword, dotOffset, caretOffset);
+    }
+
+    @Override
+    protected ImageIcon getIcon() {
+        return fieldIcon;
+    }
+}

BIN
jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/class_16.png


BIN
jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/field_16.png


BIN
jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/method_16.png


BIN
jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/editor/completion/method_static_protected_16.png


+ 459 - 447
jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/lexer/GlslKeywordLibrary.java

@@ -31,6 +31,7 @@
  */
  */
 package com.jme3.gde.glsl.highlighter.lexer;
 package com.jme3.gde.glsl.highlighter.lexer;
 
 
+import com.jme3.gde.glsl.highlighter.util.Trie;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
 
 
@@ -40,477 +41,488 @@ import java.util.List;
  *
  *
  * @author grizeldi
  * @author grizeldi
  */
  */
-class GlslKeywordLibrary {
-
+public final class GlslKeywordLibrary {
+    
     public enum KeywordType {
     public enum KeywordType {
         KEYWORD, BUILTIN_FUNCTION, BUILTIN_VARIABLE, BASIC_TYPE, UNFINISHED;
         KEYWORD, BUILTIN_FUNCTION, BUILTIN_VARIABLE, BASIC_TYPE, UNFINISHED;
     }
     }
-    private static final List<String> keywords = new ArrayList<>(),
-            builtinFunctions = new ArrayList<>(),
-            builtinVariables = new ArrayList<>(),
-            basicTypes = new ArrayList<>();
+
+    public record Keyword(String keyword, KeywordType keywordType) {
+
+    }
+    
+    private static final Trie keywords = new Trie();
+    private static final Trie builtinFunctions = new Trie();
+    private static final Trie builtinVariables = new Trie();
+    private static final Trie basicTypes = new Trie();
 
 
     static {
     static {
         //keywords
         //keywords
-        keywords.add("attribute");
-        keywords.add("const");
-        keywords.add("uniform");
-        keywords.add("varying");
-        keywords.add("buffer");
-        keywords.add("shared");
-        keywords.add("coherent");
-        keywords.add("volatile");
-        keywords.add("restrict");
-        keywords.add("readonly");
-        keywords.add("writeonly");
-        keywords.add("atomic_uint");
-        keywords.add("layout");
-        keywords.add("centroid");
-        keywords.add("flat");
-        keywords.add("smooth");
-        keywords.add("noperspective");
-        keywords.add("patch");
-        keywords.add("sample");
-        keywords.add("break");
-        keywords.add("continue");
-        keywords.add("do");
-        keywords.add("for");
-        keywords.add("while");
-        keywords.add("switch");
-        keywords.add("case");
-        keywords.add("default");
-        keywords.add("if");
-        keywords.add("else");
-        keywords.add("subroutine");
-        keywords.add("in");
-        keywords.add("out");
-        keywords.add("inout");
-        keywords.add("void");
-        keywords.add("true");
-        keywords.add("false");
-        keywords.add("invariant");
-        keywords.add("precise");
-        keywords.add("discard");
-        keywords.add("return");
+        keywords.insert("attribute");
+        keywords.insert("const");
+        keywords.insert("uniform");
+        keywords.insert("varying");
+        keywords.insert("buffer");
+        keywords.insert("shared");
+        keywords.insert("coherent");
+        keywords.insert("volatile");
+        keywords.insert("restrict");
+        keywords.insert("readonly");
+        keywords.insert("writeonly");
+        keywords.insert("atomic_uint");
+        keywords.insert("layout");
+        keywords.insert("centroid");
+        keywords.insert("flat");
+        keywords.insert("smooth");
+        keywords.insert("noperspective");
+        keywords.insert("patch");
+        keywords.insert("sample");
+        keywords.insert("break");
+        keywords.insert("continue");
+        keywords.insert("do");
+        keywords.insert("for");
+        keywords.insert("while");
+        keywords.insert("switch");
+        keywords.insert("case");
+        keywords.insert("default");
+        keywords.insert("if");
+        keywords.insert("else");
+        keywords.insert("subroutine");
+        keywords.insert("in");
+        keywords.insert("out");
+        keywords.insert("inout");
+        keywords.insert("void");
+        keywords.insert("true");
+        keywords.insert("false");
+        keywords.insert("invariant");
+        keywords.insert("precise");
+        keywords.insert("discard");
+        keywords.insert("return");
         //primitives and other types
         //primitives and other types
-        basicTypes.add("float");
-        basicTypes.add("double");
-        basicTypes.add("int");
-        basicTypes.add("bool");
-        basicTypes.add("mat2");
-        basicTypes.add("mat3");
-        basicTypes.add("mat4");
-        basicTypes.add("dmat2");
-        basicTypes.add("dmat3");
-        basicTypes.add("dmat4");
-        basicTypes.add("mat2x2");
-        basicTypes.add("mat2x3");
-        basicTypes.add("mat2x4");
-        basicTypes.add("dmat2x2");
-        basicTypes.add("dmat2x3");
-        basicTypes.add("dmat2x4");
-        basicTypes.add("mat3x2");
-        basicTypes.add("mat3x3");
-        basicTypes.add("mat3x4");
-        basicTypes.add("dmat3x2");
-        basicTypes.add("dmat3x3");
-        basicTypes.add("dmat3x4");
-        basicTypes.add("mat4x2");
-        basicTypes.add("mat4x3");
-        basicTypes.add("mat4x4");
-        basicTypes.add("dmat4x2");
-        basicTypes.add("dmat4x3");
-        basicTypes.add("dmat4x4");
-        basicTypes.add("vec2");
-        basicTypes.add("vec3");
-        basicTypes.add("vec4");
-        basicTypes.add("ivec2");
-        basicTypes.add("ivec3");
-        basicTypes.add("ivec4");
-        basicTypes.add("bvec2");
-        basicTypes.add("bvec3");
-        basicTypes.add("bvec4");
-        basicTypes.add("dvec2");
-        basicTypes.add("dvec3");
-        basicTypes.add("dvec4");
-        basicTypes.add("uint");
-        basicTypes.add("uvec2");
-        basicTypes.add("uvec3");
-        basicTypes.add("uvec4");
-        basicTypes.add("lowp");
-        basicTypes.add("mediump");
-        basicTypes.add("highp");
-        basicTypes.add("precision");
-        basicTypes.add("sampler1D");
-        basicTypes.add("sampler2D");
-        basicTypes.add("sampler3D");
-        basicTypes.add("samplerCube");
-        basicTypes.add("sampler1DShadow");
-        basicTypes.add("sampler2DShadow");
-        basicTypes.add("samplerCubeShadow");
-        basicTypes.add("sampler1DArray");
-        basicTypes.add("sampler2DArray");
-        basicTypes.add("sampler1DArrayShadow");
-        basicTypes.add("sampler2DArrayShadow");
-        basicTypes.add("isampler1D");
-        basicTypes.add("isampler2D");
-        basicTypes.add("isampler3D");
-        basicTypes.add("isamplerCube");
-        basicTypes.add("isampler1DArray");
-        basicTypes.add("isampler2DArray");
-        basicTypes.add("usampler1D");
-        basicTypes.add("usampler2D");
-        basicTypes.add("usampler3D");
-        basicTypes.add("usamplerCube");
-        basicTypes.add("usampler1DArray");
-        basicTypes.add("usampler2DArray");
-        basicTypes.add("sampler2DRect");
-        basicTypes.add("sampler2DRectShadow");
-        basicTypes.add("isampler2DRect");
-        basicTypes.add("usampler2DRect");
-        basicTypes.add("samplerBuffer");
-        basicTypes.add("isamplerBuffer");
-        basicTypes.add("usamplerBuffer");
-        basicTypes.add("sampler2DMS");
-        basicTypes.add("isampler2DMS");
-        basicTypes.add("usampler2DMS");
-        basicTypes.add("sampler2DMSArray");
-        basicTypes.add("isampler2DMSArray");
-        basicTypes.add("usampler2DMSArray");
-        basicTypes.add("samplerCubeArray");
-        basicTypes.add("samplerCubeArrayShadow");
-        basicTypes.add("isamplerCubeArray");
-        basicTypes.add("usamplerCubeArray");
-        basicTypes.add("image1D");
-        basicTypes.add("iimage1D");
-        basicTypes.add("uimage1D");
-        basicTypes.add("image2D");
-        basicTypes.add("iimage2D");
-        basicTypes.add("uimage2D");
-        basicTypes.add("image3D");
-        basicTypes.add("iimage3D");
-        basicTypes.add("uimage3D");
-        basicTypes.add("image2DRect");
-        basicTypes.add("iimage2DRect");
-        basicTypes.add("uimage2DRect");
-        basicTypes.add("imageCube");
-        basicTypes.add("iimageCube");
-        basicTypes.add("uimageCube");
-        basicTypes.add("imageBuffer");
-        basicTypes.add("iimageBuffer");
-        basicTypes.add("uimageBuffer");
-        basicTypes.add("image1DArray");
-        basicTypes.add("iimage1DArray");
-        basicTypes.add("uimage1DArray");
-        basicTypes.add("image2DArray");
-        basicTypes.add("iimage2DArray");
-        basicTypes.add("uimage2DArray");
-        basicTypes.add("imageCubeArray");
-        basicTypes.add("iimageCubeArray");
-        basicTypes.add("uimageCubeArray");
-        basicTypes.add("image2DMS");
-        basicTypes.add("iimage2DMS");
-        basicTypes.add("uimage2DMS");
-        basicTypes.add("image2DMSArray");
-        basicTypes.add("iimage2DMSArray");
-        basicTypes.add("uimage2DMSArray");
-        basicTypes.add("struct");
+        basicTypes.insert("float");
+        basicTypes.insert("double");
+        basicTypes.insert("int");
+        basicTypes.insert("bool");
+        basicTypes.insert("mat2");
+        basicTypes.insert("mat3");
+        basicTypes.insert("mat4");
+        basicTypes.insert("dmat2");
+        basicTypes.insert("dmat3");
+        basicTypes.insert("dmat4");
+        basicTypes.insert("mat2x2");
+        basicTypes.insert("mat2x3");
+        basicTypes.insert("mat2x4");
+        basicTypes.insert("dmat2x2");
+        basicTypes.insert("dmat2x3");
+        basicTypes.insert("dmat2x4");
+        basicTypes.insert("mat3x2");
+        basicTypes.insert("mat3x3");
+        basicTypes.insert("mat3x4");
+        basicTypes.insert("dmat3x2");
+        basicTypes.insert("dmat3x3");
+        basicTypes.insert("dmat3x4");
+        basicTypes.insert("mat4x2");
+        basicTypes.insert("mat4x3");
+        basicTypes.insert("mat4x4");
+        basicTypes.insert("dmat4x2");
+        basicTypes.insert("dmat4x3");
+        basicTypes.insert("dmat4x4");
+        basicTypes.insert("vec2");
+        basicTypes.insert("vec3");
+        basicTypes.insert("vec4");
+        basicTypes.insert("ivec2");
+        basicTypes.insert("ivec3");
+        basicTypes.insert("ivec4");
+        basicTypes.insert("bvec2");
+        basicTypes.insert("bvec3");
+        basicTypes.insert("bvec4");
+        basicTypes.insert("dvec2");
+        basicTypes.insert("dvec3");
+        basicTypes.insert("dvec4");
+        basicTypes.insert("uint");
+        basicTypes.insert("uvec2");
+        basicTypes.insert("uvec3");
+        basicTypes.insert("uvec4");
+        basicTypes.insert("lowp");
+        basicTypes.insert("mediump");
+        basicTypes.insert("highp");
+        basicTypes.insert("precision");
+        basicTypes.insert("sampler1D");
+        basicTypes.insert("sampler2D");
+        basicTypes.insert("sampler3D");
+        basicTypes.insert("samplerCube");
+        basicTypes.insert("sampler1DShadow");
+        basicTypes.insert("sampler2DShadow");
+        basicTypes.insert("samplerCubeShadow");
+        basicTypes.insert("sampler1DArray");
+        basicTypes.insert("sampler2DArray");
+        basicTypes.insert("sampler1DArrayShadow");
+        basicTypes.insert("sampler2DArrayShadow");
+        basicTypes.insert("isampler1D");
+        basicTypes.insert("isampler2D");
+        basicTypes.insert("isampler3D");
+        basicTypes.insert("isamplerCube");
+        basicTypes.insert("isampler1DArray");
+        basicTypes.insert("isampler2DArray");
+        basicTypes.insert("usampler1D");
+        basicTypes.insert("usampler2D");
+        basicTypes.insert("usampler3D");
+        basicTypes.insert("usamplerCube");
+        basicTypes.insert("usampler1DArray");
+        basicTypes.insert("usampler2DArray");
+        basicTypes.insert("sampler2DRect");
+        basicTypes.insert("sampler2DRectShadow");
+        basicTypes.insert("isampler2DRect");
+        basicTypes.insert("usampler2DRect");
+        basicTypes.insert("samplerBuffer");
+        basicTypes.insert("isamplerBuffer");
+        basicTypes.insert("usamplerBuffer");
+        basicTypes.insert("sampler2DMS");
+        basicTypes.insert("isampler2DMS");
+        basicTypes.insert("usampler2DMS");
+        basicTypes.insert("sampler2DMSArray");
+        basicTypes.insert("isampler2DMSArray");
+        basicTypes.insert("usampler2DMSArray");
+        basicTypes.insert("samplerCubeArray");
+        basicTypes.insert("samplerCubeArrayShadow");
+        basicTypes.insert("isamplerCubeArray");
+        basicTypes.insert("usamplerCubeArray");
+        basicTypes.insert("image1D");
+        basicTypes.insert("iimage1D");
+        basicTypes.insert("uimage1D");
+        basicTypes.insert("image2D");
+        basicTypes.insert("iimage2D");
+        basicTypes.insert("uimage2D");
+        basicTypes.insert("image3D");
+        basicTypes.insert("iimage3D");
+        basicTypes.insert("uimage3D");
+        basicTypes.insert("image2DRect");
+        basicTypes.insert("iimage2DRect");
+        basicTypes.insert("uimage2DRect");
+        basicTypes.insert("imageCube");
+        basicTypes.insert("iimageCube");
+        basicTypes.insert("uimageCube");
+        basicTypes.insert("imageBuffer");
+        basicTypes.insert("iimageBuffer");
+        basicTypes.insert("uimageBuffer");
+        basicTypes.insert("image1DArray");
+        basicTypes.insert("iimage1DArray");
+        basicTypes.insert("uimage1DArray");
+        basicTypes.insert("image2DArray");
+        basicTypes.insert("iimage2DArray");
+        basicTypes.insert("uimage2DArray");
+        basicTypes.insert("imageCubeArray");
+        basicTypes.insert("iimageCubeArray");
+        basicTypes.insert("uimageCubeArray");
+        basicTypes.insert("image2DMS");
+        basicTypes.insert("iimage2DMS");
+        basicTypes.insert("uimage2DMS");
+        basicTypes.insert("image2DMSArray");
+        basicTypes.insert("iimage2DMSArray");
+        basicTypes.insert("uimage2DMSArray");
+        basicTypes.insert("struct");
         //builtin variables
         //builtin variables
         //compute shaders
         //compute shaders
-        builtinVariables.add("gl_NumWorkGroups");
-        builtinVariables.add("gl_WorkGroupSize");
-        builtinVariables.add("gl_WorkGroupID");
-        builtinVariables.add("gl_LocalInvocationID");
-        builtinVariables.add("gl_GlobalInvocationID");
-        builtinVariables.add("gl_LocalInvocationIndex");
+        builtinVariables.insert("gl_NumWorkGroups");
+        builtinVariables.insert("gl_WorkGroupSize");
+        builtinVariables.insert("gl_WorkGroupID");
+        builtinVariables.insert("gl_LocalInvocationID");
+        builtinVariables.insert("gl_GlobalInvocationID");
+        builtinVariables.insert("gl_LocalInvocationIndex");
         //vertex shaders
         //vertex shaders
-        builtinVariables.add("gl_VertexID");
-        builtinVariables.add("gl_InstanceID");
-        builtinVariables.add("gl_Position");
+        builtinVariables.insert("gl_VertexID");
+        builtinVariables.insert("gl_InstanceID");
+        builtinVariables.insert("gl_Position");
         //geometry shaders
         //geometry shaders
-        builtinVariables.add("gl_PrimitiveIDIn");
-        builtinVariables.add("gl_Layer");
-        builtinVariables.add("gl_ViewportIndex");
+        builtinVariables.insert("gl_PrimitiveIDIn");
+        builtinVariables.insert("gl_Layer");
+        builtinVariables.insert("gl_ViewportIndex");
         //tesselation shaders
         //tesselation shaders
-        builtinVariables.add("gl_MaxPatchVertices");
-        builtinVariables.add("gl_PatchVerticesIn");
-        builtinVariables.add("gl_TessLevelOuter");
-        builtinVariables.add("gl_TessLevelInner");
-        builtinVariables.add("gl_TessCoord");
+        builtinVariables.insert("gl_MaxPatchVertices");
+        builtinVariables.insert("gl_PatchVerticesIn");
+        builtinVariables.insert("gl_TessLevelOuter");
+        builtinVariables.insert("gl_TessLevelInner");
+        builtinVariables.insert("gl_TessCoord");
         //fragment shaders
         //fragment shaders
-        builtinVariables.add("gl_FragCoord");
-        builtinVariables.add("gl_FrontFacing");
-        builtinVariables.add("gl_PointCoord");
-        builtinVariables.add("gl_SampleID");
-        builtinVariables.add("gl_SamplePosition");
-        builtinVariables.add("gl_SampleMaskIn");
-        builtinVariables.add("gl_Layer");
-        builtinVariables.add("gl_ViewportIndex");
-        builtinVariables.add("gl_FragColor");
+        builtinVariables.insert("gl_FragCoord");
+        builtinVariables.insert("gl_FrontFacing");
+        builtinVariables.insert("gl_PointCoord");
+        builtinVariables.insert("gl_SampleID");
+        builtinVariables.insert("gl_SamplePosition");
+        builtinVariables.insert("gl_SampleMaskIn");
+        builtinVariables.insert("gl_Layer");
+        builtinVariables.insert("gl_ViewportIndex");
+        builtinVariables.insert("gl_FragColor");
         //general
         //general
-        builtinVariables.add("gl_Position");
-        builtinVariables.add("gl_PointSize");
-        builtinVariables.add("gl_ClipDistance");
-        builtinVariables.add("gl_InvocationID");
-        builtinVariables.add("gl_PrimitiveID");
+        builtinVariables.insert("gl_Position");
+        builtinVariables.insert("gl_PointSize");
+        builtinVariables.insert("gl_ClipDistance");
+        builtinVariables.insert("gl_InvocationID");
+        builtinVariables.insert("gl_PrimitiveID");
         //jme variables - this is why we build custom plugins :) (apart from existing being under GPL)
         //jme variables - this is why we build custom plugins :) (apart from existing being under GPL)
-        builtinVariables.add("inPosition");
-        builtinVariables.add("inNormal");
-        builtinVariables.add("inColor");
-        builtinVariables.add("inTextCoord");
-        builtinVariables.add("g_WorldMatrix");
-        builtinVariables.add("g_ViewMatrix");
-        builtinVariables.add("g_ProjectionMatrix");
-        builtinVariables.add("g_WorldViewMatrix");
-        builtinVariables.add("g_WorldViewProjectionMatrix");
-        builtinVariables.add("g_WorldNormalMatrix");
-        builtinVariables.add("g_NormalMatrix");
-        builtinVariables.add("g_ViewProjectionMatrix");
-        builtinVariables.add("g_WorldMatrixInverseTranspose");
-        builtinVariables.add("g_WorldMatrixInverse");
-        builtinVariables.add("g_ViewMatrixInverse");
-        builtinVariables.add("g_ProjectionMatrixInverse");
-        builtinVariables.add("g_ViewProjectionMatrixInverse");
-        builtinVariables.add("g_WorldViewMatrixInverse");
-        builtinVariables.add("g_NormalMatrixInverse");
-        builtinVariables.add("g_WorldViewProjectionMatrixInverse");
-        builtinVariables.add("g_ViewPort");
-        builtinVariables.add("g_FrustumNearFar");
-        builtinVariables.add("g_Resolution");
-        builtinVariables.add("g_ResolutionInverse");
-        builtinVariables.add("g_Aspect");
-        builtinVariables.add("g_CameraPosition");
-        builtinVariables.add("g_CameraDirection");
-        builtinVariables.add("g_CameraLeft");
-        builtinVariables.add("g_CameraUp");
-        builtinVariables.add("g_Time");
-        builtinVariables.add("g_Tpf");
-        builtinVariables.add("g_FrameRate");
-        builtinVariables.add("g_LightDirection");
-        builtinVariables.add("g_LightPosition");
-        builtinVariables.add("g_LightColor");
-        builtinVariables.add("g_AmbientLightColor");
+        builtinVariables.insert("inPosition");
+        builtinVariables.insert("inNormal");
+        builtinVariables.insert("inColor");
+        builtinVariables.insert("inTextCoord");
+        builtinVariables.insert("g_WorldMatrix");
+        builtinVariables.insert("g_ViewMatrix");
+        builtinVariables.insert("g_ProjectionMatrix");
+        builtinVariables.insert("g_WorldViewMatrix");
+        builtinVariables.insert("g_WorldViewProjectionMatrix");
+        builtinVariables.insert("g_WorldNormalMatrix");
+        builtinVariables.insert("g_NormalMatrix");
+        builtinVariables.insert("g_ViewProjectionMatrix");
+        builtinVariables.insert("g_WorldMatrixInverseTranspose");
+        builtinVariables.insert("g_WorldMatrixInverse");
+        builtinVariables.insert("g_ViewMatrixInverse");
+        builtinVariables.insert("g_ProjectionMatrixInverse");
+        builtinVariables.insert("g_ViewProjectionMatrixInverse");
+        builtinVariables.insert("g_WorldViewMatrixInverse");
+        builtinVariables.insert("g_NormalMatrixInverse");
+        builtinVariables.insert("g_WorldViewProjectionMatrixInverse");
+        builtinVariables.insert("g_ViewPort");
+        builtinVariables.insert("g_FrustumNearFar");
+        builtinVariables.insert("g_Resolution");
+        builtinVariables.insert("g_ResolutionInverse");
+        builtinVariables.insert("g_Aspect");
+        builtinVariables.insert("g_CameraPosition");
+        builtinVariables.insert("g_CameraDirection");
+        builtinVariables.insert("g_CameraLeft");
+        builtinVariables.insert("g_CameraUp");
+        builtinVariables.insert("g_Time");
+        builtinVariables.insert("g_Tpf");
+        builtinVariables.insert("g_FrameRate");
+        builtinVariables.insert("g_LightDirection");
+        builtinVariables.insert("g_LightPosition");
+        builtinVariables.insert("g_LightColor");
+        builtinVariables.insert("g_AmbientLightColor");
         //builtin functions
         //builtin functions
-        builtinFunctions.add("radians");
-        builtinFunctions.add("degrees");
-        builtinFunctions.add("sin");
-        builtinFunctions.add("cos");
-        builtinFunctions.add("tan");
-        builtinFunctions.add("asin");
-        builtinFunctions.add("acos");
-        builtinFunctions.add("atan");
-        builtinFunctions.add("sinh");
-        builtinFunctions.add("cosh");
-        builtinFunctions.add("tanh");
-        builtinFunctions.add("asinh");
-        builtinFunctions.add("acosh");
-        builtinFunctions.add("atanh");
-        builtinFunctions.add("pow");
-        builtinFunctions.add("exp");
-        builtinFunctions.add("log");
-        builtinFunctions.add("exp2");
-        builtinFunctions.add("log2");
-        builtinFunctions.add("sqrt");
-        builtinFunctions.add("inversesqrt");
-        builtinFunctions.add("abs");
-        builtinFunctions.add("sign");
-        builtinFunctions.add("floor");
-        builtinFunctions.add("trunc");
-        builtinFunctions.add("round");
-        builtinFunctions.add("roundEven");
-        builtinFunctions.add("ceil");
-        builtinFunctions.add("fract");
-        builtinFunctions.add("mod");
-        builtinFunctions.add("modf");
-        builtinFunctions.add("min");
-        builtinFunctions.add("max");
-        builtinFunctions.add("clamp");
-        builtinFunctions.add("mix");
-        builtinFunctions.add("step");
-        builtinFunctions.add("smoothstep");
-        builtinFunctions.add("isnan");
-        builtinFunctions.add("isinf");
-        builtinFunctions.add("floatBitsToInt");
-        builtinFunctions.add("floatBitsToUInt");
-        builtinFunctions.add("intBitsToFloat");
-        builtinFunctions.add("uintBitsToFloat");
-        builtinFunctions.add("fma");
-        builtinFunctions.add("frexp");
-        builtinFunctions.add("packUnorm2x16");
-        builtinFunctions.add("packSnorm2x16");
-        builtinFunctions.add("packUnorm4x8");
-        builtinFunctions.add("packSnorm4x8");
-        builtinFunctions.add("unpackUnorm2x16");
-        builtinFunctions.add("unpackSnorm2x16");
-        builtinFunctions.add("unpackUnorm4x8");
-        builtinFunctions.add("unpackSnorm4x8");
-        builtinFunctions.add("packDouble2x32");
-        builtinFunctions.add("unpackDouble2x32");
-        builtinFunctions.add("packHalf2x16");
-        builtinFunctions.add("unpackHalf2x16");
-        builtinFunctions.add("length");
-        builtinFunctions.add("distance");
-        builtinFunctions.add("dot");
-        builtinFunctions.add("cross");
-        builtinFunctions.add("normalize");
-        builtinFunctions.add("ftransform");
-        builtinFunctions.add("faceforward");
-        builtinFunctions.add("reflect");
-        builtinFunctions.add("refract");
-        builtinFunctions.add("matrixCompMult");
-        builtinFunctions.add("outerProduct");
-        builtinFunctions.add("transpose");
-        builtinFunctions.add("determinant");
-        builtinFunctions.add("inverse");
-        builtinFunctions.add("lessThan");
-        builtinFunctions.add("lessThanEqual");
-        builtinFunctions.add("greaterThan");
-        builtinFunctions.add("greaterThanEqual");
-        builtinFunctions.add("equal");
-        builtinFunctions.add("notEqual");
-        builtinFunctions.add("any");
-        builtinFunctions.add("all");
-        builtinFunctions.add("not");
-        builtinFunctions.add("uaddCarry");
-        builtinFunctions.add("usubBorrow");
-        builtinFunctions.add("umulExtended");
-        builtinFunctions.add("imulExtended");
-        builtinFunctions.add("bitfieldExtract");
-        builtinFunctions.add("bitfieldInsert");
-        builtinFunctions.add("bitfieldReverse");
-        builtinFunctions.add("bitCount");
-        builtinFunctions.add("findLSB");
-        builtinFunctions.add("findMSB");
-        builtinFunctions.add("textureSize");
-        builtinFunctions.add("textureQueryLod");
-        builtinFunctions.add("textureQueryLevels");
-        builtinFunctions.add("texture");
-        builtinFunctions.add("textureProj");
-        builtinFunctions.add("textureLod");
-        builtinFunctions.add("textureOffset");
-        builtinFunctions.add("texelFetch");
-        builtinFunctions.add("texelFetchOffset");
-        builtinFunctions.add("textureProjOffset");
-        builtinFunctions.add("textureLodOffset");
-        builtinFunctions.add("textureProjLod");
-        builtinFunctions.add("textureProjLodOffset");
-        builtinFunctions.add("textureGrad");
-        builtinFunctions.add("textureGradOffset");
-        builtinFunctions.add("textureProjGrad");
-        builtinFunctions.add("textureProjGradOffset");
-        builtinFunctions.add("textureGather");
-        builtinFunctions.add("textureGatherOffset");
-        builtinFunctions.add("textureGatherOffsets");
-        builtinFunctions.add("texture1D");
-        builtinFunctions.add("texture1DProj");
-        builtinFunctions.add("texture1DLod");
-        builtinFunctions.add("texture1DProjLod");
-        builtinFunctions.add("texture2D");
-        builtinFunctions.add("texture2DProj");
-        builtinFunctions.add("texture2DLod");
-        builtinFunctions.add("texture2DProjLod");
-        builtinFunctions.add("texture3D");
-        builtinFunctions.add("texture3DProj");
-        builtinFunctions.add("texture3DLod");
-        builtinFunctions.add("texture3DProjLod");
-        builtinFunctions.add("textureCube");
-        builtinFunctions.add("textureCubeLod");
-        builtinFunctions.add("shadow1D");
-        builtinFunctions.add("shadow2D");
-        builtinFunctions.add("shadow1DProj");
-        builtinFunctions.add("shadow2DProj");
-        builtinFunctions.add("shadow1DLod");
-        builtinFunctions.add("shadow2DLod");
-        builtinFunctions.add("shadow1DProjLod");
-        builtinFunctions.add("shadow2DProjLod");
-        builtinFunctions.add("atomicCounterIncrement");
-        builtinFunctions.add("atomicCounterDecrement");
-        builtinFunctions.add("atomicCounter");
-        builtinFunctions.add("atomicAdd");
-        builtinFunctions.add("atomicMin");
-        builtinFunctions.add("atomicMax");
-        builtinFunctions.add("atomicAnd");
-        builtinFunctions.add("atomicOr");
-        builtinFunctions.add("atomicXor");
-        builtinFunctions.add("atomicExchange");
-        builtinFunctions.add("atomicCompSwap");
-        builtinFunctions.add("imageSize");
-        builtinFunctions.add("imageLoad");
-        builtinFunctions.add("imageStore");
-        builtinFunctions.add("imageAtomicAdd");
-        builtinFunctions.add("imageAtomicMin");
-        builtinFunctions.add("imageAtomicMax");
-        builtinFunctions.add("imageAtomicAnd");
-        builtinFunctions.add("imageAtomicOr");
-        builtinFunctions.add("imageAtomicXor");
-        builtinFunctions.add("imageAtomicExchange");
-        builtinFunctions.add("imageAtomicCompSwap");
-        builtinFunctions.add("dFdx");
-        builtinFunctions.add("dFdy");
-        builtinFunctions.add("fwidth");
-        builtinFunctions.add("interpolateAtCentroid");
-        builtinFunctions.add("interpolateAtSample");
-        builtinFunctions.add("interpolateAtOffset");
-        builtinFunctions.add("noise1");
-        builtinFunctions.add("noise2");
-        builtinFunctions.add("noise3");
-        builtinFunctions.add("noise4");
-        builtinFunctions.add("EmitStreamVertex");
-        builtinFunctions.add("EndStreamPrimitive");
-        builtinFunctions.add("EmitVertex");
-        builtinFunctions.add("EndPrimitive");
-        builtinFunctions.add("barrier");
-        builtinFunctions.add("memoryBarrier");
-        builtinFunctions.add("memoryBarrierAtomicCounter");
-        builtinFunctions.add("memoryBarrierBuffer");
-        builtinFunctions.add("memoryBarrierShared");
-        builtinFunctions.add("memoryBarrierImage");
-        builtinFunctions.add("groupMemoryBarrier");
+        builtinFunctions.insert("radians");
+        builtinFunctions.insert("degrees");
+        builtinFunctions.insert("sin");
+        builtinFunctions.insert("cos");
+        builtinFunctions.insert("tan");
+        builtinFunctions.insert("asin");
+        builtinFunctions.insert("acos");
+        builtinFunctions.insert("atan");
+        builtinFunctions.insert("sinh");
+        builtinFunctions.insert("cosh");
+        builtinFunctions.insert("tanh");
+        builtinFunctions.insert("asinh");
+        builtinFunctions.insert("acosh");
+        builtinFunctions.insert("atanh");
+        builtinFunctions.insert("pow");
+        builtinFunctions.insert("exp");
+        builtinFunctions.insert("log");
+        builtinFunctions.insert("exp2");
+        builtinFunctions.insert("log2");
+        builtinFunctions.insert("sqrt");
+        builtinFunctions.insert("inversesqrt");
+        builtinFunctions.insert("abs");
+        builtinFunctions.insert("sign");
+        builtinFunctions.insert("floor");
+        builtinFunctions.insert("trunc");
+        builtinFunctions.insert("round");
+        builtinFunctions.insert("roundEven");
+        builtinFunctions.insert("ceil");
+        builtinFunctions.insert("fract");
+        builtinFunctions.insert("mod");
+        builtinFunctions.insert("modf");
+        builtinFunctions.insert("min");
+        builtinFunctions.insert("max");
+        builtinFunctions.insert("clamp");
+        builtinFunctions.insert("mix");
+        builtinFunctions.insert("step");
+        builtinFunctions.insert("smoothstep");
+        builtinFunctions.insert("isnan");
+        builtinFunctions.insert("isinf");
+        builtinFunctions.insert("floatBitsToInt");
+        builtinFunctions.insert("floatBitsToUInt");
+        builtinFunctions.insert("intBitsToFloat");
+        builtinFunctions.insert("uintBitsToFloat");
+        builtinFunctions.insert("fma");
+        builtinFunctions.insert("frexp");
+        builtinFunctions.insert("packUnorm2x16");
+        builtinFunctions.insert("packSnorm2x16");
+        builtinFunctions.insert("packUnorm4x8");
+        builtinFunctions.insert("packSnorm4x8");
+        builtinFunctions.insert("unpackUnorm2x16");
+        builtinFunctions.insert("unpackSnorm2x16");
+        builtinFunctions.insert("unpackUnorm4x8");
+        builtinFunctions.insert("unpackSnorm4x8");
+        builtinFunctions.insert("packDouble2x32");
+        builtinFunctions.insert("unpackDouble2x32");
+        builtinFunctions.insert("packHalf2x16");
+        builtinFunctions.insert("unpackHalf2x16");
+        builtinFunctions.insert("length");
+        builtinFunctions.insert("distance");
+        builtinFunctions.insert("dot");
+        builtinFunctions.insert("cross");
+        builtinFunctions.insert("normalize");
+        builtinFunctions.insert("ftransform");
+        builtinFunctions.insert("faceforward");
+        builtinFunctions.insert("reflect");
+        builtinFunctions.insert("refract");
+        builtinFunctions.insert("matrixCompMult");
+        builtinFunctions.insert("outerProduct");
+        builtinFunctions.insert("transpose");
+        builtinFunctions.insert("determinant");
+        builtinFunctions.insert("inverse");
+        builtinFunctions.insert("lessThan");
+        builtinFunctions.insert("lessThanEqual");
+        builtinFunctions.insert("greaterThan");
+        builtinFunctions.insert("greaterThanEqual");
+        builtinFunctions.insert("equal");
+        builtinFunctions.insert("notEqual");
+        builtinFunctions.insert("any");
+        builtinFunctions.insert("all");
+        builtinFunctions.insert("not");
+        builtinFunctions.insert("uaddCarry");
+        builtinFunctions.insert("usubBorrow");
+        builtinFunctions.insert("umulExtended");
+        builtinFunctions.insert("imulExtended");
+        builtinFunctions.insert("bitfieldExtract");
+        builtinFunctions.insert("bitfieldInsert");
+        builtinFunctions.insert("bitfieldReverse");
+        builtinFunctions.insert("bitCount");
+        builtinFunctions.insert("findLSB");
+        builtinFunctions.insert("findMSB");
+        builtinFunctions.insert("textureSize");
+        builtinFunctions.insert("textureQueryLod");
+        builtinFunctions.insert("textureQueryLevels");
+        builtinFunctions.insert("texture");
+        builtinFunctions.insert("textureProj");
+        builtinFunctions.insert("textureLod");
+        builtinFunctions.insert("textureOffset");
+        builtinFunctions.insert("texelFetch");
+        builtinFunctions.insert("texelFetchOffset");
+        builtinFunctions.insert("textureProjOffset");
+        builtinFunctions.insert("textureLodOffset");
+        builtinFunctions.insert("textureProjLod");
+        builtinFunctions.insert("textureProjLodOffset");
+        builtinFunctions.insert("textureGrad");
+        builtinFunctions.insert("textureGradOffset");
+        builtinFunctions.insert("textureProjGrad");
+        builtinFunctions.insert("textureProjGradOffset");
+        builtinFunctions.insert("textureGather");
+        builtinFunctions.insert("textureGatherOffset");
+        builtinFunctions.insert("textureGatherOffsets");
+        builtinFunctions.insert("texture1D");
+        builtinFunctions.insert("texture1DProj");
+        builtinFunctions.insert("texture1DLod");
+        builtinFunctions.insert("texture1DProjLod");
+        builtinFunctions.insert("texture2D");
+        builtinFunctions.insert("texture2DProj");
+        builtinFunctions.insert("texture2DLod");
+        builtinFunctions.insert("texture2DProjLod");
+        builtinFunctions.insert("texture3D");
+        builtinFunctions.insert("texture3DProj");
+        builtinFunctions.insert("texture3DLod");
+        builtinFunctions.insert("texture3DProjLod");
+        builtinFunctions.insert("textureCube");
+        builtinFunctions.insert("textureCubeLod");
+        builtinFunctions.insert("shadow1D");
+        builtinFunctions.insert("shadow2D");
+        builtinFunctions.insert("shadow1DProj");
+        builtinFunctions.insert("shadow2DProj");
+        builtinFunctions.insert("shadow1DLod");
+        builtinFunctions.insert("shadow2DLod");
+        builtinFunctions.insert("shadow1DProjLod");
+        builtinFunctions.insert("shadow2DProjLod");
+        builtinFunctions.insert("atomicCounterIncrement");
+        builtinFunctions.insert("atomicCounterDecrement");
+        builtinFunctions.insert("atomicCounter");
+        builtinFunctions.insert("atomicAdd");
+        builtinFunctions.insert("atomicMin");
+        builtinFunctions.insert("atomicMax");
+        builtinFunctions.insert("atomicAnd");
+        builtinFunctions.insert("atomicOr");
+        builtinFunctions.insert("atomicXor");
+        builtinFunctions.insert("atomicExchange");
+        builtinFunctions.insert("atomicCompSwap");
+        builtinFunctions.insert("imageSize");
+        builtinFunctions.insert("imageLoad");
+        builtinFunctions.insert("imageStore");
+        builtinFunctions.insert("imageAtomicAdd");
+        builtinFunctions.insert("imageAtomicMin");
+        builtinFunctions.insert("imageAtomicMax");
+        builtinFunctions.insert("imageAtomicAnd");
+        builtinFunctions.insert("imageAtomicOr");
+        builtinFunctions.insert("imageAtomicXor");
+        builtinFunctions.insert("imageAtomicExchange");
+        builtinFunctions.insert("imageAtomicCompSwap");
+        builtinFunctions.insert("dFdx");
+        builtinFunctions.insert("dFdy");
+        builtinFunctions.insert("fwidth");
+        builtinFunctions.insert("interpolateAtCentroid");
+        builtinFunctions.insert("interpolateAtSample");
+        builtinFunctions.insert("interpolateAtOffset");
+        builtinFunctions.insert("noise1");
+        builtinFunctions.insert("noise2");
+        builtinFunctions.insert("noise3");
+        builtinFunctions.insert("noise4");
+        builtinFunctions.insert("EmitStreamVertex");
+        builtinFunctions.insert("EndStreamPrimitive");
+        builtinFunctions.insert("EmitVertex");
+        builtinFunctions.insert("EndPrimitive");
+        builtinFunctions.insert("barrier");
+        builtinFunctions.insert("memoryBarrier");
+        builtinFunctions.insert("memoryBarrierAtomicCounter");
+        builtinFunctions.insert("memoryBarrierBuffer");
+        builtinFunctions.insert("memoryBarrierShared");
+        builtinFunctions.insert("memoryBarrierImage");
+        builtinFunctions.insert("groupMemoryBarrier");
     }
     }
 
 
+    /**
+     * Finds if given string is either a partial match, full match or nothing at
+     * all
+     *
+     * @param s string to search for
+     * @return returns the status of the string
+     */
     public static KeywordType lookup(String s) {
     public static KeywordType lookup(String s) {
         KeywordType returnType = null;
         KeywordType returnType = null;
-        for (String primitive : basicTypes) {
-            if (primitive.startsWith(s)) {
-                if (primitive.equals(s)) {
-                    returnType = KeywordType.BASIC_TYPE;
-                    break;
-                } else {
-                    returnType = KeywordType.UNFINISHED;
-                }
-            }
+        returnType = lookup(s, returnType, KeywordType.BASIC_TYPE, basicTypes);
+        if (returnType == KeywordType.UNFINISHED || returnType == null) {
+            returnType = lookup(s, returnType, KeywordType.BUILTIN_VARIABLE, builtinVariables);
         }
         }
-        for (String var : builtinVariables) {
-            if (var.startsWith(s)) {
-                if (var.equals(s)) {
-                    returnType = KeywordType.BUILTIN_VARIABLE;
-                    break;
-                } else {
-                    returnType = KeywordType.UNFINISHED;
-                }
-            }
+        if (returnType == KeywordType.UNFINISHED || returnType == null) {
+            returnType = lookup(s, returnType, KeywordType.BUILTIN_FUNCTION, builtinFunctions);
         }
         }
-        for (String func : builtinFunctions) {
-            if (func.startsWith(s) && (returnType == KeywordType.UNFINISHED || returnType == null)) {
-                if (func.equals(s)) {
-                    returnType = KeywordType.BUILTIN_FUNCTION;
-                    break;
-                } else {
-                    returnType = KeywordType.UNFINISHED;
-                }
-            }
-        }
-        for (String keyword : keywords) {
-            if (keyword.startsWith(s) && (returnType == KeywordType.UNFINISHED || returnType == null)) {
-                if (keyword.equals(s)) {
-                    returnType = KeywordType.KEYWORD;
-                    break;
-                } else {
-                    returnType = KeywordType.UNFINISHED;
-                }
-            }
+        if (returnType == KeywordType.UNFINISHED || returnType == null) {
+            returnType = lookup(s, returnType, KeywordType.KEYWORD, keywords);
         }
         }
 
 
         return returnType;
         return returnType;
     }
     }
+
+    /**
+     * Gets all the possible matches for given string (auto-complete)
+     *
+     * @param s string to search for
+     * @return all the matches for given string
+     */
+    public static List<Keyword> lookupAll(String s) {
+        List<Keyword> matches = new ArrayList<>();
+
+        matches.addAll(basicTypes.searchAll(s).stream().map((keyword) -> new Keyword(keyword, KeywordType.BASIC_TYPE)).toList());
+        matches.addAll(builtinVariables.searchAll(s).stream().map((keyword) -> new Keyword(keyword, KeywordType.BUILTIN_VARIABLE)).toList());
+        matches.addAll(builtinFunctions.searchAll(s).stream().map((keyword) -> new Keyword(keyword, KeywordType.BUILTIN_FUNCTION)).toList());
+        matches.addAll(keywords.searchAll(s).stream().map((keyword) -> new Keyword(keyword, KeywordType.KEYWORD)).toList());
+
+        return matches;
+    }
+
+    private static KeywordType lookup(String s, KeywordType currentType, KeywordType matchType, Trie searchTrie) {
+        Trie.MatchType match = searchTrie.search(s);
+        if (match == Trie.MatchType.FULL_MATCH) {
+            return matchType;
+        }
+        if (match == Trie.MatchType.PARTIAL_MATCH) {
+            return KeywordType.UNFINISHED;
+        }
+
+        return currentType;
+    }
 }
 }

+ 20 - 26
jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/lexer/GlslLexer.java

@@ -190,21 +190,26 @@ public class GlslLexer implements Lexer<GlslTokenID> {
                             }
                             }
                         }
                         }
                     }
                     }
-                    if (current == null) {
-                    }
-                    switch (current) {
-                        case BASIC_TYPE:
-                            return token(GlslTokenID.PRIMITIVE);
-                        case KEYWORD:
-                            return token(GlslTokenID.KEYWORD);
-                        case BUILTIN_VARIABLE:
-                            return token(GlslTokenID.BUILTIN_VARIABLE);
-                        case BUILTIN_FUNCTION:
-                            return token(GlslTokenID.BUILTIN_FUNCTION);
+                    if (current != null) {
+                        switch (current) {
+                            case BASIC_TYPE -> {
+                                return token(GlslTokenID.PRIMITIVE);
+                            }
+                            case KEYWORD -> {
+                                return token(GlslTokenID.KEYWORD);
+                            }
+                            case BUILTIN_VARIABLE -> {
+                                return token(GlslTokenID.BUILTIN_VARIABLE);
+                            }
+                            case BUILTIN_FUNCTION -> {
+                                return token(GlslTokenID.BUILTIN_FUNCTION);
+                            }
+                        }
                     }
                     }
                 }
                 }
             }
             }
         }
         }
+        
         //Those have to be recognized separately for closing bracket recognition
         //Those have to be recognized separately for closing bracket recognition
         return token(GlslTokenID.TEXT);
         return token(GlslTokenID.TEXT);
     }
     }
@@ -224,21 +229,10 @@ public class GlslLexer implements Lexer<GlslTokenID> {
     }
     }
 
 
     private boolean isDigit(int c) {
     private boolean isDigit(int c) {
-        switch (c) {
-            case '0':
-            case '1':
-            case '2':
-            case '3':
-            case '4':
-            case '5':
-            case '6':
-            case '7':
-            case '8':
-            case '9':
-                return true;
-            default:
-                return false;
-        }
+        return switch (c) {
+            case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> true;
+            default -> false;
+        };
     }
     }
 
 
     private void readTillNewLine() {
     private void readTillNewLine() {

+ 147 - 0
jme3-glsl-highlighter/src/com/jme3/gde/glsl/highlighter/util/Trie.java

@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2003-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.glsl.highlighter.util;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Simple and efficient text search
+ */
+public class Trie {
+
+    private final TrieNode root;
+
+    private static class TrieNode {
+
+        Map<Character, TrieNode> children = new HashMap<>();
+        boolean isEndOfWord = false;
+    }
+
+    private record Pair(TrieNode node, StringBuilder prefix) {
+
+    }
+
+    public enum MatchType {
+        NO_MATCH,
+        PARTIAL_MATCH,
+        FULL_MATCH
+    }
+
+    public Trie() {
+        root = new TrieNode();
+    }
+
+    /**
+     * Insert word to the Trie structure
+     *
+     * @param word word to insert
+     */
+    public void insert(String word) {
+        TrieNode current = root;
+        for (char ch : word.toCharArray()) {
+            current = current.children.computeIfAbsent(ch, c -> new TrieNode());
+        }
+        current.isEndOfWord = true;
+    }
+
+    private TrieNode findNode(String word) {
+        TrieNode current = root;
+        for (char ch : word.toCharArray()) {
+            if (!current.children.containsKey(ch)) {
+                return null;
+            }
+            current = current.children.get(ch);
+        }
+
+        return current;
+    }
+
+    /**
+     * Searches for the string
+     *
+     * @param word word to search for
+     * @return match type
+     */
+    public MatchType search(String word) {
+        TrieNode node = findNode(word);
+        if (node == null) {
+            return MatchType.NO_MATCH;
+        }
+
+        return node.isEndOfWord ? MatchType.FULL_MATCH : MatchType.PARTIAL_MATCH;
+    }
+
+    /**
+     * Searches for the string and gives out all the possible matches
+     *
+     * @param word word to search for
+     */
+    public List<String> searchAll(String word) {
+        TrieNode node = findNode(word);
+        if (node == null) {
+            return Collections.emptyList();
+        }
+
+        return collectAllWords(node, word);
+    }
+
+    private static List<String> collectAllWords(TrieNode startNode, String prefix) {
+        List<String> results = new ArrayList<>();
+        Deque<Pair> stack = new ArrayDeque<>();
+        stack.push(new Pair(startNode, new StringBuilder(prefix)));
+
+        while (!stack.isEmpty()) {
+            Pair pair = stack.pop();
+            TrieNode node = pair.node;
+            StringBuilder currentPrefix = pair.prefix;
+
+            if (node.isEndOfWord) {
+                results.add(currentPrefix.toString());
+            }
+
+            for (Map.Entry<Character, TrieNode> entry : node.children.entrySet()) {
+                char nextChar = entry.getKey();
+                TrieNode childNode = entry.getValue();
+                stack.push(new Pair(childNode, new StringBuilder(currentPrefix).append(nextChar)));
+            }
+        }
+
+        return results;
+    }
+
+}

+ 4 - 1
jme3-materialeditor/src/com/jme3/gde/materials/MaterialPropertyEditor.java

@@ -39,6 +39,7 @@ import com.jme3.gde.core.properties.SceneExplorerProperty;
 import com.jme3.gde.core.properties.SceneExplorerPropertyEditor;
 import com.jme3.gde.core.properties.SceneExplorerPropertyEditor;
 import com.jme3.material.Material;
 import com.jme3.material.Material;
 import com.jme3.asset.MaterialKey;
 import com.jme3.asset.MaterialKey;
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
 import java.awt.Component;
 import java.awt.Component;
 import java.awt.Graphics;
 import java.awt.Graphics;
 import java.awt.Rectangle;
 import java.awt.Rectangle;
@@ -108,7 +109,9 @@ public class MaterialPropertyEditor implements PropertyEditor, SceneExplorerProp
     public void setAsText(final String text) throws IllegalArgumentException {
     public void setAsText(final String text) throws IllegalArgumentException {
         if ("create j3m file".equals(text)) {
         if ("create j3m file".equals(text)) {
             try {
             try {
-                Node geom = SceneExplorerTopComponent.findInstance().getLastSelected();
+                AbstractSceneExplorerNode[] selected = SceneExplorerTopComponent.findInstance().getLastSelected();
+                assert (selected != null && selected.length > 0);
+                Node geom = selected[0];        
                 assert (geom != null);
                 assert (geom != null);
                 ProjectAssetManager pm = geom.getLookup().lookup(ProjectAssetManager.class);
                 ProjectAssetManager pm = geom.getLookup().lookup(ProjectAssetManager.class);
                 assert (pm != null);
                 assert (pm != null);

+ 1 - 0
jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties

@@ -4,6 +4,7 @@ CTL_OpenSceneComposer=Edit in SceneComposer
 CTL_SceneComposerAction=SceneComposer
 CTL_SceneComposerAction=SceneComposer
 CTL_SceneComposerTopComponent=SceneComposer Window
 CTL_SceneComposerTopComponent=SceneComposer Window
 CTL_SomeAction=SomeAction
 CTL_SomeAction=SomeAction
+CTL_MergeAnimationsAction=Merge Animations
 HINT_SceneComposerTopComponent=This is a SceneComposer window
 HINT_SceneComposerTopComponent=This is a SceneComposer window
 OpenIDE-Module-Display-Category=jMonkeyEngine
 OpenIDE-Module-Display-Category=jMonkeyEngine
 OpenIDE-Module-Long-Description=\
 OpenIDE-Module-Long-Description=\

+ 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.
  *  All rights reserved.
  * 
  * 
  *  Redistribution and use in source and binary forms, with or without
  *  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
     // End of variables declaration//GEN-END:variables
 
 
     private void emit(Spatial root) {
     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()) {
             for (Spatial child : n.getChildren()) {
                 emit(child);
                 emit(child);
             }
             }
@@ -1112,14 +1111,14 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
      * @return
      * @return
      */
      */
     public static synchronized SceneComposerTopComponent findInstance() {
     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(
             Logger.getLogger(SceneComposerTopComponent.class.getName()).warning(
                     "Cannot find " + PREFERRED_ID + " component. It will not be located properly in the window system.");
                     "Cannot find " + PREFERRED_ID + " component. It will not be located properly in the window system.");
             return getDefault();
             return getDefault();
         }
         }
-        if (win instanceof SceneComposerTopComponent) {
-            return (SceneComposerTopComponent) win;
+        if (window instanceof SceneComposerTopComponent sceneComposerTopComponent) {
+            return sceneComposerTopComponent;
         }
         }
         Logger.getLogger(SceneComposerTopComponent.class.getName()).warning(
         Logger.getLogger(SceneComposerTopComponent.class.getName()).warning(
                 "There seem to be multiple components with the '" + PREFERRED_ID
                 "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) {
     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();
         cleanupControllers();
         SceneApplication.getApplication().addSceneListener(this);
         SceneApplication.getApplication().addSceneListener(this);
         Node node;
         Node node;
-        if (spat instanceof Node) {
-            node = (Node) spat;
+        if (spatial instanceof Node node1) {
+            node = node1;
         } else {
         } else {
             node = new Node();
             node = new Node();
-            node.attachChild(spat);
+            node.attachChild(spatial);
         }
         }
         JmeNode jmeNode = NodeUtility.createNode(node, file, false);
         JmeNode jmeNode = NodeUtility.createNode(node, file, false);
         SceneRequest request = new SceneRequest(this, jmeNode, manager);
         SceneRequest request = new SceneRequest(this, jmeNode, manager);
@@ -1318,28 +1313,30 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
             return;
             return;
         }
         }
         Collection<AbstractSceneExplorerNode> items = (Collection<AbstractSceneExplorerNode>) result.allInstances();
         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) {
         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;
             return true;
         } else if (toolController != null) {
         } else if (toolController != null) {
-            Spatial selectedGizmo = toolController.getMarker(node);
+            Spatial selectedGizmo = toolController.getMarker(first);
             if (selectedGizmo != null) {
             if (selectedGizmo != null) {
                 selectSpatial(selectedGizmo);
                 selectSpatial(selectedGizmo);
-                SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{node});
-                SceneExplorerTopComponent.findInstance().setSelectedNode(node);
+                SceneExplorerTopComponent.findInstance().setSelectedNode(nodes);
                 return true;
                 return true;
             }
             }
         }
         }
@@ -1356,8 +1353,8 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
         } else if (toolController != null) {
         } else if (toolController != null) {
             toolController.updateSelection(selection);
             toolController.updateSelection(selection);
         }
         }
-        if (selection instanceof Node) {
-            setSelectedObjectText(((Node) selection).getName());
+        if (selection instanceof Node node) {
+            setSelectedObjectText(node.getName());
         } else if (selection instanceof Spatial) {
         } else if (selection instanceof Spatial) {
             setSelectedObjectText(selection.getName());
             setSelectedObjectText(selection.getName());
         } else {
         } else {
@@ -1423,57 +1420,53 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
 
 
             editorController.setTerrainLodCamera();
             editorController.setTerrainLodCamera();
             final SpatialAssetDataObject dobj = ((SpatialAssetDataObject) currentRequest.getDataObject());
             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);
 //            currentRequest.getManager().addClassPathEventListener(listener);
         }
         }

+ 42 - 30
jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneEditTool.java

@@ -1,6 +1,26 @@
 /*
 /*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
+ * Copyright (c) 2009-2024 jMonkeyEngine All rights reserved. <p/>
+ * 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. <p/> * 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. <p/> * 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. <p/> 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;
 package com.jme3.gde.scenecomposer;
 
 
@@ -37,8 +57,6 @@ import com.jme3.util.BufferUtils;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
 import java.nio.FloatBuffer;
 import java.nio.FloatBuffer;
 import java.nio.ShortBuffer;
 import java.nio.ShortBuffer;
-import java.util.Iterator;
-import java.util.concurrent.Callable;
 import org.openide.loaders.DataObject;
 import org.openide.loaders.DataObject;
 import org.openide.util.Lookup;
 import org.openide.util.Lookup;
 
 
@@ -85,7 +103,8 @@ public abstract class SceneEditTool {
      * @param toolNode parent node that the marker will attach to
      * @param toolNode parent node that the marker will attach to
      * @param onTopToolNode the node displayed on top of the scene
      * @param onTopToolNode the node displayed on top of the scene
      * @param selectedSpatial the selected spatial
      * @param selectedSpatial the selected spatial
-     * @param toolController the toolController {@link SceneComposerToolController }
+     * @param toolController the toolController {@link SceneComposerToolController
+     * }
      */
      */
     public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) {
     public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) {
         this.manager = manager;
         this.manager = manager;
@@ -129,18 +148,15 @@ public abstract class SceneEditTool {
 
 
     /**
     /**
      * Called when the selected spatial has been modified outside of the tool.
      * Called when the selected spatial has been modified outside of the tool.
-     * @TODO: why? just move the tool where the object is each frame?
-     * Proposed Answer: Performance.
+     *
+     * @TODO: why? just move the tool where the object is each frame? Proposed
+     * Answer: Performance.
      */
      */
     public void updateToolsTransformation() {
     public void updateToolsTransformation() {
 
 
-        SceneApplication.getApplication().enqueue(new Callable<Object>() {
-
-            @Override
-            public Object call() throws Exception {
-                doUpdateToolsTransformation();
-                return null;
-            }
+        SceneApplication.getApplication().enqueue(() -> {
+            doUpdateToolsTransformation();
+            return null;
         });
         });
     }
     }
 
 
@@ -148,17 +164,15 @@ public abstract class SceneEditTool {
         if (toolController.getSelectedSpatial() != null) {
         if (toolController.getSelectedSpatial() != null) {
             axisMarker.setLocalTranslation(toolController.getSelectedSpatial().getWorldTranslation());
             axisMarker.setLocalTranslation(toolController.getSelectedSpatial().getWorldTranslation());
             switch (transformType) {
             switch (transformType) {
-                case local:
+                case local ->
                     axisMarker.setLocalRotation(toolController.getSelectedSpatial().getWorldRotation());
                     axisMarker.setLocalRotation(toolController.getSelectedSpatial().getWorldRotation());
-                    break;
-                case global:
+                case global ->
                     axisMarker.setLocalRotation(Quaternion.IDENTITY);
                     axisMarker.setLocalRotation(Quaternion.IDENTITY);
-                    break;
-                case camera:
+                case camera -> {
                     if (camera != null) {
                     if (camera != null) {
                         axisMarker.setLocalRotation(camera.getRotation());
                         axisMarker.setLocalRotation(camera.getRotation());
                     }
                     }
-                    break;
+                }
             }
             }
             setAxisMarkerScale(toolController.getSelectedSpatial());
             setAxisMarkerScale(toolController.getSelectedSpatial());
         } else {
         } else {
@@ -173,10 +187,12 @@ public abstract class SceneEditTool {
      */
      */
     private void setAxisMarkerScale(Spatial selected) {
     private void setAxisMarkerScale(Spatial selected) {
         if (selected != null) {
         if (selected != null) {
-            if (selected.getWorldBound() instanceof BoundingBox) {
-                BoundingBox bbox = (BoundingBox) selected.getWorldBound();
+            if (selected.getWorldBound() instanceof BoundingBox bbox) {
                 float smallest = Math.min(Math.min(bbox.getXExtent(), bbox.getYExtent()), bbox.getZExtent());
                 float smallest = Math.min(Math.min(bbox.getXExtent(), bbox.getYExtent()), bbox.getZExtent());
                 float scale = Math.max(1, smallest / 2f);
                 float scale = Math.max(1, smallest / 2f);
+                if (scale > 100) {
+                    scale = 1;
+                }
                 axisMarker.setLocalScale(scale);
                 axisMarker.setLocalScale(scale);
             }
             }
         } else {
         } else {
@@ -321,12 +337,8 @@ public abstract class SceneEditTool {
         if (exclude == null) {
         if (exclude == null) {
             result = results.getClosestCollision();
             result = results.getClosestCollision();
         } else {
         } else {
-            Iterator<CollisionResult> it = results.iterator();
-            while (it.hasNext()) {
-                CollisionResult cr = it.next();
-                if (isExcluded(cr.getGeometry(), exclude)) {
-                    continue;
-                } else {
+            for (CollisionResult cr : results) {
+                if (!isExcluded(cr.getGeometry(), exclude)) {
                     return cr;
                     return cr;
                 }
                 }
             }
             }
@@ -495,7 +507,7 @@ public abstract class SceneEditTool {
         redMat.setColor("Color", ColorRGBA.Red);
         redMat.setColor("Color", ColorRGBA.Red);
         redMat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
         redMat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
         redMat.getAdditionalRenderState().setLineWidth(2f);
         redMat.getAdditionalRenderState().setLineWidth(2f);
-        
+
         greenMat = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
         greenMat = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
         greenMat.getAdditionalRenderState().setWireframe(false);
         greenMat.getAdditionalRenderState().setWireframe(false);
         greenMat.setColor("Color", ColorRGBA.Green);
         greenMat.setColor("Color", ColorRGBA.Green);
@@ -535,7 +547,7 @@ public abstract class SceneEditTool {
         orangeMat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
         orangeMat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
         orangeMat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
         orangeMat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
         orangeMat.getAdditionalRenderState().setLineWidth(2f);
         orangeMat.getAdditionalRenderState().setLineWidth(2f);
-        
+
         Node axis = new Node();
         Node axis = new Node();
 
 
         // create arrows
         // create arrows

+ 14 - 1
jme3-scenecomposer/src/com/jme3/gde/scenecomposer/layer.xml

@@ -33,6 +33,15 @@
                 <attr name="selectionType" stringvalue="ANY"/>
                 <attr name="selectionType" stringvalue="ANY"/>
                 <attr name="type" stringvalue="com.jme3.gde.core.assets.SpatialAssetDataObject"/>
                 <attr name="type" stringvalue="com.jme3.gde.core.assets.SpatialAssetDataObject"/>
             </file>
             </file>
+            <file name="com-jme3-gde-core-assets-actions-MergeAnimationsAction.instance">
+                <attr name="delegate" methodvalue="org.openide.awt.Actions.inject"/>
+                <attr name="displayName" bundlevalue="com.jme3.gde.scenecomposer.Bundle#CTL_MergeAnimationsAction"/>
+                <attr name="injectable" stringvalue="com.jme3.gde.core.assets.actions.MergeAnimationsAction"/>
+                <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.context"/>
+                <attr name="noIconInMenu" boolvalue="false"/>
+                <attr name="selectionType" stringvalue="ANY"/>
+                <attr name="type" stringvalue="com.jme3.gde.core.assets.SpatialAssetDataObject"/>
+            </file>
         </folder>
         </folder>
         <folder name="Window">
         <folder name="Window">
             <file name="com-jme3-gde-scenecomposer-SceneComposerAction.instance">
             <file name="com-jme3-gde-scenecomposer-SceneComposerAction.instance">
@@ -59,9 +68,13 @@
                         <attr name="originalFile" stringvalue="Actions/SceneComposer/com-jme3-gde-scenecomposer-LinkSceneComposer.instance"/>
                         <attr name="originalFile" stringvalue="Actions/SceneComposer/com-jme3-gde-scenecomposer-LinkSceneComposer.instance"/>
                         <attr name="position" intvalue="30"/>
                         <attr name="position" intvalue="30"/>
                     </file>
                     </file>
+                    <file name="com-jme3-gde-core-assets-actions-animation-MergeAnimationsAction.shadow">
+                        <attr name="originalFile" stringvalue="Actions/SceneComposer/com-jme3-gde-core-assets-actions-MergeAnimationsAction.instance"/>
+                        <attr name="position" intvalue="40"/>
+                    </file>
                     <file name="scenecomposer_sep-2.instance">
                     <file name="scenecomposer_sep-2.instance">
                         <attr name="instanceClass" stringvalue="javax.swing.JSeparator"/>
                         <attr name="instanceClass" stringvalue="javax.swing.JSeparator"/>
-                        <attr name="position" intvalue="40"/>
+                        <attr name="position" intvalue="45"/>
                     </file>
                     </file>
                 </folder>
                 </folder>
             </folder>
             </folder>

+ 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;
 package com.jme3.gde.scenecomposer.tools;
 
 
 import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
 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.JmeNode;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
 import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent;
 import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent;
@@ -98,7 +126,7 @@ public class SelectTool extends SceneEditTool {
                             JmeSpatial n = rootNode.getChild(selec);
                             JmeSpatial n = rootNode.getChild(selec);
                             if (n != null) {
                             if (n != null) {
                                 SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{n});
                                 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;
 package com.jme3.gde.scenecomposer.tools.shortcuts;
 
 
 import com.jme3.asset.AssetManager;
 import com.jme3.asset.AssetManager;
 import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
 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.JmeNode;
 import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent;
 import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent;
 import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
 import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
@@ -57,7 +84,11 @@ public class DuplicateShortcut extends ShortcutTool {
 
 
     private void duplicate() {
     private void duplicate() {
         Spatial selected = toolController.getSelectedSpatial();
         Spatial selected = toolController.getSelectedSpatial();
-
+        
+        if(selected == null) {
+            return;
+        }
+        
         Spatial clone = selected.clone();
         Spatial clone = selected.clone();
         clone.move(1, 0, 1);
         clone.move(1, 0, 1);
 
 
@@ -67,28 +98,17 @@ public class DuplicateShortcut extends ShortcutTool {
         final Spatial cloned = clone;
         final Spatial cloned = clone;
         final JmeNode rootNode = toolController.getRootNode();
         final JmeNode rootNode = toolController.getRootNode();
         refreshSelected(rootNode, selected.getParent());
         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);
         toolController.updateSelection(selected);
     }
     }
 
 
     private void refreshSelected(final JmeNode jmeRootNode, final Node parent) {
     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 class DuplicateUndo extends AbstractUndoableSceneEdit {
 
 
-        private Spatial spatial;
-        private Node parent;
+        private final Spatial spatial;
+        private final Node parent;
 
 
         DuplicateUndo(Spatial spatial, Node parent) {
         DuplicateUndo(Spatial spatial, Node parent) {
             this.spatial = spatial;
             this.spatial = spatial;

BIN
nbi/antlib/nbi-ant-tasks.jar


BIN
nbi/antlib/nbi-engine.jar


BIN
nbi/antlib/nbi-registries-management.jar