diff --git a/jme3-templates/src/com/jme3/gde/templates/Bundle.properties b/jme3-templates/src/com/jme3/gde/templates/Bundle.properties
index 8e086f4d..4edb0237 100644
--- a/jme3-templates/src/com/jme3/gde/templates/Bundle.properties
+++ b/jme3-templates/src/com/jme3/gde/templates/Bundle.properties
@@ -5,4 +5,7 @@ OpenIDE-Module-Long-Description=\
OpenIDE-Module-Name=Project Templates
OpenIDE-Module-Short-Description=Provides Project Templates
Templates/Project/JME3/BasicGameProject.zip=Basic Game (with Ant)
-Templates/Project/JME3/GradleDesktopGameProject.zip=Basic Game (with Gradle)
\ No newline at end of file
+Templates/Project/JME3/GradleDesktopGameProject.zip=Basic Game (with Gradle)
+Templates/Project/JME3/Examples/RollingTheMonkeyProject.zip=Rolling The Monkey
+Templates/Project/JME3/Examples/MonkeyZone=Monkey Zone
+Templates/Project/JME3/Examples/JaimesAscent=Jaimes Ascent
\ No newline at end of file
diff --git a/jme3-templates/src/com/jme3/gde/templates/GradleDesktopGameProject.zip b/jme3-templates/src/com/jme3/gde/templates/GradleDesktopGameProject.zip
index 1fd3f16b..0651c528 100644
Binary files a/jme3-templates/src/com/jme3/gde/templates/GradleDesktopGameProject.zip and b/jme3-templates/src/com/jme3/gde/templates/GradleDesktopGameProject.zip differ
diff --git a/jme3-templates/src/com/jme3/gde/templates/RollingTheMonkeyProject.zip b/jme3-templates/src/com/jme3/gde/templates/RollingTheMonkeyProject.zip
new file mode 100644
index 00000000..00789f9f
Binary files /dev/null and b/jme3-templates/src/com/jme3/gde/templates/RollingTheMonkeyProject.zip differ
diff --git a/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/AdditionalLibrary.java b/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/AdditionalLibrary.java
index 690b5640..041c66e9 100644
--- a/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/AdditionalLibrary.java
+++ b/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/AdditionalLibrary.java
@@ -90,7 +90,7 @@ public enum AdditionalLibrary implements TemplateLibrary {
HEART("Heart Library", NbBundle.getMessage(AdditionalLibrary.class,
"additionalLibrary.heart.description"),
"com.github.stephengold", "Heart",
- "8.1.0", false),
+ "9.0.0", false),
PARTICLE_MONKEY("Particle Monkey",
NbBundle.getMessage(AdditionalLibrary.class,
"additionalLibrary.particlemonkey.description"),
@@ -111,7 +111,11 @@ public enum AdditionalLibrary implements TemplateLibrary {
ZAY_ES_NET("Zay-ES-Net Networking Extension",
NbBundle.getMessage(AdditionalLibrary.class,
"additionalLibrary.zayesnet.description"),
- "com.simsilica", "zay-es-net", "1.5.0", false);
+ "com.simsilica", "zay-es-net", "1.5.0", false),
+ WES("Wes Library", NbBundle.getMessage(AdditionalLibrary.class,
+ "additionalLibrary.wes.description"),
+ "com.github.stephengold", "Wes",
+ "0.8.1", false),;
/**
* The name of the library. This will be displayed in the jComboBox in the
diff --git a/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/Bundle.properties b/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/Bundle.properties
index a292da8a..a6eee1ba 100644
--- a/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/Bundle.properties
+++ b/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/Bundle.properties
@@ -5,7 +5,8 @@ additionalLibrary.jme3-vr.description=Core jMonkeyEngine library providing Virtu
additionalLibrary.heart.description=The Heart Library provides an assortment of useful classes and assets to augment jMonkeyEngine.
additionalLibrary.particlemonkey.description=Particle Monkey is a more modern particle system with better artistic controls.
additionalLibrary.shaderblowex.description=Extended filters library for JMonkey Game Engine.
-additionalLibrary.sio2.description=A base library of useful utility code for JME-based games. \
+additionalLibrary.sio2.description=A base library of useful utility code for JME-based games.
+additionalLibrary.wes.description=An animation editing and retargeting library for jMonkeyEngine. \
Includes game system management infrastructure, useful base app states, an event bus, and useful Zay-ES utilities. \
This is a useful base library for any JME game.
additionalLibrary.zayes.description=Zay-ES (pronounced like Doctor Zaius from Planet of the Apes) is a high-performance \
diff --git a/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/NetworkingLibrary.java b/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/NetworkingLibrary.java
index 79f1912d..e26e2d89 100644
--- a/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/NetworkingLibrary.java
+++ b/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/NetworkingLibrary.java
@@ -79,7 +79,7 @@ public enum NetworkingLibrary implements TemplateLibrary {
SIMETHEREAL("SimEthereal", NbBundle.getMessage(NetworkingLibrary.class,
"networkinglibrary.simethereal.description"),
"com.simsilica", "sim-ethereal",
- "1.7.0", false);
+ "1.8.0", false);
/**
* The name of the library. This will be displayed in the jComboBox in the
diff --git a/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/PhysicsLibrary.java b/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/PhysicsLibrary.java
index 2ff80e99..914f1613 100644
--- a/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/PhysicsLibrary.java
+++ b/jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/PhysicsLibrary.java
@@ -75,7 +75,7 @@ public enum PhysicsLibrary implements TemplateLibrary {
MINIE("Minie", NbBundle.getMessage(PhysicsLibrary.class,
"physicslibrary.minie.description"),
"com.github.stephengold", "Minie",
- "5.0.0", false);
+ "8.2.0", false);
/**
* The name of the library. This will be displayed in the jComboBox in the
diff --git a/jme3-templates/src/com/jme3/gde/templates/jaimesascent/Bundle.properties b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/Bundle.properties
new file mode 100644
index 00000000..53cf704a
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/Bundle.properties
@@ -0,0 +1,16 @@
+LBL_DownloadProjectStep=Download project
+LBL_CreateProjectStep=Name and Location
+JaimesAscentPanelVisual.browseButton.text=Br&owse...
+JaimesAscentPanelVisual.createdFolderLabel.text=Project &Folder:
+JaimesAscentPanelVisual.projectLocationLabel.text=Project &Location:
+JaimesAscentDownloadPanelVisual.downloadButton.text=Download project
+JaimesAscentDownloadPanelVisual.downloadButton.actionCommand=DOWNLOAD
+JaimesAscentPanelVisual.projectNameLabel.text=Project &Name:
+JaimesAscentPanelVisual.browseButton.text=Browse
+JaimesAscentPanelVisual.browseButton.actionCommand=BROWSE
+JaimesAscentDownloadPanelVisual.jTextArea1.text=Pressing the button below will download the project from Github, to a temporary location from which it will be installed in the next step\n
+JaimesAscentDownloadPanelVisual.statusField.text=
+JaimesAscentDownloadPanelVisual.downloading=Downloading... Please wait
+JaimesAscentDownloadPanelVisual.downloadSuccess=Download complete. Press 'next' to proceed.
+JaimesAscentDownloadPanelVisual.downloadFailed=Download failed.
+JaimesAscentDownloadPanelVisual.jLabel1.text=
diff --git a/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentDescription.html b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentDescription.html
new file mode 100644
index 00000000..bd44c955
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentDescription.html
@@ -0,0 +1,13 @@
+
+
+
+ Jaimes Ascent
+
+
+ A sample application demonstrating a chase cam with mouse look,
+ physics, moving objects and animations.
+
+ Easily extendable classes and architecture using AppStates and Controls.
+ Suitable for beginners.
+
+
diff --git a/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentDownloadPanel.java b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentDownloadPanel.java
new file mode 100644
index 00000000..b2d9c627
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentDownloadPanel.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 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.templates.jaimesascent;
+
+import java.awt.Component;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import org.openide.WizardDescriptor;
+import org.openide.WizardValidationException;
+import org.openide.util.HelpCtx;
+import org.openide.util.NbBundle;
+
+/**
+ * Panel just asking for basic info.
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class JaimesAscentDownloadPanel implements WizardDescriptor.Panel,
+ WizardDescriptor.ValidatingPanel, WizardDescriptor.FinishablePanel {
+
+ private WizardDescriptor wizardDescriptor;
+ private JaimesAscentDownloadPanelVisual component;
+
+ static String ZIP_NAME = "JaimesAscent.zip";
+ static String DOWNLOAD_FOLDER = System.getProperty("java.io.tmpdir");
+
+ public JaimesAscentDownloadPanel() {
+ }
+
+ public int doDownloadZip() {
+ return downloadFile("https://github.com/neph1/JaimesAscent/archive/refs/tags/v1.1.1.zip", DOWNLOAD_FOLDER, ZIP_NAME);
+ }
+
+ private int downloadFile(String fileURL, String saveDir, String fileName) {
+ HttpURLConnection httpConn = null;
+ BufferedInputStream inputStream = null;
+ FileOutputStream fileOutputStream = null;
+
+ final File outputFile = new File(saveDir, fileName);
+
+ if (outputFile.exists()) {
+ return 1;
+ }
+
+ try {
+ // Create URL object
+ URL url = new URL(fileURL);
+ httpConn = (HttpURLConnection) url.openConnection();
+
+ // Check HTTP response code
+ int responseCode = httpConn.getResponseCode();
+ if (responseCode == HttpURLConnection.HTTP_OK) {
+ // Open input stream from the HTTP connection
+ inputStream = new BufferedInputStream(httpConn.getInputStream());
+
+
+
+ // Create output stream to save the file
+ fileOutputStream = new FileOutputStream(outputFile);
+
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ fileOutputStream.write(buffer, 0, bytesRead);
+ }
+
+ return 1;
+ }
+ } catch (IOException e) {
+ return 0;
+ } finally {
+ // Close resources
+ try {
+ if (inputStream != null) inputStream.close();
+ if (fileOutputStream != null) fileOutputStream.close();
+ if (httpConn != null) httpConn.disconnect();
+ } catch (IOException ex) {
+ return 0;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public Component getComponent() {
+ if (component == null) {
+ component = new JaimesAscentDownloadPanelVisual(this);
+ component.setName(NbBundle.getMessage(JaimesAscentDownloadPanel.class, "LBL_DownloadProjectStep"));
+ }
+ return component;
+ }
+
+ @Override
+ public HelpCtx getHelp() {
+ return new HelpCtx("sdk.download_project");
+ }
+
+ @Override
+ public boolean isValid() {
+ getComponent();
+ return component.valid(wizardDescriptor);
+ }
+
+ private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0
+
+ @Override
+ public final void addChangeListener(ChangeListener l) {
+ synchronized (listeners) {
+ listeners.add(l);
+ }
+ }
+
+ @Override
+ public final void removeChangeListener(ChangeListener l) {
+ synchronized (listeners) {
+ listeners.remove(l);
+ }
+ }
+
+ protected final void fireChangeEvent() {
+ Set ls;
+ synchronized (listeners) {
+ ls = new HashSet<>(listeners);
+ }
+ ChangeEvent ev = new ChangeEvent(this);
+ for (ChangeListener l : ls) {
+ l.stateChanged(ev);
+ }
+ }
+
+ @Override
+ public void readSettings(Object settings) {
+ wizardDescriptor = (WizardDescriptor) settings;
+ component.read(wizardDescriptor);
+ }
+
+ @Override
+ public void storeSettings(Object settings) {
+ WizardDescriptor d = (WizardDescriptor) settings;
+ component.store(d);
+ }
+
+ @Override
+ public boolean isFinishPanel() {
+ return false;
+ }
+
+ @Override
+ public void validate() throws WizardValidationException {
+ getComponent();
+ component.validate(wizardDescriptor);
+ }
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentDownloadPanelVisual.form b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentDownloadPanelVisual.form
new file mode 100644
index 00000000..9caac7e1
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentDownloadPanelVisual.form
@@ -0,0 +1,120 @@
+
+
+
diff --git a/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentDownloadPanelVisual.java b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentDownloadPanelVisual.java
new file mode 100644
index 00000000..04f83bef
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentDownloadPanelVisual.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 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.templates.jaimesascent;
+
+import javax.swing.JPanel;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import org.openide.WizardDescriptor;
+import org.openide.WizardValidationException;
+
+public class JaimesAscentDownloadPanelVisual extends JPanel implements DocumentListener {
+
+ public static final String PROP_PROJECT_NAME = "projectName";
+ private final JaimesAscentDownloadPanel panel;
+
+ public JaimesAscentDownloadPanelVisual(JaimesAscentDownloadPanel panel) {
+ initComponents();
+ this.panel = panel;
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ downloadButton = new javax.swing.JButton();
+ jScrollPane1 = new javax.swing.JScrollPane();
+ jTextArea1 = new javax.swing.JTextArea();
+ statusField = new javax.swing.JTextField();
+ jLabel1 = new javax.swing.JLabel();
+
+ org.openide.awt.Mnemonics.setLocalizedText(downloadButton, org.openide.util.NbBundle.getMessage(JaimesAscentDownloadPanelVisual.class, "JaimesAscentDownloadPanelVisual.downloadButton.text")); // NOI18N
+ downloadButton.setActionCommand(org.openide.util.NbBundle.getMessage(JaimesAscentDownloadPanelVisual.class, "JaimesAscentDownloadPanelVisual.downloadButton.actionCommand")); // NOI18N
+ downloadButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ downloadButtonActionPerformed(evt);
+ }
+ });
+
+ jTextArea1.setEditable(false);
+ jTextArea1.setColumns(20);
+ jTextArea1.setLineWrap(true);
+ jTextArea1.setRows(5);
+ jTextArea1.setText(org.openide.util.NbBundle.getMessage(JaimesAscentDownloadPanelVisual.class, "JaimesAscentDownloadPanelVisual.jTextArea1.text")); // NOI18N
+ jTextArea1.setWrapStyleWord(true);
+ jTextArea1.setEnabled(false);
+ jScrollPane1.setViewportView(jTextArea1);
+
+ statusField.setEditable(false);
+ statusField.setText(org.openide.util.NbBundle.getMessage(JaimesAscentDownloadPanelVisual.class, "JaimesAscentDownloadPanelVisual.statusField.text")); // NOI18N
+ statusField.setEnabled(false);
+ statusField.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ statusFieldActionPerformed(evt);
+ }
+ });
+
+ jLabel1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/jme3/gde/templates/jaimesascent/jaimesascent.png"))); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(JaimesAscentDownloadPanelVisual.class, "JaimesAscentDownloadPanelVisual.jLabel1.text")); // NOI18N
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(jLabel1)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 307, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(statusField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 313, javax.swing.GroupLayout.PREFERRED_SIZE)))
+ .addGroup(layout.createSequentialGroup()
+ .addGap(99, 99, 99)
+ .addComponent(downloadButton)))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(71, 71, 71)
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGap(21, 21, 21)
+ .addComponent(downloadButton)
+ .addGap(18, 18, 18)
+ .addComponent(statusField, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(jLabel1)))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+ }// //GEN-END:initComponents
+
+ private void downloadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_downloadButtonActionPerformed
+ statusField.setText(org.openide.util.NbBundle.getMessage(JaimesAscentDownloadPanelVisual.class, "JaimesAscentDownloadPanelVisual.downloading"));
+ final int result = this.panel.doDownloadZip();
+ if (result == 1) {
+ statusField.setText(org.openide.util.NbBundle.getMessage(JaimesAscentDownloadPanelVisual.class, "JaimesAscentDownloadPanelVisual.downloadSuccess"));
+ } else {
+ statusField.setText(org.openide.util.NbBundle.getMessage(JaimesAscentDownloadPanelVisual.class, "JaimesAscentDownloadPanelVisual.downloadFailed"));
+ }
+ }//GEN-LAST:event_downloadButtonActionPerformed
+
+ private void statusFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_statusFieldActionPerformed
+
+ }//GEN-LAST:event_statusFieldActionPerformed
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ javax.swing.JButton downloadButton;
+ javax.swing.JLabel jLabel1;
+ javax.swing.JScrollPane jScrollPane1;
+ javax.swing.JTextArea jTextArea1;
+ javax.swing.JTextField statusField;
+ // End of variables declaration//GEN-END:variables
+
+ @Override
+ public void addNotify() {
+ super.addNotify();
+ }
+
+ boolean valid(WizardDescriptor wizardDescriptor) {
+
+ return true;
+ }
+
+ void store(WizardDescriptor d) {
+
+ }
+
+ void read(WizardDescriptor settings) {
+
+ }
+
+ void validate(WizardDescriptor d) throws WizardValidationException {
+ // nothing to validate
+ }
+
+ // Implementation of DocumentListener --------------------------------------
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+
+ }
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ updateTexts(e);
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ updateTexts(e);
+ }
+
+ /** Handles changes in the Project name and project directory, */
+ private void updateTexts(DocumentEvent e) {
+
+ panel.fireChangeEvent(); // Notify that the panel changed
+ }
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentPanelVisual.form b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentPanelVisual.form
new file mode 100644
index 00000000..6451a2c5
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentPanelVisual.form
@@ -0,0 +1,122 @@
+
+
+
diff --git a/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentPanelVisual.java b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentPanelVisual.java
new file mode 100644
index 00000000..79eb619c
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentPanelVisual.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 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.templates.jaimesascent;
+
+import java.io.File;
+import javax.swing.JFileChooser;
+import javax.swing.JPanel;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.Document;
+import org.netbeans.spi.project.ui.support.ProjectChooser;
+import org.openide.WizardDescriptor;
+import org.openide.WizardValidationException;
+import org.openide.filesystems.FileUtil;
+
+public class JaimesAscentPanelVisual extends JPanel implements DocumentListener {
+
+ public static final String PROP_PROJECT_NAME = "projectName";
+ static final String PROJECT_NAME = "JaimesAscent";
+ private final JaimesAscentWizardPanel panel;
+
+ public JaimesAscentPanelVisual(JaimesAscentWizardPanel panel) {
+ initComponents();
+ this.panel = panel;
+ // Register listener on the textFields to make the automatic updates
+ projectNameTextField.getDocument().addDocumentListener(this);
+ projectLocationTextField.getDocument().addDocumentListener(this);
+ }
+
+ public String getProjectName() {
+ return this.projectNameTextField.getText();
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ projectNameLabel = new javax.swing.JLabel();
+ projectNameTextField = new javax.swing.JTextField();
+ projectLocationLabel = new javax.swing.JLabel();
+ projectLocationTextField = new javax.swing.JTextField();
+ browseButton = new javax.swing.JButton();
+ createdFolderLabel = new javax.swing.JLabel();
+ createdFolderTextField = new javax.swing.JTextField();
+
+ projectNameLabel.setLabelFor(projectNameTextField);
+ org.openide.awt.Mnemonics.setLocalizedText(projectNameLabel, org.openide.util.NbBundle.getMessage(JaimesAscentPanelVisual.class, "JaimesAscentPanelVisual.projectNameLabel.text")); // NOI18N
+
+ projectLocationLabel.setLabelFor(projectLocationTextField);
+ org.openide.awt.Mnemonics.setLocalizedText(projectLocationLabel, org.openide.util.NbBundle.getMessage(JaimesAscentPanelVisual.class, "JaimesAscentPanelVisual.projectLocationLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(JaimesAscentPanelVisual.class, "JaimesAscentPanelVisual.browseButton.text")); // NOI18N
+ browseButton.setActionCommand(org.openide.util.NbBundle.getMessage(JaimesAscentPanelVisual.class, "JaimesAscentPanelVisual.browseButton.actionCommand")); // NOI18N
+ browseButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ browseButtonActionPerformed(evt);
+ }
+ });
+
+ createdFolderLabel.setLabelFor(createdFolderTextField);
+ org.openide.awt.Mnemonics.setLocalizedText(createdFolderLabel, org.openide.util.NbBundle.getMessage(JaimesAscentPanelVisual.class, "JaimesAscentPanelVisual.createdFolderLabel.text")); // NOI18N
+
+ createdFolderTextField.setEditable(false);
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(projectNameLabel)
+ .addComponent(projectLocationLabel)
+ .addComponent(createdFolderLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(projectNameTextField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 191, Short.MAX_VALUE)
+ .addComponent(projectLocationTextField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 191, Short.MAX_VALUE)
+ .addComponent(createdFolderTextField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 191, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(browseButton)
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(projectNameLabel)
+ .addComponent(projectNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(projectLocationLabel)
+ .addComponent(projectLocationTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(browseButton))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(createdFolderLabel)
+ .addComponent(createdFolderTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addContainerGap(213, Short.MAX_VALUE))
+ );
+ }// //GEN-END:initComponents
+
+ private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
+ String command = evt.getActionCommand();
+ if ("BROWSE".equals(command)) {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setCurrentDirectory(null);
+ chooser.setDialogTitle("Select Project Location");
+ chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ String path = this.projectLocationTextField.getText();
+ if (path.length() > 0) {
+ File f = new File(path);
+ if (f.exists()) {
+ chooser.setSelectedFile(f);
+ }
+ }
+ if (JFileChooser.APPROVE_OPTION == chooser.showOpenDialog(this)) {
+ File projectDir = chooser.getSelectedFile();
+ projectLocationTextField.setText(FileUtil.normalizeFile(projectDir).getAbsolutePath());
+ }
+ panel.fireChangeEvent();
+ }
+
+ }//GEN-LAST:event_browseButtonActionPerformed
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ javax.swing.JButton browseButton;
+ javax.swing.JLabel createdFolderLabel;
+ javax.swing.JTextField createdFolderTextField;
+ javax.swing.JLabel projectLocationLabel;
+ javax.swing.JTextField projectLocationTextField;
+ javax.swing.JLabel projectNameLabel;
+ javax.swing.JTextField projectNameTextField;
+ // End of variables declaration//GEN-END:variables
+
+ @Override
+ public void addNotify() {
+ super.addNotify();
+ //same problem as in 31086, initial focus on Cancel button
+ projectNameTextField.requestFocus();
+ }
+
+ boolean valid(WizardDescriptor wizardDescriptor) {
+
+ if (projectNameTextField.getText().length() == 0) {
+ // TODO if using org.openide.dialogs >= 7.8, can use WizardDescriptor.PROP_ERROR_MESSAGE:
+ wizardDescriptor.putProperty("WizardPanel_errorMessage",
+ "Project Name is not a valid folder name.");
+ return false; // Display name not specified
+ }
+ File f = FileUtil.normalizeFile(new File(projectLocationTextField.getText()).getAbsoluteFile());
+ if (!f.isDirectory()) {
+ String message = "Project Folder is not a valid path.";
+ wizardDescriptor.putProperty("WizardPanel_errorMessage", message);
+ return false;
+ }
+ final File destFolder = FileUtil.normalizeFile(new File(createdFolderTextField.getText()).getAbsoluteFile());
+
+ File projLoc = destFolder;
+ while (projLoc != null && !projLoc.exists()) {
+ projLoc = projLoc.getParentFile();
+ }
+ if (projLoc == null || !projLoc.canWrite()) {
+ wizardDescriptor.putProperty("WizardPanel_errorMessage",
+ "Project Folder cannot be created.");
+ return false;
+ }
+
+ if (FileUtil.toFileObject(projLoc) == null) {
+ String message = "Project Folder is not a valid path.";
+ wizardDescriptor.putProperty("WizardPanel_errorMessage", message);
+ return false;
+ }
+
+ File[] kids = destFolder.listFiles();
+ if (destFolder.exists() && kids != null && kids.length > 0) {
+ // Folder exists and is not empty
+ wizardDescriptor.putProperty("WizardPanel_errorMessage",
+ "Project Folder already exists and is not empty.");
+ return false;
+ }
+ wizardDescriptor.putProperty("WizardPanel_errorMessage", "");
+ return true;
+ }
+
+ void store(WizardDescriptor d) {
+ String name = projectNameTextField.getText().trim();
+ String folder = createdFolderTextField.getText().trim();
+
+ d.putProperty("projdir", new File(folder));
+ d.putProperty("name", name);
+ }
+
+ void read(WizardDescriptor settings) {
+ File projectLocation = (File) settings.getProperty("projdir");
+ if (projectLocation == null || projectLocation.getParentFile() == null || !projectLocation.getParentFile().isDirectory()) {
+ projectLocation = ProjectChooser.getProjectsFolder();
+ } else {
+ projectLocation = projectLocation.getParentFile();
+ }
+ this.projectLocationTextField.setText(projectLocation.getAbsolutePath());
+
+ String projectName = (String) settings.getProperty("name");
+ if (projectName == null) {
+ projectName = PROJECT_NAME;
+ }
+ this.projectNameTextField.setText(projectName);
+ this.projectNameTextField.selectAll();
+ }
+
+ void validate(WizardDescriptor d) throws WizardValidationException {
+ // nothing to validate
+ }
+
+ // Implementation of DocumentListener --------------------------------------
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ updateTexts(e);
+ if (this.projectNameTextField.getDocument() == e.getDocument()) {
+ firePropertyChange(PROP_PROJECT_NAME, null, this.projectNameTextField.getText());
+ }
+ }
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ updateTexts(e);
+ if (this.projectNameTextField.getDocument() == e.getDocument()) {
+ firePropertyChange(PROP_PROJECT_NAME, null, this.projectNameTextField.getText());
+ }
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ updateTexts(e);
+ if (this.projectNameTextField.getDocument() == e.getDocument()) {
+ firePropertyChange(PROP_PROJECT_NAME, null, this.projectNameTextField.getText());
+ }
+ }
+
+ /** Handles changes in the Project name and project directory, */
+ private void updateTexts(DocumentEvent e) {
+
+ Document doc = e.getDocument();
+
+ if (doc == projectNameTextField.getDocument() || doc == projectLocationTextField.getDocument()) {
+ // Change in the project name
+
+ String projectName = projectNameTextField.getText();
+ String projectFolder = projectLocationTextField.getText();
+
+ //if (projectFolder.trim().length() == 0 || projectFolder.equals(oldName)) {
+ createdFolderTextField.setText(projectFolder + File.separatorChar + projectName);
+ //}
+
+ }
+ panel.fireChangeEvent(); // Notify that the panel changed
+ }
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentWizardIterator.java b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentWizardIterator.java
new file mode 100644
index 00000000..9dce2a72
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentWizardIterator.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 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.templates.jaimesascent;
+
+import com.jme3.gde.templates.gradledesktop.options.CachedOptionsContainer;
+import java.awt.Component;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.MessageFormat;
+import java.util.Enumeration;
+import java.util.LinkedHashSet;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import javax.swing.JComponent;
+import javax.swing.event.ChangeListener;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.spi.project.ui.support.ProjectChooser;
+import org.openide.WizardDescriptor;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.NbBundle;
+
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class JaimesAscentWizardIterator implements WizardDescriptor.InstantiatingIterator {
+
+ private int index;
+ private WizardDescriptor.Panel[] panels;
+ private WizardDescriptor wiz;
+
+ final static String master = "JaimesAscent-1.1.1/";
+
+ public JaimesAscentWizardIterator() {
+
+ // Initiate the options getting...
+ CachedOptionsContainer.getInstance();
+ }
+
+ public static JaimesAscentWizardIterator createIterator() {
+ return new JaimesAscentWizardIterator();
+ }
+
+ private WizardDescriptor.Panel[] createPanels() {
+ return new WizardDescriptor.Panel[]{
+ new JaimesAscentDownloadPanel(),
+ new JaimesAscentWizardPanel()
+ };
+ }
+
+ private String[] createSteps() {
+ return new String[]{
+ NbBundle.getMessage(JaimesAscentWizardIterator.class, "LBL_DownloadProjectStep"),
+ NbBundle.getMessage(JaimesAscentWizardIterator.class, "LBL_CreateProjectStep"),
+ };
+ }
+
+ @Override
+ public Set/**/ instantiate(/*ProgressHandle handle*/) throws IOException {
+ Set resultSet = new LinkedHashSet<>();
+ File dirF = FileUtil.normalizeFile((File) wiz.getProperty("projdir"));
+ dirF.mkdirs();
+
+ FileObject template = FileUtil.toFileObject(new File(
+ JaimesAscentDownloadPanel.DOWNLOAD_FOLDER,
+ JaimesAscentDownloadPanel.ZIP_NAME));
+
+ FileObject dir = FileUtil.toFileObject(dirF);
+ unZipFile(template.getInputStream(), dir);
+
+ // Always open top dir as a project:
+ resultSet.add(dir);
+ // Look for nested projects to open as well:
+ Enumeration extends FileObject> e = dir.getFolders(true);
+ while (e.hasMoreElements()) {
+ FileObject subfolder = e.nextElement();
+ if (ProjectManager.getDefault().isProject(subfolder)) {
+ resultSet.add(subfolder);
+ }
+ }
+
+ File parent = dirF.getParentFile();
+ if (parent != null && parent.exists()) {
+ ProjectChooser.setProjectsFolder(parent);
+ }
+
+ return resultSet;
+ }
+
+ @Override
+ public void initialize(WizardDescriptor wiz) {
+ this.wiz = wiz;
+ index = 0;
+ panels = createPanels();
+ // Make sure list of steps is accurate.
+ String[] steps = createSteps();
+ for (int i = 0; i < panels.length; i++) {
+ Component c = panels[i].getComponent();
+ if (steps[i] == null) {
+ // Default step name to component name of panel.
+ // Mainly useful for getting the name of the target
+ // chooser to appear in the list of steps.
+ steps[i] = c.getName();
+ }
+ if (c instanceof JComponent jc) { // Step #.
+ // TODO if using org.openide.dialogs >= 7.8, can use WizardDescriptor.PROP_*:
+ jc.putClientProperty("WizardPanel_contentSelectedIndex", i);
+ // Step name (actually the whole list for reference).
+ jc.putClientProperty("WizardPanel_contentData", steps);
+ }
+ }
+ }
+
+ @Override
+ public void uninitialize(WizardDescriptor wiz) {
+ this.wiz.putProperty("projdir", null);
+ this.wiz.putProperty("name", null);
+ this.wiz = null;
+ panels = null;
+ }
+
+ @Override
+ public String name() {
+ return MessageFormat.format("{0} of {1}",
+ new Object[]{index + 1, panels.length});
+ }
+
+ @Override
+ public boolean hasNext() {
+ return index < panels.length - 1;
+ }
+
+ @Override
+ public boolean hasPrevious() {
+ return index > 0;
+ }
+
+ @Override
+ public void nextPanel() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ index++;
+ }
+
+ @Override
+ public void previousPanel() {
+ if (!hasPrevious()) {
+ throw new NoSuchElementException();
+ }
+ index--;
+ }
+
+ @Override
+ public WizardDescriptor.Panel current() {
+ return panels[index];
+ }
+
+ // If nothing unusual changes in the middle of the wizard, simply:
+ @Override
+ public final void addChangeListener(ChangeListener l) {
+ }
+
+ @Override
+ public final void removeChangeListener(ChangeListener l) {
+ }
+
+ private static void unZipFile(InputStream source, FileObject projectRoot) throws IOException {
+ try (source) {
+ ZipInputStream str = new ZipInputStream(source);
+ ZipEntry entry;
+
+ while ((entry = str.getNextEntry()) != null) {
+ if (entry.getName().endsWith(master)) {
+ continue;
+ }
+ final String entryName = entry.getName().replace(master, "");
+ if (entry.isDirectory()) {
+ FileUtil.createFolder(projectRoot, entryName);
+ } else {
+ FileObject fo = FileUtil.createData(projectRoot, entryName);
+ try (OutputStream out = fo.getOutputStream()) {
+ FileUtil.copy(str, out);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentWizardPanel.java b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentWizardPanel.java
new file mode 100644
index 00000000..25db6a3f
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/JaimesAscentWizardPanel.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 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.templates.jaimesascent;
+
+import java.awt.Component;
+import java.util.HashSet;
+import java.util.Set;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import org.openide.WizardDescriptor;
+import org.openide.WizardValidationException;
+import org.openide.util.HelpCtx;
+import org.openide.util.NbBundle;
+
+/**
+ * Panel just asking for basic info.
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class JaimesAscentWizardPanel implements WizardDescriptor.Panel,
+ WizardDescriptor.ValidatingPanel, WizardDescriptor.FinishablePanel {
+
+ private WizardDescriptor wizardDescriptor;
+ private JaimesAscentPanelVisual component;
+
+ public JaimesAscentWizardPanel() {
+ }
+
+ @Override
+ public Component getComponent() {
+ if (component == null) {
+ component = new JaimesAscentPanelVisual(this);
+ component.setName(NbBundle.getMessage(JaimesAscentWizardPanel.class, "LBL_CreateProjectStep"));
+ }
+ return component;
+ }
+
+ @Override
+ public HelpCtx getHelp() {
+ return new HelpCtx("sdk.project_creation");
+ }
+
+ @Override
+ public boolean isValid() {
+ getComponent();
+ return component.valid(wizardDescriptor);
+ }
+
+ private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0
+
+ @Override
+ public final void addChangeListener(ChangeListener l) {
+ synchronized (listeners) {
+ listeners.add(l);
+ }
+ }
+
+ @Override
+ public final void removeChangeListener(ChangeListener l) {
+ synchronized (listeners) {
+ listeners.remove(l);
+ }
+ }
+
+ protected final void fireChangeEvent() {
+ Set ls;
+ synchronized (listeners) {
+ ls = new HashSet<>(listeners);
+ }
+ ChangeEvent ev = new ChangeEvent(this);
+ for (ChangeListener l : ls) {
+ l.stateChanged(ev);
+ }
+ }
+
+ @Override
+ public void readSettings(Object settings) {
+ wizardDescriptor = (WizardDescriptor) settings;
+ component.read(wizardDescriptor);
+ }
+
+ @Override
+ public void storeSettings(Object settings) {
+ WizardDescriptor d = (WizardDescriptor) settings;
+ component.store(d);
+ }
+
+ @Override
+ public boolean isFinishPanel() {
+ return false;
+ }
+
+ @Override
+ public void validate() throws WizardValidationException {
+ getComponent();
+ component.validate(wizardDescriptor);
+ }
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/jaimesascent/build.gradle.ftl b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/build.gradle.ftl
new file mode 100644
index 00000000..f40ada91
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/build.gradle.ftl
@@ -0,0 +1,65 @@
+plugins {
+ id 'java'
+ id 'application'
+}
+
+group 'com.JaimesAscent'
+version '1.0'
+
+mainClassName = "com.JaimesAscent.JaimesAscent"
+
+repositories {
+ mavenCentral()
+}
+
+project.ext {
+ jmeVer = '3.7.0-stable'
+}
+
+project(":assets") {
+ apply plugin: "java"
+
+ buildDir = rootProject.file("build/assets")
+
+ sourceSets {
+ main {
+ resources {
+ srcDir '.'
+ }
+ }
+ }
+
+ java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+ }
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+}
+
+dependencies {
+
+ implementation "org.jmonkeyengine:jme3-core:$jmeVer"
+ implementation "org.jmonkeyengine:jme3-desktop:$jmeVer"
+ implementation "org.jmonkeyengine:jme3-lwjgl:$jmeVer"
+ implementation "org.jmonkeyengine:jme3-lwjgl:$jmeVer"
+ implementation "com.github.stephengold:Heart:9.0.0"
+ implementation "com.github.stephengold:Minie:8.0.0"
+ implementation project("assets")
+
+}
+
+jar {
+ manifest {
+ attributes 'Main-Class': "$mainClassName"
+ }
+}
+
+wrapper {
+ gradleVersion = '8.6'
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/jaimesascent/jaimesascent.png b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/jaimesascent.png
new file mode 100644
index 00000000..aa686c8f
Binary files /dev/null and b/jme3-templates/src/com/jme3/gde/templates/jaimesascent/jaimesascent.png differ
diff --git a/jme3-templates/src/com/jme3/gde/templates/layer.xml b/jme3-templates/src/com/jme3/gde/templates/layer.xml
index 001c24b8..e631ab51 100644
--- a/jme3-templates/src/com/jme3/gde/templates/layer.xml
+++ b/jme3-templates/src/com/jme3/gde/templates/layer.xml
@@ -25,6 +25,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jme3-templates/src/com/jme3/gde/templates/monkeyzone/Bundle.properties b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/Bundle.properties
new file mode 100644
index 00000000..8351f695
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/Bundle.properties
@@ -0,0 +1,16 @@
+LBL_DownloadProjectStep=Download project
+LBL_CreateProjectStep=Name and Location
+MonkeyZonePanelVisual.browseButton.text=Br&owse...
+MonkeyZoneDownloadPanelVisual.downloading=Downloading... Please wait
+MonkeyZoneDownloadPanelVisual.downloadSuccess=Download complete. Press 'next' to proceed.
+MonkeyZoneDownloadPanelVisual.downloadFailed=Download failed.
+MonkeyZoneDownloadPanelVisual.downloadButton.actionCommand=DOWNLOAD
+MonkeyZoneDownloadPanelVisual.downloadButton.text=Download project
+MonkeyZoneDownloadPanelVisual.jLabel1.text=
+MonkeyZoneDownloadPanelVisual.statusField.text=
+MonkeyZoneDownloadPanelVisual.jTextArea1.text=Pressing the button below will download the project from Github, to a temporary location from which it will be installed in the next step\n
+MonkeyZonePanelVisual.projectLocationLabel.text=Project &Location:
+MonkeyZonePanelVisual.projectNameLabel.text=Project &Name:
+MonkeyZonePanelVisual.createdFolderLabel.text=Project &Folder:
+MonkeyZonePanelVisual.browseButton.actionCommand=BROWSE
+MonkeyZonePanelVisual.browseButton.text=Browse
diff --git a/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneDescription.html b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneDescription.html
new file mode 100644
index 00000000..cb9ab6f0
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneDescription.html
@@ -0,0 +1,13 @@
+
+
+
+ Monkey Zone
+
+
+ A networked multiplayer game by the jMonkeyEngine team.
+
+ Inspired by the classic game BattleZone, this is a networked multiplayer
+ game with both on foot action and enterable vehicles.
+ This is not a beginner project.
+
+
diff --git a/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneDownloadPanel.java b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneDownloadPanel.java
new file mode 100644
index 00000000..64f4802b
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneDownloadPanel.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 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.templates.monkeyzone;
+
+import java.awt.Component;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import org.openide.WizardDescriptor;
+import org.openide.WizardValidationException;
+import org.openide.util.HelpCtx;
+import org.openide.util.NbBundle;
+
+/**
+ * Panel just asking for basic info.
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class MonkeyZoneDownloadPanel implements WizardDescriptor.Panel,
+ WizardDescriptor.ValidatingPanel, WizardDescriptor.FinishablePanel {
+
+ private WizardDescriptor wizardDescriptor;
+ private MonkeyZoneDownloadPanelVisual component;
+
+ static String ZIP_NAME = "MonkeyZone.zip";
+ static String DOWNLOAD_FOLDER = System.getProperty("java.io.tmpdir");
+
+ public MonkeyZoneDownloadPanel() {
+ }
+
+ public int doDownloadZip() {
+ return downloadFile("https://github.com/jMonkeyEngine/monkeyzone/archive/refs/heads/master.zip", DOWNLOAD_FOLDER, ZIP_NAME);
+ }
+
+ private int downloadFile(String fileURL, String saveDir, String fileName) {
+ HttpURLConnection httpConn = null;
+ BufferedInputStream inputStream = null;
+ FileOutputStream fileOutputStream = null;
+
+ final File outputFile = new File(saveDir, fileName);
+
+ if (outputFile.exists()) {
+ return 1;
+ }
+
+ try {
+ // Create URL object
+ URL url = new URL(fileURL);
+ httpConn = (HttpURLConnection) url.openConnection();
+
+ // Check HTTP response code
+ int responseCode = httpConn.getResponseCode();
+ if (responseCode == HttpURLConnection.HTTP_OK) {
+ // Open input stream from the HTTP connection
+ inputStream = new BufferedInputStream(httpConn.getInputStream());
+
+
+
+ // Create output stream to save the file
+ fileOutputStream = new FileOutputStream(outputFile);
+
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ fileOutputStream.write(buffer, 0, bytesRead);
+ }
+
+ return 1;
+ }
+ } catch (IOException e) {
+ return 0;
+ } finally {
+ // Close resources
+ try {
+ if (inputStream != null) inputStream.close();
+ if (fileOutputStream != null) fileOutputStream.close();
+ if (httpConn != null) httpConn.disconnect();
+ } catch (IOException ex) {
+ return 0;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public Component getComponent() {
+ if (component == null) {
+ component = new MonkeyZoneDownloadPanelVisual(this);
+ component.setName(NbBundle.getMessage(MonkeyZoneDownloadPanel.class, "LBL_DownloadProjectStep"));
+ }
+ return component;
+ }
+
+ @Override
+ public HelpCtx getHelp() {
+ return new HelpCtx("sdk.download_project");
+ }
+
+ @Override
+ public boolean isValid() {
+ getComponent();
+ return component.valid(wizardDescriptor);
+ }
+
+ private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0
+
+ @Override
+ public final void addChangeListener(ChangeListener l) {
+ synchronized (listeners) {
+ listeners.add(l);
+ }
+ }
+
+ @Override
+ public final void removeChangeListener(ChangeListener l) {
+ synchronized (listeners) {
+ listeners.remove(l);
+ }
+ }
+
+ protected final void fireChangeEvent() {
+ Set ls;
+ synchronized (listeners) {
+ ls = new HashSet<>(listeners);
+ }
+ ChangeEvent ev = new ChangeEvent(this);
+ for (ChangeListener l : ls) {
+ l.stateChanged(ev);
+ }
+ }
+
+ @Override
+ public void readSettings(Object settings) {
+ wizardDescriptor = (WizardDescriptor) settings;
+ component.read(wizardDescriptor);
+ }
+
+ @Override
+ public void storeSettings(Object settings) {
+ WizardDescriptor d = (WizardDescriptor) settings;
+ component.store(d);
+ }
+
+ @Override
+ public boolean isFinishPanel() {
+ return false;
+ }
+
+ @Override
+ public void validate() throws WizardValidationException {
+ getComponent();
+ component.validate(wizardDescriptor);
+ }
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneDownloadPanelVisual.form b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneDownloadPanelVisual.form
new file mode 100644
index 00000000..57d50cb2
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneDownloadPanelVisual.form
@@ -0,0 +1,120 @@
+
+
+
diff --git a/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneDownloadPanelVisual.java b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneDownloadPanelVisual.java
new file mode 100644
index 00000000..cdde5187
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneDownloadPanelVisual.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 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.templates.monkeyzone;
+
+import javax.swing.JPanel;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import org.openide.WizardDescriptor;
+import org.openide.WizardValidationException;
+
+public class MonkeyZoneDownloadPanelVisual extends JPanel implements DocumentListener {
+
+ public static final String PROP_PROJECT_NAME = "projectName";
+ private final MonkeyZoneDownloadPanel panel;
+
+ public MonkeyZoneDownloadPanelVisual(MonkeyZoneDownloadPanel panel) {
+ initComponents();
+ this.panel = panel;
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ downloadButton = new javax.swing.JButton();
+ jScrollPane1 = new javax.swing.JScrollPane();
+ jTextArea1 = new javax.swing.JTextArea();
+ statusField = new javax.swing.JTextField();
+ jLabel1 = new javax.swing.JLabel();
+
+ org.openide.awt.Mnemonics.setLocalizedText(downloadButton, org.openide.util.NbBundle.getMessage(MonkeyZoneDownloadPanelVisual.class, "MonkeyZoneDownloadPanelVisual.downloadButton.text")); // NOI18N
+ downloadButton.setActionCommand(org.openide.util.NbBundle.getMessage(MonkeyZoneDownloadPanelVisual.class, "MonkeyZoneDownloadPanelVisual.downloadButton.actionCommand")); // NOI18N
+ downloadButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ downloadButtonActionPerformed(evt);
+ }
+ });
+
+ jTextArea1.setEditable(false);
+ jTextArea1.setColumns(20);
+ jTextArea1.setLineWrap(true);
+ jTextArea1.setRows(5);
+ jTextArea1.setText(org.openide.util.NbBundle.getMessage(MonkeyZoneDownloadPanelVisual.class, "MonkeyZoneDownloadPanelVisual.jTextArea1.text")); // NOI18N
+ jTextArea1.setWrapStyleWord(true);
+ jTextArea1.setEnabled(false);
+ jScrollPane1.setViewportView(jTextArea1);
+
+ statusField.setEditable(false);
+ statusField.setText(org.openide.util.NbBundle.getMessage(MonkeyZoneDownloadPanelVisual.class, "MonkeyZoneDownloadPanelVisual.statusField.text")); // NOI18N
+ statusField.setEnabled(false);
+ statusField.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ statusFieldActionPerformed(evt);
+ }
+ });
+
+ jLabel1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/jme3/gde/templates/monkeyzone/monkeyzone.png"))); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(MonkeyZoneDownloadPanelVisual.class, "MonkeyZoneDownloadPanelVisual.jLabel1.text")); // NOI18N
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(jLabel1)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 307, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(statusField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 313, javax.swing.GroupLayout.PREFERRED_SIZE)))
+ .addGroup(layout.createSequentialGroup()
+ .addGap(99, 99, 99)
+ .addComponent(downloadButton)))
+ .addContainerGap(14, Short.MAX_VALUE))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(71, 71, 71)
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGap(21, 21, 21)
+ .addComponent(downloadButton)
+ .addGap(18, 18, 18)
+ .addComponent(statusField, javax.swing.GroupLayout.PREFERRED_SIZE, 52, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(jLabel1)))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+ }// //GEN-END:initComponents
+
+ private void downloadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_downloadButtonActionPerformed
+ statusField.setText(org.openide.util.NbBundle.getMessage(MonkeyZoneDownloadPanelVisual.class, "MonkeyZoneDownloadPanelVisual.downloading"));
+ final int result = this.panel.doDownloadZip();
+ if (result == 1) {
+ statusField.setText(org.openide.util.NbBundle.getMessage(MonkeyZoneDownloadPanelVisual.class, "MonkeyZoneDownloadPanelVisual.downloadSuccess"));
+ } else {
+ statusField.setText(org.openide.util.NbBundle.getMessage(MonkeyZoneDownloadPanelVisual.class, "MonkeyZoneDownloadPanelVisual.downloadFailed"));
+ }
+ }//GEN-LAST:event_downloadButtonActionPerformed
+
+ private void statusFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_statusFieldActionPerformed
+
+ }//GEN-LAST:event_statusFieldActionPerformed
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ javax.swing.JButton downloadButton;
+ javax.swing.JLabel jLabel1;
+ javax.swing.JScrollPane jScrollPane1;
+ javax.swing.JTextArea jTextArea1;
+ javax.swing.JTextField statusField;
+ // End of variables declaration//GEN-END:variables
+
+ @Override
+ public void addNotify() {
+ super.addNotify();
+ }
+
+ boolean valid(WizardDescriptor wizardDescriptor) {
+
+ return true;
+ }
+
+ void store(WizardDescriptor d) {
+
+ }
+
+ void read(WizardDescriptor settings) {
+
+ }
+
+ void validate(WizardDescriptor d) throws WizardValidationException {
+ // nothing to validate
+ }
+
+ // Implementation of DocumentListener --------------------------------------
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+
+ }
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ updateTexts(e);
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ updateTexts(e);
+ }
+
+ /** Handles changes in the Project name and project directory, */
+ private void updateTexts(DocumentEvent e) {
+
+ panel.fireChangeEvent(); // Notify that the panel changed
+ }
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZonePanelVisual.form b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZonePanelVisual.form
new file mode 100644
index 00000000..ddb652b7
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZonePanelVisual.form
@@ -0,0 +1,122 @@
+
+
+
diff --git a/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZonePanelVisual.java b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZonePanelVisual.java
new file mode 100644
index 00000000..f4cc3e63
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZonePanelVisual.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 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.templates.monkeyzone;
+
+import java.io.File;
+import javax.swing.JFileChooser;
+import javax.swing.JPanel;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.Document;
+import org.netbeans.spi.project.ui.support.ProjectChooser;
+import org.openide.WizardDescriptor;
+import org.openide.WizardValidationException;
+import org.openide.filesystems.FileUtil;
+
+public class MonkeyZonePanelVisual extends JPanel implements DocumentListener {
+
+ public static final String PROP_PROJECT_NAME = "projectName";
+ static final String PROJECT_NAME = "MonkeyZone";
+ private final MonkeyZoneWizardPanel panel;
+
+ public MonkeyZonePanelVisual(MonkeyZoneWizardPanel panel) {
+ initComponents();
+ this.panel = panel;
+ // Register listener on the textFields to make the automatic updates
+ projectNameTextField.getDocument().addDocumentListener(this);
+ projectLocationTextField.getDocument().addDocumentListener(this);
+ }
+
+ public String getProjectName() {
+ return this.projectNameTextField.getText();
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ projectNameLabel = new javax.swing.JLabel();
+ projectNameTextField = new javax.swing.JTextField();
+ projectLocationLabel = new javax.swing.JLabel();
+ projectLocationTextField = new javax.swing.JTextField();
+ browseButton = new javax.swing.JButton();
+ createdFolderLabel = new javax.swing.JLabel();
+ createdFolderTextField = new javax.swing.JTextField();
+
+ projectNameLabel.setLabelFor(projectNameTextField);
+ org.openide.awt.Mnemonics.setLocalizedText(projectNameLabel, org.openide.util.NbBundle.getMessage(MonkeyZonePanelVisual.class, "MonkeyZonePanelVisual.projectNameLabel.text")); // NOI18N
+
+ projectLocationLabel.setLabelFor(projectLocationTextField);
+ org.openide.awt.Mnemonics.setLocalizedText(projectLocationLabel, org.openide.util.NbBundle.getMessage(MonkeyZonePanelVisual.class, "MonkeyZonePanelVisual.projectLocationLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(MonkeyZonePanelVisual.class, "MonkeyZonePanelVisual.browseButton.text")); // NOI18N
+ browseButton.setActionCommand(org.openide.util.NbBundle.getMessage(MonkeyZonePanelVisual.class, "MonkeyZonePanelVisual.browseButton.actionCommand")); // NOI18N
+ browseButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ browseButtonActionPerformed(evt);
+ }
+ });
+
+ createdFolderLabel.setLabelFor(createdFolderTextField);
+ org.openide.awt.Mnemonics.setLocalizedText(createdFolderLabel, org.openide.util.NbBundle.getMessage(MonkeyZonePanelVisual.class, "MonkeyZonePanelVisual.createdFolderLabel.text")); // NOI18N
+
+ createdFolderTextField.setEditable(false);
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(projectNameLabel)
+ .addComponent(projectLocationLabel)
+ .addComponent(createdFolderLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(projectNameTextField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 191, Short.MAX_VALUE)
+ .addComponent(projectLocationTextField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 191, Short.MAX_VALUE)
+ .addComponent(createdFolderTextField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 191, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(browseButton)
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(projectNameLabel)
+ .addComponent(projectNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(projectLocationLabel)
+ .addComponent(projectLocationTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(browseButton))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(createdFolderLabel)
+ .addComponent(createdFolderTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addContainerGap(213, Short.MAX_VALUE))
+ );
+ }// //GEN-END:initComponents
+
+ private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
+ String command = evt.getActionCommand();
+ if ("BROWSE".equals(command)) {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setCurrentDirectory(null);
+ chooser.setDialogTitle("Select Project Location");
+ chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ String path = this.projectLocationTextField.getText();
+ if (path.length() > 0) {
+ File f = new File(path);
+ if (f.exists()) {
+ chooser.setSelectedFile(f);
+ }
+ }
+ if (JFileChooser.APPROVE_OPTION == chooser.showOpenDialog(this)) {
+ File projectDir = chooser.getSelectedFile();
+ projectLocationTextField.setText(FileUtil.normalizeFile(projectDir).getAbsolutePath());
+ }
+ panel.fireChangeEvent();
+ }
+
+ }//GEN-LAST:event_browseButtonActionPerformed
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ javax.swing.JButton browseButton;
+ javax.swing.JLabel createdFolderLabel;
+ javax.swing.JTextField createdFolderTextField;
+ javax.swing.JLabel projectLocationLabel;
+ javax.swing.JTextField projectLocationTextField;
+ javax.swing.JLabel projectNameLabel;
+ javax.swing.JTextField projectNameTextField;
+ // End of variables declaration//GEN-END:variables
+
+ @Override
+ public void addNotify() {
+ super.addNotify();
+ //same problem as in 31086, initial focus on Cancel button
+ projectNameTextField.requestFocus();
+ }
+
+ boolean valid(WizardDescriptor wizardDescriptor) {
+
+ if (projectNameTextField.getText().length() == 0) {
+ // TODO if using org.openide.dialogs >= 7.8, can use WizardDescriptor.PROP_ERROR_MESSAGE:
+ wizardDescriptor.putProperty("WizardPanel_errorMessage",
+ "Project Name is not a valid folder name.");
+ return false; // Display name not specified
+ }
+ File f = FileUtil.normalizeFile(new File(projectLocationTextField.getText()).getAbsoluteFile());
+ if (!f.isDirectory()) {
+ String message = "Project Folder is not a valid path.";
+ wizardDescriptor.putProperty("WizardPanel_errorMessage", message);
+ return false;
+ }
+ final File destFolder = FileUtil.normalizeFile(new File(createdFolderTextField.getText()).getAbsoluteFile());
+
+ File projLoc = destFolder;
+ while (projLoc != null && !projLoc.exists()) {
+ projLoc = projLoc.getParentFile();
+ }
+ if (projLoc == null || !projLoc.canWrite()) {
+ wizardDescriptor.putProperty("WizardPanel_errorMessage",
+ "Project Folder cannot be created.");
+ return false;
+ }
+
+ if (FileUtil.toFileObject(projLoc) == null) {
+ String message = "Project Folder is not a valid path.";
+ wizardDescriptor.putProperty("WizardPanel_errorMessage", message);
+ return false;
+ }
+
+ File[] kids = destFolder.listFiles();
+ if (destFolder.exists() && kids != null && kids.length > 0) {
+ // Folder exists and is not empty
+ wizardDescriptor.putProperty("WizardPanel_errorMessage",
+ "Project Folder already exists and is not empty.");
+ return false;
+ }
+ wizardDescriptor.putProperty("WizardPanel_errorMessage", "");
+ return true;
+ }
+
+ void store(WizardDescriptor d) {
+ String name = projectNameTextField.getText().trim();
+ String folder = createdFolderTextField.getText().trim();
+
+ d.putProperty("projdir", new File(folder));
+ d.putProperty("name", name);
+ }
+
+ void read(WizardDescriptor settings) {
+ File projectLocation = (File) settings.getProperty("projdir");
+ if (projectLocation == null || projectLocation.getParentFile() == null || !projectLocation.getParentFile().isDirectory()) {
+ projectLocation = ProjectChooser.getProjectsFolder();
+ } else {
+ projectLocation = projectLocation.getParentFile();
+ }
+ this.projectLocationTextField.setText(projectLocation.getAbsolutePath());
+
+ String projectName = (String) settings.getProperty("name");
+ if (projectName == null) {
+ projectName = PROJECT_NAME;
+ }
+ this.projectNameTextField.setText(projectName);
+ this.projectNameTextField.selectAll();
+ }
+
+ void validate(WizardDescriptor d) throws WizardValidationException {
+ // nothing to validate
+ }
+
+ // Implementation of DocumentListener --------------------------------------
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ updateTexts(e);
+ if (this.projectNameTextField.getDocument() == e.getDocument()) {
+ firePropertyChange(PROP_PROJECT_NAME, null, this.projectNameTextField.getText());
+ }
+ }
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ updateTexts(e);
+ if (this.projectNameTextField.getDocument() == e.getDocument()) {
+ firePropertyChange(PROP_PROJECT_NAME, null, this.projectNameTextField.getText());
+ }
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ updateTexts(e);
+ if (this.projectNameTextField.getDocument() == e.getDocument()) {
+ firePropertyChange(PROP_PROJECT_NAME, null, this.projectNameTextField.getText());
+ }
+ }
+
+ /** Handles changes in the Project name and project directory, */
+ private void updateTexts(DocumentEvent e) {
+
+ Document doc = e.getDocument();
+
+ if (doc == projectNameTextField.getDocument() || doc == projectLocationTextField.getDocument()) {
+ // Change in the project name
+
+ String projectName = projectNameTextField.getText();
+ String projectFolder = projectLocationTextField.getText();
+
+ //if (projectFolder.trim().length() == 0 || projectFolder.equals(oldName)) {
+ createdFolderTextField.setText(projectFolder + File.separatorChar + projectName);
+ //}
+
+ }
+ panel.fireChangeEvent(); // Notify that the panel changed
+ }
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneWizardIterator.java b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneWizardIterator.java
new file mode 100644
index 00000000..50d5200f
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneWizardIterator.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2009-2010 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.templates.monkeyzone;
+
+import com.jme3.gde.templates.gradledesktop.options.CachedOptionsContainer;
+import java.awt.Component;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.util.Enumeration;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+import javax.swing.JComponent;
+import javax.swing.event.ChangeListener;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.spi.project.ui.support.ProjectChooser;
+import org.openide.WizardDescriptor;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.NbBundle;
+
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class MonkeyZoneWizardIterator implements WizardDescriptor.InstantiatingIterator {
+
+ private int index;
+ private WizardDescriptor.Panel[] panels;
+ private WizardDescriptor wiz;
+
+
+ final static String master = "monkeyzone-master/";
+
+
+ public MonkeyZoneWizardIterator() {
+
+ // Initiate the options getting...
+ CachedOptionsContainer.getInstance();
+ }
+
+ public static MonkeyZoneWizardIterator createIterator() {
+ return new MonkeyZoneWizardIterator();
+ }
+
+ private WizardDescriptor.Panel[] createPanels() {
+ return new WizardDescriptor.Panel[]{
+ new MonkeyZoneDownloadPanel(),
+ new MonkeyZoneWizardPanel()
+ };
+ }
+
+ private String[] createSteps() {
+ return new String[]{
+ NbBundle.getMessage(MonkeyZoneWizardIterator.class, "LBL_DownloadProjectStep"),
+ NbBundle.getMessage(MonkeyZoneWizardIterator.class, "LBL_CreateProjectStep"),
+ };
+ }
+
+ @Override
+ public Set/**/ instantiate(/*ProgressHandle handle*/) throws IOException {
+ Set resultSet = new LinkedHashSet<>();
+ File dirF = FileUtil.normalizeFile((File) wiz.getProperty("projdir"));
+ dirF.mkdirs();
+
+ FileObject template = FileUtil.toFileObject(new File(
+ MonkeyZoneDownloadPanel.DOWNLOAD_FOLDER,
+ MonkeyZoneDownloadPanel.ZIP_NAME));
+
+ FileObject dir = FileUtil.toFileObject(dirF);
+ unZipFile(template.getInputStream(), dir);
+
+ // Always open top dir as a project:
+ resultSet.add(dir);
+ // Look for nested projects to open as well:
+ Enumeration extends FileObject> e = dir.getFolders(true);
+ while (e.hasMoreElements()) {
+ FileObject subfolder = e.nextElement();
+ if (ProjectManager.getDefault().isProject(subfolder)) {
+ resultSet.add(subfolder);
+ }
+ }
+
+ File parent = dirF.getParentFile();
+ if (parent != null && parent.exists()) {
+ ProjectChooser.setProjectsFolder(parent);
+ }
+
+ return resultSet;
+ }
+
+ @Override
+ public void initialize(WizardDescriptor wiz) {
+ this.wiz = wiz;
+ index = 0;
+ panels = createPanels();
+ // Make sure list of steps is accurate.
+ String[] steps = createSteps();
+ for (int i = 0; i < panels.length; i++) {
+ Component c = panels[i].getComponent();
+ if (steps[i] == null) {
+ // Default step name to component name of panel.
+ // Mainly useful for getting the name of the target
+ // chooser to appear in the list of steps.
+ steps[i] = c.getName();
+ }
+ if (c instanceof JComponent jc) { // Step #.
+ // TODO if using org.openide.dialogs >= 7.8, can use WizardDescriptor.PROP_*:
+ jc.putClientProperty("WizardPanel_contentSelectedIndex", i);
+ // Step name (actually the whole list for reference).
+ jc.putClientProperty("WizardPanel_contentData", steps);
+ }
+ }
+ }
+
+ @Override
+ public void uninitialize(WizardDescriptor wiz) {
+ this.wiz.putProperty("projdir", null);
+ this.wiz.putProperty("name", null);
+ this.wiz = null;
+ panels = null;
+ }
+
+ @Override
+ public String name() {
+ return MessageFormat.format("{0} of {1}",
+ new Object[]{index + 1, panels.length});
+ }
+
+ @Override
+ public boolean hasNext() {
+ return index < panels.length - 1;
+ }
+
+ @Override
+ public boolean hasPrevious() {
+ return index > 0;
+ }
+
+ @Override
+ public void nextPanel() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ index++;
+ }
+
+ @Override
+ public void previousPanel() {
+ if (!hasPrevious()) {
+ throw new NoSuchElementException();
+ }
+ index--;
+ }
+
+ @Override
+ public WizardDescriptor.Panel current() {
+ return panels[index];
+ }
+
+ // If nothing unusual changes in the middle of the wizard, simply:
+ @Override
+ public final void addChangeListener(ChangeListener l) {
+ }
+
+ @Override
+ public final void removeChangeListener(ChangeListener l) {
+ }
+
+ private void createFileFromTemplate(File target, String templateResourcePath, Map tokens) throws IOException {
+
+ // Create FreeMarker script engine
+ ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
+ ScriptEngine engine = scriptEngineManager.getEngineByName("freemarker");
+ Map bindings = engine.getContext().getBindings(ScriptContext.ENGINE_SCOPE);
+ bindings.putAll(tokens);
+
+ // Process template
+ try {
+ FileObject targetFO = FileUtil.toFileObject(target);
+ try (Writer os = new BufferedWriter(new OutputStreamWriter(targetFO.getOutputStream(), StandardCharsets.UTF_8)); Reader is = new BufferedReader(new InputStreamReader(MonkeyZoneWizardIterator.class.getResourceAsStream("/" + templateResourcePath)));) {
+ engine.getContext().setWriter(os);
+ engine.eval(is);
+ }
+ } catch (IOException | ScriptException ex) {
+ throw new IOException(ex.getMessage(), ex);
+ }
+ }
+
+ private static void unZipFile(InputStream source, FileObject projectRoot) throws IOException {
+ try (source) {
+ ZipInputStream str = new ZipInputStream(source);
+ ZipEntry entry;
+ while ((entry = str.getNextEntry()) != null) {
+ if (entry.getName().endsWith(master)) {
+ continue;
+ }
+ final String entryName = entry.getName().replace(master, "");
+ if (entry.isDirectory()) {
+ FileUtil.createFolder(projectRoot, entryName);
+ } else {
+ FileObject fo = FileUtil.createData(projectRoot, entryName);
+ try (OutputStream out = fo.getOutputStream()) {
+ FileUtil.copy(str, out);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneWizardPanel.java b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneWizardPanel.java
new file mode 100644
index 00000000..57dcb16f
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/MonkeyZoneWizardPanel.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 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.templates.monkeyzone;
+
+import java.awt.Component;
+import java.util.HashSet;
+import java.util.Set;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import org.openide.WizardDescriptor;
+import org.openide.WizardValidationException;
+import org.openide.util.HelpCtx;
+import org.openide.util.NbBundle;
+
+/**
+ * Panel just asking for basic info.
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class MonkeyZoneWizardPanel implements WizardDescriptor.Panel,
+ WizardDescriptor.ValidatingPanel, WizardDescriptor.FinishablePanel {
+
+ private WizardDescriptor wizardDescriptor;
+ private MonkeyZonePanelVisual component;
+
+ public MonkeyZoneWizardPanel() {
+ }
+
+ @Override
+ public Component getComponent() {
+ if (component == null) {
+ component = new MonkeyZonePanelVisual(this);
+ component.setName(NbBundle.getMessage(MonkeyZoneWizardPanel.class, "LBL_CreateProjectStep"));
+ }
+ return component;
+ }
+
+ @Override
+ public HelpCtx getHelp() {
+ return new HelpCtx("sdk.project_creation");
+ }
+
+ @Override
+ public boolean isValid() {
+ getComponent();
+ return component.valid(wizardDescriptor);
+ }
+
+ private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0
+
+ @Override
+ public final void addChangeListener(ChangeListener l) {
+ synchronized (listeners) {
+ listeners.add(l);
+ }
+ }
+
+ @Override
+ public final void removeChangeListener(ChangeListener l) {
+ synchronized (listeners) {
+ listeners.remove(l);
+ }
+ }
+
+ protected final void fireChangeEvent() {
+ Set ls;
+ synchronized (listeners) {
+ ls = new HashSet<>(listeners);
+ }
+ ChangeEvent ev = new ChangeEvent(this);
+ for (ChangeListener l : ls) {
+ l.stateChanged(ev);
+ }
+ }
+
+ @Override
+ public void readSettings(Object settings) {
+ wizardDescriptor = (WizardDescriptor) settings;
+ component.read(wizardDescriptor);
+ }
+
+ @Override
+ public void storeSettings(Object settings) {
+ WizardDescriptor d = (WizardDescriptor) settings;
+ component.store(d);
+ }
+
+ @Override
+ public boolean isFinishPanel() {
+ return false;
+ }
+
+ @Override
+ public void validate() throws WizardValidationException {
+ getComponent();
+ component.validate(wizardDescriptor);
+ }
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/monkeyzone/build.gradle.ftl b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/build.gradle.ftl
new file mode 100644
index 00000000..8093c2a7
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/build.gradle.ftl
@@ -0,0 +1,65 @@
+plugins {
+ id 'java'
+ id 'application'
+}
+
+group 'com.monkeyzone'
+version '1.0'
+
+mainClassName = "com.monkeyzone.MonkeyZone"
+
+repositories {
+ mavenCentral()
+}
+
+project.ext {
+ jmeVer = '3.7.0-stable'
+}
+
+project(":assets") {
+ apply plugin: "java"
+
+ buildDir = rootProject.file("build/assets")
+
+ sourceSets {
+ main {
+ resources {
+ srcDir '.'
+ }
+ }
+ }
+
+ java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+ }
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+}
+
+dependencies {
+
+ implementation "org.jmonkeyengine:jme3-core:$jmeVer"
+ implementation "org.jmonkeyengine:jme3-desktop:$jmeVer"
+ implementation "org.jmonkeyengine:jme3-lwjgl:$jmeVer"
+ implementation "org.jmonkeyengine:jme3-lwjgl:$jmeVer"
+ implementation "com.github.stephengold:Heart:9.0.0"
+ implementation "com.github.stephengold:Minie:8.0.0"
+ implementation project("assets")
+
+}
+
+jar {
+ manifest {
+ attributes 'Main-Class': "$mainClassName"
+ }
+}
+
+wrapper {
+ gradleVersion = '8.6'
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/monkeyzone/monkeyzone.png b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/monkeyzone.png
new file mode 100644
index 00000000..562a0d59
Binary files /dev/null and b/jme3-templates/src/com/jme3/gde/templates/monkeyzone/monkeyzone.png differ
diff --git a/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/Bundle.properties b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/Bundle.properties
new file mode 100644
index 00000000..12750bdc
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/Bundle.properties
@@ -0,0 +1,6 @@
+LBL_CreateProjectStep=Name and Location
+RollingTheMonkeyPanelVisual.browseButton.text=Br&owse...
+RollingTheMonkeyPanelVisual.createdFolderLabel.text=Project &Folder:
+RollingTheMonkeyPanelVisual.projectNameLabel.text=Project &Name:
+RollingTheMonkeyPanelVisual.projectLocationLabel.text=Project &Location:
+RollingTheMonkeyPanelVisual.browseButton.actionCommand=BROWSE
diff --git a/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyDescription.html b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyDescription.html
new file mode 100644
index 00000000..2ae5760e
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyDescription.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ A simple physics based marble game. Made by SkidRunner (Mark E. Picknell).
+
+ Here are some ideas of how you can take it further:
+ 1. Add a timer and display the time it takes to pick up all the cubes
+ 2. Replace the texture of the sphere with one of your choosing.
+ 3. Change the physics of the sphere.
+
+
diff --git a/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyPanelVisual.form b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyPanelVisual.form
new file mode 100644
index 00000000..1ec44697
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyPanelVisual.form
@@ -0,0 +1,122 @@
+
+
+
diff --git a/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyPanelVisual.java b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyPanelVisual.java
new file mode 100644
index 00000000..90106282
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyPanelVisual.java
@@ -0,0 +1,263 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.gde.templates.rollingthemonkey;
+
+import java.io.File;
+import javax.swing.JFileChooser;
+import javax.swing.JPanel;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.Document;
+import org.netbeans.spi.project.ui.support.ProjectChooser;
+import org.openide.WizardDescriptor;
+import org.openide.WizardValidationException;
+import org.openide.filesystems.FileUtil;
+
+public class RollingTheMonkeyPanelVisual extends JPanel implements DocumentListener {
+
+ public static final String PROP_PROJECT_NAME = "projectName";
+ private final RollingTheMonkeyWizardPanel panel;
+
+ public RollingTheMonkeyPanelVisual(RollingTheMonkeyWizardPanel panel) {
+ initComponents();
+ this.panel = panel;
+ // Register listener on the textFields to make the automatic updates
+ projectNameTextField.getDocument().addDocumentListener(this);
+ projectLocationTextField.getDocument().addDocumentListener(this);
+ }
+
+ public String getProjectName() {
+ return this.projectNameTextField.getText();
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ projectNameLabel = new javax.swing.JLabel();
+ projectNameTextField = new javax.swing.JTextField();
+ projectLocationLabel = new javax.swing.JLabel();
+ projectLocationTextField = new javax.swing.JTextField();
+ browseButton = new javax.swing.JButton();
+ createdFolderLabel = new javax.swing.JLabel();
+ createdFolderTextField = new javax.swing.JTextField();
+
+ projectNameLabel.setLabelFor(projectNameTextField);
+ org.openide.awt.Mnemonics.setLocalizedText(projectNameLabel, org.openide.util.NbBundle.getMessage(RollingTheMonkeyPanelVisual.class, "RollingTheMonkeyPanelVisual.projectNameLabel.text")); // NOI18N
+
+ projectLocationLabel.setLabelFor(projectLocationTextField);
+ org.openide.awt.Mnemonics.setLocalizedText(projectLocationLabel, org.openide.util.NbBundle.getMessage(RollingTheMonkeyPanelVisual.class, "RollingTheMonkeyPanelVisual.projectLocationLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(RollingTheMonkeyPanelVisual.class, "RollingTheMonkeyPanelVisual.browseButton.text")); // NOI18N
+ browseButton.setActionCommand(org.openide.util.NbBundle.getMessage(RollingTheMonkeyPanelVisual.class, "RollingTheMonkeyPanelVisual.browseButton.actionCommand")); // NOI18N
+ browseButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ browseButtonActionPerformed(evt);
+ }
+ });
+
+ createdFolderLabel.setLabelFor(createdFolderTextField);
+ org.openide.awt.Mnemonics.setLocalizedText(createdFolderLabel, org.openide.util.NbBundle.getMessage(RollingTheMonkeyPanelVisual.class, "RollingTheMonkeyPanelVisual.createdFolderLabel.text")); // NOI18N
+
+ createdFolderTextField.setEditable(false);
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(projectNameLabel)
+ .addComponent(projectLocationLabel)
+ .addComponent(createdFolderLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(projectNameTextField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 191, Short.MAX_VALUE)
+ .addComponent(projectLocationTextField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 191, Short.MAX_VALUE)
+ .addComponent(createdFolderTextField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 191, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(browseButton)
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(projectNameLabel)
+ .addComponent(projectNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(projectLocationLabel)
+ .addComponent(projectLocationTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(browseButton))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(createdFolderLabel)
+ .addComponent(createdFolderTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addContainerGap(213, Short.MAX_VALUE))
+ );
+ }// //GEN-END:initComponents
+
+ private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
+ String command = evt.getActionCommand();
+ if ("BROWSE".equals(command)) {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setCurrentDirectory(null);
+ chooser.setDialogTitle("Select Project Location");
+ chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ String path = this.projectLocationTextField.getText();
+ if (path.length() > 0) {
+ File f = new File(path);
+ if (f.exists()) {
+ chooser.setSelectedFile(f);
+ }
+ }
+ if (JFileChooser.APPROVE_OPTION == chooser.showOpenDialog(this)) {
+ File projectDir = chooser.getSelectedFile();
+ projectLocationTextField.setText(FileUtil.normalizeFile(projectDir).getAbsolutePath());
+ }
+ panel.fireChangeEvent();
+ }
+
+ }//GEN-LAST:event_browseButtonActionPerformed
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ javax.swing.JButton browseButton;
+ javax.swing.JLabel createdFolderLabel;
+ javax.swing.JTextField createdFolderTextField;
+ javax.swing.JLabel projectLocationLabel;
+ javax.swing.JTextField projectLocationTextField;
+ javax.swing.JLabel projectNameLabel;
+ javax.swing.JTextField projectNameTextField;
+ // End of variables declaration//GEN-END:variables
+
+ @Override
+ public void addNotify() {
+ super.addNotify();
+ //same problem as in 31086, initial focus on Cancel button
+ projectNameTextField.requestFocus();
+ }
+
+ boolean valid(WizardDescriptor wizardDescriptor) {
+
+ if (projectNameTextField.getText().length() == 0) {
+ // TODO if using org.openide.dialogs >= 7.8, can use WizardDescriptor.PROP_ERROR_MESSAGE:
+ wizardDescriptor.putProperty("WizardPanel_errorMessage",
+ "Project Name is not a valid folder name.");
+ return false; // Display name not specified
+ }
+ File f = FileUtil.normalizeFile(new File(projectLocationTextField.getText()).getAbsoluteFile());
+ if (!f.isDirectory()) {
+ String message = "Project Folder is not a valid path.";
+ wizardDescriptor.putProperty("WizardPanel_errorMessage", message);
+ return false;
+ }
+ final File destFolder = FileUtil.normalizeFile(new File(createdFolderTextField.getText()).getAbsoluteFile());
+
+ File projLoc = destFolder;
+ while (projLoc != null && !projLoc.exists()) {
+ projLoc = projLoc.getParentFile();
+ }
+ if (projLoc == null || !projLoc.canWrite()) {
+ wizardDescriptor.putProperty("WizardPanel_errorMessage",
+ "Project Folder cannot be created.");
+ return false;
+ }
+
+ if (FileUtil.toFileObject(projLoc) == null) {
+ String message = "Project Folder is not a valid path.";
+ wizardDescriptor.putProperty("WizardPanel_errorMessage", message);
+ return false;
+ }
+
+ File[] kids = destFolder.listFiles();
+ if (destFolder.exists() && kids != null && kids.length > 0) {
+ // Folder exists and is not empty
+ wizardDescriptor.putProperty("WizardPanel_errorMessage",
+ "Project Folder already exists and is not empty.");
+ return false;
+ }
+ wizardDescriptor.putProperty("WizardPanel_errorMessage", "");
+ return true;
+ }
+
+ void store(WizardDescriptor d) {
+ String name = projectNameTextField.getText().trim();
+ String folder = createdFolderTextField.getText().trim();
+
+ d.putProperty("projdir", new File(folder));
+ d.putProperty("name", name);
+ }
+
+ void read(WizardDescriptor settings) {
+ File projectLocation = (File) settings.getProperty("projdir");
+ if (projectLocation == null || projectLocation.getParentFile() == null || !projectLocation.getParentFile().isDirectory()) {
+ projectLocation = ProjectChooser.getProjectsFolder();
+ } else {
+ projectLocation = projectLocation.getParentFile();
+ }
+ this.projectLocationTextField.setText(projectLocation.getAbsolutePath());
+
+ String projectName = (String) settings.getProperty("name");
+ if (projectName == null) {
+ projectName = "RollingTheMonkey";
+ }
+ this.projectNameTextField.setText(projectName);
+ this.projectNameTextField.selectAll();
+ }
+
+ void validate(WizardDescriptor d) throws WizardValidationException {
+ // nothing to validate
+ }
+
+ // Implementation of DocumentListener --------------------------------------
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ updateTexts(e);
+ if (this.projectNameTextField.getDocument() == e.getDocument()) {
+ firePropertyChange(PROP_PROJECT_NAME, null, this.projectNameTextField.getText());
+ }
+ }
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ updateTexts(e);
+ if (this.projectNameTextField.getDocument() == e.getDocument()) {
+ firePropertyChange(PROP_PROJECT_NAME, null, this.projectNameTextField.getText());
+ }
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ updateTexts(e);
+ if (this.projectNameTextField.getDocument() == e.getDocument()) {
+ firePropertyChange(PROP_PROJECT_NAME, null, this.projectNameTextField.getText());
+ }
+ }
+
+ /** Handles changes in the Project name and project directory, */
+ private void updateTexts(DocumentEvent e) {
+
+ Document doc = e.getDocument();
+
+ if (doc == projectNameTextField.getDocument() || doc == projectLocationTextField.getDocument()) {
+ // Change in the project name
+
+ String projectName = projectNameTextField.getText();
+ String projectFolder = projectLocationTextField.getText();
+
+ //if (projectFolder.trim().length() == 0 || projectFolder.equals(oldName)) {
+ createdFolderTextField.setText(projectFolder + File.separatorChar + projectName);
+ //}
+
+ }
+ panel.fireChangeEvent(); // Notify that the panel changed
+ }
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyWizardIterator.java b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyWizardIterator.java
new file mode 100644
index 00000000..f3afe777
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyWizardIterator.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2009-2010 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.templates.rollingthemonkey;
+
+import com.jme3.gde.templates.gradledesktop.options.CachedOptionsContainer;
+import java.awt.Component;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+import javax.swing.JComponent;
+import javax.swing.event.ChangeListener;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.spi.project.ui.support.ProjectChooser;
+import org.netbeans.spi.project.ui.templates.support.Templates;
+import org.openide.WizardDescriptor;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.NbBundle;
+
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class RollingTheMonkeyWizardIterator implements WizardDescriptor./*Progress*/InstantiatingIterator {
+
+ private int index;
+ private WizardDescriptor.Panel[] panels;
+ private WizardDescriptor wiz;
+
+ private static final String TEMPLATE_SETTINGS = "com/jme3/gde/templates/files/freemarker/settings.gradle.ftl";
+ private static final String BUILDFILE = "com/jme3/gde/templates/rollingthemonkey/build.gradle.ftl";
+
+ public RollingTheMonkeyWizardIterator() {
+
+ // Initiate the options getting...
+ CachedOptionsContainer.getInstance();
+ }
+
+ public static RollingTheMonkeyWizardIterator createIterator() {
+ return new RollingTheMonkeyWizardIterator();
+ }
+
+ private WizardDescriptor.Panel[] createPanels() {
+ return new WizardDescriptor.Panel[]{
+ new RollingTheMonkeyWizardPanel()
+ };
+ }
+
+ private String[] createSteps() {
+ return new String[]{
+ NbBundle.getMessage(RollingTheMonkeyWizardIterator.class, "LBL_CreateProjectStep"),
+ };
+ }
+
+ @Override
+ public Set/**/ instantiate(/*ProgressHandle handle*/) throws IOException {
+ Set resultSet = new LinkedHashSet<>();
+ File dirF = FileUtil.normalizeFile((File) wiz.getProperty("projdir"));
+ dirF.mkdirs();
+
+ FileObject template = Templates.getTemplate(wiz);
+ FileObject dir = FileUtil.toFileObject(dirF);
+ unZipFile(template.getInputStream(), dir);
+
+ // Create settings.gradle from template
+ File gradleSettingsFile = new File(dirF, "settings.gradle");
+ createFileFromTemplate(gradleSettingsFile, TEMPLATE_SETTINGS,
+ Collections.singletonMap("name", wiz.getProperty("name")));
+
+ // Create build.gradle from template
+ File gradleBuildFile = new File(dirF, "build.gradle");
+ Map buildFileBindings = new HashMap<>();
+
+ createFileFromTemplate(gradleBuildFile, BUILDFILE, buildFileBindings);
+
+ // Always open top dir as a project:
+ resultSet.add(dir);
+ // Look for nested projects to open as well:
+ Enumeration extends FileObject> e = dir.getFolders(true);
+ while (e.hasMoreElements()) {
+ FileObject subfolder = e.nextElement();
+ if (ProjectManager.getDefault().isProject(subfolder)) {
+ resultSet.add(subfolder);
+ }
+ }
+
+ File parent = dirF.getParentFile();
+ if (parent != null && parent.exists()) {
+ ProjectChooser.setProjectsFolder(parent);
+ }
+
+ return resultSet;
+ }
+
+ @Override
+ public void initialize(WizardDescriptor wiz) {
+ this.wiz = wiz;
+ index = 0;
+ panels = createPanels();
+ // Make sure list of steps is accurate.
+ String[] steps = createSteps();
+ for (int i = 0; i < panels.length; i++) {
+ Component c = panels[i].getComponent();
+ if (steps[i] == null) {
+ // Default step name to component name of panel.
+ // Mainly useful for getting the name of the target
+ // chooser to appear in the list of steps.
+ steps[i] = c.getName();
+ }
+ if (c instanceof JComponent) { // assume Swing components
+ JComponent jc = (JComponent) c;
+ // Step #.
+ // TODO if using org.openide.dialogs >= 7.8, can use WizardDescriptor.PROP_*:
+ jc.putClientProperty("WizardPanel_contentSelectedIndex", i);
+ // Step name (actually the whole list for reference).
+ jc.putClientProperty("WizardPanel_contentData", steps);
+ }
+ }
+ }
+
+ @Override
+ public void uninitialize(WizardDescriptor wiz) {
+ this.wiz.putProperty("projdir", null);
+ this.wiz.putProperty("name", null);
+ this.wiz = null;
+ panels = null;
+ }
+
+ @Override
+ public String name() {
+ return MessageFormat.format("{0} of {1}",
+ new Object[]{index + 1, panels.length});
+ }
+
+ @Override
+ public boolean hasNext() {
+ return index < panels.length - 1;
+ }
+
+ @Override
+ public boolean hasPrevious() {
+ return index > 0;
+ }
+
+ @Override
+ public void nextPanel() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ index++;
+ }
+
+ @Override
+ public void previousPanel() {
+ if (!hasPrevious()) {
+ throw new NoSuchElementException();
+ }
+ index--;
+ }
+
+ @Override
+ public WizardDescriptor.Panel current() {
+ return panels[index];
+ }
+
+ // If nothing unusual changes in the middle of the wizard, simply:
+ @Override
+ public final void addChangeListener(ChangeListener l) {
+ }
+
+ @Override
+ public final void removeChangeListener(ChangeListener l) {
+ }
+
+ private void createFileFromTemplate(File target, String templateResourcePath, Map tokens) throws IOException {
+
+ // Create FreeMarker script engine
+ ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
+ ScriptEngine engine = scriptEngineManager.getEngineByName("freemarker");
+ Map bindings = engine.getContext().getBindings(ScriptContext.ENGINE_SCOPE);
+ bindings.putAll(tokens);
+
+ // Process template
+ try {
+ FileObject targetFO = FileUtil.toFileObject(target);
+ try (Writer os = new BufferedWriter(new OutputStreamWriter(targetFO.getOutputStream(), StandardCharsets.UTF_8)); Reader is = new BufferedReader(new InputStreamReader(RollingTheMonkeyWizardIterator.class.getResourceAsStream("/" + templateResourcePath)));) {
+ engine.getContext().setWriter(os);
+ engine.eval(is);
+ }
+ } catch (IOException | ScriptException ex) {
+ throw new IOException(ex.getMessage(), ex);
+ }
+ }
+
+ private static void unZipFile(InputStream source, FileObject projectRoot) throws IOException {
+ try (source) {
+ ZipInputStream str = new ZipInputStream(source);
+ ZipEntry entry;
+ while ((entry = str.getNextEntry()) != null) {
+ if (entry.isDirectory()) {
+ FileUtil.createFolder(projectRoot, entry.getName());
+ } else {
+ FileObject fo = FileUtil.createData(projectRoot, entry.getName());
+ try (OutputStream out = fo.getOutputStream()) {
+ FileUtil.copy(str, out);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyWizardPanel.java b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyWizardPanel.java
new file mode 100644
index 00000000..207ee0de
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/RollingTheMonkeyWizardPanel.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 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.templates.rollingthemonkey;
+
+import java.awt.Component;
+import java.util.HashSet;
+import java.util.Set;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import org.openide.WizardDescriptor;
+import org.openide.WizardValidationException;
+import org.openide.util.HelpCtx;
+import org.openide.util.NbBundle;
+
+/**
+ * Panel just asking for basic info.
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class RollingTheMonkeyWizardPanel implements WizardDescriptor.Panel,
+ WizardDescriptor.ValidatingPanel, WizardDescriptor.FinishablePanel {
+
+ private WizardDescriptor wizardDescriptor;
+ private RollingTheMonkeyPanelVisual component;
+
+ public RollingTheMonkeyWizardPanel() {
+ }
+
+ @Override
+ public Component getComponent() {
+ if (component == null) {
+ component = new RollingTheMonkeyPanelVisual(this);
+ component.setName(NbBundle.getMessage(RollingTheMonkeyWizardPanel.class, "LBL_CreateProjectStep"));
+ }
+ return component;
+ }
+
+ @Override
+ public HelpCtx getHelp() {
+ return new HelpCtx("sdk.project_creation");
+ }
+
+ @Override
+ public boolean isValid() {
+ getComponent();
+ return component.valid(wizardDescriptor);
+ }
+
+ private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0
+
+ @Override
+ public final void addChangeListener(ChangeListener l) {
+ synchronized (listeners) {
+ listeners.add(l);
+ }
+ }
+
+ @Override
+ public final void removeChangeListener(ChangeListener l) {
+ synchronized (listeners) {
+ listeners.remove(l);
+ }
+ }
+
+ protected final void fireChangeEvent() {
+ Set ls;
+ synchronized (listeners) {
+ ls = new HashSet<>(listeners);
+ }
+ ChangeEvent ev = new ChangeEvent(this);
+ for (ChangeListener l : ls) {
+ l.stateChanged(ev);
+ }
+ }
+
+ @Override
+ public void readSettings(Object settings) {
+ wizardDescriptor = (WizardDescriptor) settings;
+ component.read(wizardDescriptor);
+ }
+
+ @Override
+ public void storeSettings(Object settings) {
+ WizardDescriptor d = (WizardDescriptor) settings;
+ component.store(d);
+ }
+
+ @Override
+ public boolean isFinishPanel() {
+ return false;
+ }
+
+ @Override
+ public void validate() throws WizardValidationException {
+ getComponent();
+ component.validate(wizardDescriptor);
+ }
+}
diff --git a/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/build.gradle.ftl b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/build.gradle.ftl
new file mode 100644
index 00000000..94e41ae1
--- /dev/null
+++ b/jme3-templates/src/com/jme3/gde/templates/rollingthemonkey/build.gradle.ftl
@@ -0,0 +1,65 @@
+plugins {
+ id 'java'
+ id 'application'
+}
+
+group 'com.rollingthemonkey'
+version '1.0'
+
+mainClassName = "com.rollingthemonkey.RollingTheMonkey"
+
+repositories {
+ mavenCentral()
+}
+
+project.ext {
+ jmeVer = '3.7.0-stable'
+}
+
+project(":assets") {
+ apply plugin: "java"
+
+ buildDir = rootProject.file("build/assets")
+
+ sourceSets {
+ main {
+ resources {
+ srcDir '.'
+ }
+ }
+ }
+
+ java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+ }
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+}
+
+dependencies {
+
+ implementation "org.jmonkeyengine:jme3-core:$jmeVer"
+ implementation "org.jmonkeyengine:jme3-desktop:$jmeVer"
+ implementation "org.jmonkeyengine:jme3-lwjgl:$jmeVer"
+ implementation "org.jmonkeyengine:jme3-lwjgl:$jmeVer"
+ implementation "com.github.stephengold:Heart:9.0.0"
+ implementation "com.github.stephengold:Minie:8.0.0"
+ implementation project("assets")
+
+}
+
+jar {
+ manifest {
+ attributes 'Main-Class': "$mainClassName"
+ }
+}
+
+wrapper {
+ gradleVersion = '8.6'
+}