diff --git a/.gitignore b/.gitignore
index 26b5b97563..81a4513bd2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -102,6 +102,7 @@ processing-examples
core/build/
build/publish/
app/build
+app/utils/build
java/build/
/build/reports
/java/bin
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 5323a1a829..64d7ddbd9d 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -97,6 +97,7 @@ compose.desktop {
dependencies {
implementation(project(":core"))
+ implementation(project(":app:utils"))
runtimeOnly(project(":java"))
implementation(libs.flatlaf)
diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index b5aa599b98..8204bdacca 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -43,6 +43,7 @@
import processing.app.ui.Toolkit;
import processing.core.*;
import processing.data.StringList;
+import processing.utils.Util;
/**
* The base class for the main processing application.
@@ -67,8 +68,6 @@ public class Base {
*/
static public boolean DEBUG = System.getenv().containsKey("DEBUG");
- /** True if running via Commander. */
- static private boolean commandLine;
/**
* If settings.txt is present inside lib, it will be used to override
@@ -472,12 +471,12 @@ static public String getVersionName() {
public static void setCommandLine() {
- commandLine = true;
+ System.setProperty("processing.cli", "true");
}
static public boolean isCommandLine() {
- return commandLine;
+ return Boolean.getBoolean("processing.cli");
}
diff --git a/app/src/processing/app/Language.java b/app/src/processing/app/Language.java
index d55c8b710c..3b66c31819 100644
--- a/app/src/processing/app/Language.java
+++ b/app/src/processing/app/Language.java
@@ -26,6 +26,7 @@
import processing.core.PApplet;
import processing.data.StringList;
+import processing.utils.Util;
/**
diff --git a/app/src/processing/app/Library.java b/app/src/processing/app/Library.java
index f23354284b..5bd1809c67 100644
--- a/app/src/processing/app/Library.java
+++ b/app/src/processing/app/Library.java
@@ -8,6 +8,7 @@
import processing.core.*;
import processing.data.StringDict;
import processing.data.StringList;
+import processing.utils.Util;
public class Library extends LocalContribution {
diff --git a/app/src/processing/app/Messages.kt b/app/src/processing/app/Messages.kt
index cae54e6e97..3a4525e986 100644
--- a/app/src/processing/app/Messages.kt
+++ b/app/src/processing/app/Messages.kt
@@ -26,45 +26,8 @@ import java.io.StringWriter
import javax.swing.JFrame
import javax.swing.JOptionPane
-class Messages {
+class Messages : processing.utils.Messages() {
companion object {
- /**
- * "No cookie for you" type messages. Nothing fatal or all that
- * much of a bummer, but something to notify the user about.
- */
- @JvmStatic
- fun showMessage(title: String = "Message", message: String) {
- if (Base.isCommandLine()) {
- println("$title: $message")
- } else {
- JOptionPane.showMessageDialog(
- Frame(), message, title,
- JOptionPane.INFORMATION_MESSAGE
- )
- }
- }
-
-
- /**
- * Non-fatal error message with optional stack trace side dish.
- */
- /**
- * Non-fatal error message.
- */
- @JvmStatic
- @JvmOverloads
- fun showWarning(title: String = "Warning", message: String, e: Throwable? = null) {
- if (Base.isCommandLine()) {
- println("$title: $message")
- } else {
- JOptionPane.showMessageDialog(
- Frame(), message, title,
- JOptionPane.WARNING_MESSAGE
- )
- }
- e?.printStackTrace()
- }
-
/**
* Non-fatal error message with two levels of formatting.
* Unlike the others, this is non-blocking and will run later on the EDT.
@@ -92,26 +55,6 @@ class Messages {
}
- /**
- * Show an error message that's actually fatal to the program.
- * This is an error that can't be recovered. Use showWarning()
- * for errors that allow P5 to continue running.
- */
- @JvmStatic
- fun showError(title: String = "Error", message: String, e: Throwable?) {
- if (Base.isCommandLine()) {
- System.err.println("$title: $message")
- } else {
- JOptionPane.showMessageDialog(
- Frame(), message, title,
- JOptionPane.ERROR_MESSAGE
- )
- }
- e?.printStackTrace()
- System.exit(1)
- }
-
-
/**
* Warning window that includes the stack trace.
*/
@@ -218,56 +161,6 @@ class Messages {
return -1
}
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
- @JvmStatic
- @Deprecated("Use log() instead")
- fun log(from: Any, message: String) {
- if (Base.DEBUG) {
- val callingClass = Throwable()
- .stackTrace[2]
- .className
- .formatClassName()
- println("$callingClass: $message")
- }
- }
-
- @JvmStatic
- fun log(message: String?) {
- if (Base.DEBUG) {
- val callingClass = Throwable()
- .stackTrace[2]
- .className
- .formatClassName()
- println("$callingClass$message")
- }
- }
-
- @JvmStatic
- fun logf(message: String?, vararg args: Any?) {
- if (Base.DEBUG) {
- val callingClass = Throwable()
- .stackTrace[2]
- .className
- .formatClassName()
- System.out.printf("$callingClass$message", *args)
- }
- }
-
- @JvmStatic
- @JvmOverloads
- fun err(message: String?, e: Throwable? = null) {
- if (Base.DEBUG) {
- if (message != null) {
- val callingClass = Throwable()
- .stackTrace[4]
- .className
- .formatClassName()
- System.err.println("$callingClass$message")
- }
- e?.printStackTrace()
- }
- }
}
}
diff --git a/app/src/processing/app/Mode.java b/app/src/processing/app/Mode.java
index 29e9ad6d3a..ec860c3a96 100644
--- a/app/src/processing/app/Mode.java
+++ b/app/src/processing/app/Mode.java
@@ -40,6 +40,7 @@
import processing.app.ui.Recent;
import processing.app.ui.Toolkit;
import processing.core.PApplet;
+import processing.utils.Util;
public abstract class Mode {
diff --git a/app/src/processing/app/Platform.java b/app/src/processing/app/Platform.java
index b911d7e0ae..73318c4808 100644
--- a/app/src/processing/app/Platform.java
+++ b/app/src/processing/app/Platform.java
@@ -36,6 +36,8 @@
import processing.core.PApplet;
import processing.core.PConstants;
import processing.data.StringDict;
+import processing.utils.SettingsResolver;
+import processing.utils.Util;
public class Platform {
@@ -129,7 +131,12 @@ static public float getSystemZoom() {
static public File getSettingsFolder() throws Exception {
- return inst.getSettingsFolder();
+ File override = Base.getSettingsOverride();
+ if (override != null) {
+ return override;
+ }
+
+ return SettingsResolver.getSettingsFolder();
}
diff --git a/app/src/processing/app/Preferences.java b/app/src/processing/app/Preferences.java
index 640c77eade..fe19d12649 100644
--- a/app/src/processing/app/Preferences.java
+++ b/app/src/processing/app/Preferences.java
@@ -1,398 +1,108 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-/*
- Part of the Processing project - http://processing.org
-
- Copyright (c) 2014-19 The Processing Foundation
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2
- as published by the Free Software Foundation.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-*/
-
package processing.app;
-import java.awt.Color;
-import java.awt.Font;
-import java.awt.SystemColor;
-import java.io.*;
-import java.util.*;
-
import processing.app.ui.Toolkit;
-import processing.core.*;
-
+import processing.core.PApplet;
-/**
- * Storage class for user preferences and environment settings.
- *
- * This class does not use the Properties class because .properties files use
- * ISO 8859-1 encoding, which is highly likely to be a problem when trying to
- * save sketch folders and locations. Like the rest of Processing, we use UTF8.
- *
- * We don't use the Java Preferences API because it would entail writing to
- * the registry (on Windows), or an obscure file location (on Mac OS X) and
- * make it far more difficult (impossible) to remove the preferences.txt to
- * reset them (when they become corrupt), or to find the the file to make
- * edits for numerous obscure preferences that are not part of the preferences
- * window. If we added a generic editor (e.g. about:config in Mozilla) for
- * such things, we could start using the Java Preferences API. But wow, that
- * sounds like a lot of work. Not unlike writing this paragraph.
- */
-public class Preferences {
- // had to rename the defaults file because people were editing it
- static final String DEFAULTS_FILE = "defaults.txt"; //$NON-NLS-1$
- static final String PREFS_FILE = "preferences.txt"; //$NON-NLS-1$
+import java.awt.*;
- static Map defaults;
- static Map table = new HashMap<>();
- static File preferencesFile;
- private static boolean initialized = false;
+public class Preferences extends processing.utils.Preferences {
+ static public void init() {
+ processing.utils.Preferences.init();
-// /** @return true if the sketchbook file did not exist */
-// static public boolean init() {
- static public void init() {
- initialized = true;
- // start by loading the defaults, in case something
- // important was deleted from the user prefs
- try {
- // Name changed for 2.1b2 to avoid problems with users modifying or
- // replacing the file after doing a search for "preferences.txt".
- load(Base.getLibStream(DEFAULTS_FILE));
- } catch (Exception e) {
- Messages.showError(null, "Could not read default settings.\n" +
- "You'll need to reinstall Processing.", e);
- }
-
- // Clone the defaults, then override any them with the user's preferences.
- // This ensures that any new/added preference will be present.
- defaults = new HashMap<>(table);
-
- // other things that have to be set explicitly for the defaults
- setColor("run.window.bgcolor", SystemColor.control); //$NON-NLS-1$
-
- // For CJK users, enable IM support by default
- if (Language.useInputMethod()) {
- setBoolean("editor.input_method_support", true);
- }
-
- // next load user preferences file
- preferencesFile = Base.getSettingsFile(PREFS_FILE);
- boolean firstRun = !preferencesFile.exists();
- if (!firstRun) {
- try {
- load(new FileInputStream(preferencesFile));
-
- } catch (Exception ex) {
- Messages.showError("Error reading preferences",
- "Error reading the preferences file. " +
- "Please delete (or move)\n" +
- preferencesFile.getAbsolutePath() +
- " and restart Processing.", ex);
- }
- }
-
- if (checkSketchbookPref() || firstRun) {
-// if (firstRun) {
- // create a new preferences file if none exists
- // saves the defaults out to the file
- save();
- }
-
- PApplet.useNativeSelect =
- Preferences.getBoolean("chooser.files.native"); //$NON-NLS-1$
-
- // Adding option to disable this in case it's getting in the way
- if (get("proxy.system").equals("true")) {
- // Use the system proxy settings by default
- // https://github.com/processing/processing/issues/2643
- System.setProperty("java.net.useSystemProxies", "true");
- }
-
- // Set HTTP, HTTPS, and SOCKS proxies for individuals
- // who want/need to override the system setting
- // http://docs.oracle.com/javase/6/docs/technotes/guides/net/proxies.html
- // Less readable version with the Oracle style sheet:
- // http://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html
- handleProxy("http", "http.proxyHost", "http.proxyPort");
- handleProxy("https", "https.proxyHost", "https.proxyPort");
- handleProxy("socks", "socksProxyHost", "socksProxyPort");
- }
-
-
- /**
- * For testing, pretend to load preferences without a real file.
- */
- static public void skipInit() {
- initialized = true;
- }
-
-
- static void handleProxy(String protocol, String hostProp, String portProp) {
- String proxyHost = get("proxy." + protocol + ".host");
- String proxyPort = get("proxy." + protocol + ".port");
- if (proxyHost != null && proxyHost.length() != 0 &&
- proxyPort != null && proxyPort.length() != 0) {
- System.setProperty(hostProp, proxyHost);
- System.setProperty(portProp, proxyPort);
- }
-
- }
-
-
- static public String getPreferencesPath() {
- return preferencesFile.getAbsolutePath();
- }
-
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
-
- /**
- * Load a set of key/value pairs from a UTF-8 encoded file into 'table'.
- * For 3.0a6, this removes any platform-specific extensions from keys, so
- * that we don't have platform-specific entries in a user's preferences.txt
- * file, which would require all prefs to be changed twice, or risk being
- * overwritten by the unchanged platform-specific version on reload.
- */
- static public void load(InputStream input) throws IOException {
- HashMap platformSpecific = new HashMap<>();
-
- String[] lines = PApplet.loadStrings(input); // Reads as UTF-8
- for (String line : lines) {
- if ((line.length() == 0) ||
- (line.charAt(0) == '#')) continue;
-
- // this won't properly handle = signs being in the text
- int equals = line.indexOf('=');
- if (equals != -1) {
- String key = line.substring(0, equals).trim();
- String value = line.substring(equals + 1).trim();
- if (!isPlatformSpecific(key, value, platformSpecific)) {
- table.put(key, value);
+ // For CJK users, enable IM support by default
+ if (Language.useInputMethod() && !getBoolean("editor.input_method_support")) {
+ setBoolean("editor.input_method_support", true);
}
- }
- }
- // Now override the keys with any platform-specific defaults we've found.
- for (String key : platformSpecific.keySet()) {
- table.put(key, platformSpecific.get(key));
- }
- }
-
- /**
- * @param key original key (may include platform extension)
- * @param value the value that goes with the key
- * @param specific where to put the key/value pairs for *this* platform
- * @return true if a platform-specific key
- */
- static protected boolean isPlatformSpecific(String key, String value,
- Map specific) {
- for (String platform : PConstants.platformNames) {
- String ext = "." + platform;
- if (key.endsWith(ext)) {
- String thisPlatform = PConstants.platformNames[PApplet.platform];
- if (platform.equals(thisPlatform)) {
- key = key.substring(0, key.lastIndexOf(ext));
- // store this for later overrides
- specific.put(key, value);
- //} else {
- // ignore platform-specific defaults for other platforms,
- // but return 'true' because it needn't be added to the big list
+ if(get("run.window.bgcolor").isEmpty()){
+ setColor("run.window.bgcolor", SystemColor.control);
}
- return true;
- }
- }
- return false;
- }
-
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
- static public void save() {
- // On startup, this is null, but ignore it. It's trying to update the
- // prefs for the open sketch before Preferences.init() has been called.
- if (preferencesFile != null) {
- try {
- File dir = preferencesFile.getParentFile();
- File preferencesTemp = File.createTempFile("preferences", ".txt", dir);
- if (!preferencesTemp.setWritable(true, false)) {
- throw new IOException("Could not set " + preferencesTemp + " writable");
+ if(checkSketchbookPref()){
+ save();
}
- // Fix for 0163 to properly use Unicode when writing preferences.txt
- PrintWriter writer = PApplet.createWriter(preferencesTemp);
+ PApplet.useNativeSelect =
+ Preferences.getBoolean("chooser.files.native"); //$NON-NLS-1$
- String[] keyList = table.keySet().toArray(new String[0]);
- // Sorting is really helpful for debugging, diffing, and finding keys
- keyList = PApplet.sort(keyList);
- for (String key : keyList) {
- writer.println(key + "=" + table.get(key)); //$NON-NLS-1$
+ // Adding option to disable this in case it's getting in the way
+ if (get("proxy.system").equals("true")) {
+ // Use the system proxy settings by default
+ // https://github.com/processing/processing/issues/2643
+ System.setProperty("java.net.useSystemProxies", "true");
}
- writer.flush();
- writer.close();
- // Rename preferences.txt to preferences.old
- File oldPreferences = new File(dir, "preferences.old");
- if (oldPreferences.exists()) {
- if (!oldPreferences.delete()) {
- throw new IOException("Could not delete preferences.old");
- }
- }
- if (preferencesFile.exists() &&
- !preferencesFile.renameTo(oldPreferences)) {
- throw new IOException("Could not replace preferences.old");
- }
- // Make the temporary file into the real preferences
- if (!preferencesTemp.renameTo(preferencesFile)) {
- throw new IOException("Could not move preferences file into place");
- }
+ // Set HTTP, HTTPS, and SOCKS proxies for individuals
+ // who want/need to override the system setting
+ // http://docs.oracle.com/javase/6/docs/technotes/guides/net/proxies.html
+ // Less readable version with the Oracle style sheet:
+ // http://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html
+ handleProxy("http", "http.proxyHost", "http.proxyPort");
+ handleProxy("https", "https.proxyHost", "https.proxyPort");
+ handleProxy("socks", "socksProxyHost", "socksProxyPort");
- } catch (IOException e) {
- Messages.showWarning("Preferences",
- "Could not save the Preferences file.", e);
- }
}
- }
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+ static public Font getFont(String familyAttr, String sizeAttr, int style) {
+ int fontSize = getInteger(sizeAttr);
- // all the information from preferences.txt
-
- static public String get(String attribute /*, String defaultValue */) {
- if (!initialized) {
- throw new RuntimeException(
- "Tried reading preferences prior to initialization."
- );
+ String fontFamily = get(familyAttr);
+ if ("processing.mono".equals(fontFamily) ||
+ Toolkit.getMonoFontName().equals(fontFamily)) {
+ return Toolkit.getMonoFont(fontSize, style);
+ }
+ return new Font(fontFamily, style, fontSize);
}
- return table.get(attribute);
- }
-
-
- static public String getDefault(String attribute) {
- return defaults.get(attribute);
- }
-
-
- static public void set(String attribute, String value) {
- table.put(attribute, value);
- }
-
-
- static public void unset(String attribute) {
- table.remove(attribute);
- }
-
- static public boolean getBoolean(String attribute) {
- String value = get(attribute); //, null);
- return Boolean.parseBoolean(value);
- /*
- supposedly not needed, because anything besides 'true'
- (ignoring case) will just be false.. so if malformed -> false
- if (value == null) return defaultValue;
+ static void handleProxy(String protocol, String hostProp, String portProp) {
+ String proxyHost = get("proxy." + protocol + ".host");
+ String proxyPort = get("proxy." + protocol + ".port");
+ if (proxyHost != null && proxyHost.length() != 0 &&
+ proxyPort != null && proxyPort.length() != 0) {
+ System.setProperty(hostProp, proxyHost);
+ System.setProperty(portProp, proxyPort);
+ }
- try {
- return (new Boolean(value)).booleanValue();
- } catch (NumberFormatException e) {
- System.err.println("expecting an integer: " + attribute + " = " + value);
}
- return defaultValue;
- */
- }
-
-
- static public void setBoolean(String attribute, boolean value) {
- set(attribute, value ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
-
- static public int getInteger(String attribute /*, int defaultValue*/) {
- return Integer.parseInt(get(attribute));
- }
- static public void setInteger(String key, int value) {
- set(key, String.valueOf(value));
- }
-
-
- static public Color getColor(String name) {
- Color parsed = Color.GRAY; // set a default
- String s = get(name);
- if ((s != null) && (s.indexOf("#") == 0)) { //$NON-NLS-1$
- try {
- parsed = new Color(Integer.parseInt(s.substring(1), 16));
- } catch (Exception ignored) { }
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ /**
+ * Check for a 4.0 sketchbook location, and if none exists,
+ * try to grab it from the 3.0 sketchbook location.
+ * @return true if a location was found and the pref didn't exist
+ */
+ static protected boolean checkSketchbookPref() {
+ // If a 4.0 sketchbook location has never been inited
+ if (getSketchbookPath() == null) {
+ String threePath = get("sketchbook.path.three"); //$NON-NLS-1$
+ // If they've run the 3.0 version, start with that location
+ if (threePath != null) {
+ setSketchbookPath(threePath);
+ return true; // save the sketchbook right away
+ }
+ // Otherwise it'll be null, and reset properly by Base
+ }
+ return false;
}
- return parsed;
- }
-
- static public void setColor(String attr, Color what) {
- set(attr, "#" + PApplet.hex(what.getRGB() & 0xffffff, 6)); //$NON-NLS-1$
- }
-
- static public Font getFont(String familyAttr, String sizeAttr, int style) {
- int fontSize = getInteger(sizeAttr);
-
- String fontFamily = get(familyAttr);
- if ("processing.mono".equals(fontFamily) ||
- Toolkit.getMonoFontName().equals(fontFamily)) {
- return Toolkit.getMonoFont(fontSize, style);
+ static public String getOldSketchbookPath() {
+ return get("sketchbook.path.three"); //$NON-NLS-1$
}
- return new Font(fontFamily, style, fontSize);
- }
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
- /**
- * Check for a 4.0 sketchbook location, and if none exists,
- * try to grab it from the 3.0 sketchbook location.
- * @return true if a location was found and the pref didn't exist
- */
- static protected boolean checkSketchbookPref() {
- // If a 4.0 sketchbook location has never been inited
- if (getSketchbookPath() == null) {
- String threePath = get("sketchbook.path.three"); //$NON-NLS-1$
- // If they've run the 3.0 version, start with that location
- if (threePath != null) {
- setSketchbookPath(threePath);
- return true; // save the sketchbook right away
- }
- // Otherwise it'll be null, and reset properly by Base
+ static public String getSketchbookPath() {
+ return get("sketchbook.path.four"); //$NON-NLS-1$
}
- return false;
- }
-
- static public String getOldSketchbookPath() {
- return get("sketchbook.path.three"); //$NON-NLS-1$
- }
-
- static public String getSketchbookPath() {
- return get("sketchbook.path.four"); //$NON-NLS-1$
- }
-
-
- static protected void setSketchbookPath(String path) {
- set("sketchbook.path.four", path); //$NON-NLS-1$
- }
+ public static void setSketchbookPath(String path) {
+ set("sketchbook.path.four", path); //$NON-NLS-1$
+ }
}
diff --git a/app/src/processing/app/Schema.kt b/app/src/processing/app/Schema.kt
index a02bf1da76..ad7a94879a 100644
--- a/app/src/processing/app/Schema.kt
+++ b/app/src/processing/app/Schema.kt
@@ -5,6 +5,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import processing.app.ui.Editor
+import processing.utils.Messages
import java.io.File
import java.io.FileOutputStream
import java.net.URI
diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java
index 8bb50352b0..1bef3da039 100644
--- a/app/src/processing/app/Sketch.java
+++ b/app/src/processing/app/Sketch.java
@@ -28,6 +28,7 @@
import processing.app.ui.Recent;
import processing.app.ui.Toolkit;
import processing.core.*;
+import processing.utils.Util;
import java.awt.Color;
import java.awt.Component;
diff --git a/app/src/processing/app/SketchCode.java b/app/src/processing/app/SketchCode.java
index 299b9373ae..797172ad90 100644
--- a/app/src/processing/app/SketchCode.java
+++ b/app/src/processing/app/SketchCode.java
@@ -24,6 +24,8 @@
package processing.app;
+import processing.utils.Util;
+
import java.io.*;
import java.util.Stack;
diff --git a/app/src/processing/app/contrib/AvailableContribution.java b/app/src/processing/app/contrib/AvailableContribution.java
index a190c0bf10..0a682f3905 100644
--- a/app/src/processing/app/contrib/AvailableContribution.java
+++ b/app/src/processing/app/contrib/AvailableContribution.java
@@ -31,6 +31,7 @@
import processing.core.PApplet;
import processing.data.StringDict;
import processing.data.StringList;
+import processing.utils.Util;
/**
diff --git a/app/src/processing/app/contrib/ContributionListing.java b/app/src/processing/app/contrib/ContributionListing.java
index 08b8d307c7..4271977e77 100644
--- a/app/src/processing/app/contrib/ContributionListing.java
+++ b/app/src/processing/app/contrib/ContributionListing.java
@@ -32,7 +32,7 @@
import processing.app.Base;
import processing.app.Messages;
import processing.app.UpdateCheck;
-import processing.app.Util;
+import processing.utils.Util;
import processing.core.PApplet;
import processing.data.StringDict;
import processing.data.StringList;
diff --git a/app/src/processing/app/contrib/ContributionManager.java b/app/src/processing/app/contrib/ContributionManager.java
index c4d45f7d7d..31b923a2f3 100644
--- a/app/src/processing/app/contrib/ContributionManager.java
+++ b/app/src/processing/app/contrib/ContributionManager.java
@@ -31,7 +31,7 @@
import processing.app.Language;
import processing.app.Messages;
import processing.app.Platform;
-import processing.app.Util;
+import processing.utils.Util;
import processing.app.ui.Editor;
import processing.core.PApplet;
import processing.data.StringDict;
diff --git a/app/src/processing/app/contrib/ContributionType.java b/app/src/processing/app/contrib/ContributionType.java
index 83ed9eb687..44f6c16325 100644
--- a/app/src/processing/app/contrib/ContributionType.java
+++ b/app/src/processing/app/contrib/ContributionType.java
@@ -29,7 +29,7 @@
import processing.app.Base;
import processing.app.Library;
import processing.app.Messages;
-import processing.app.Util;
+import processing.utils.Util;
import processing.app.ui.Editor;
import processing.core.PApplet;
import processing.data.StringDict;
diff --git a/app/src/processing/app/contrib/ListPanel.java b/app/src/processing/app/contrib/ListPanel.java
index 21062d2f82..89133cd631 100644
--- a/app/src/processing/app/contrib/ListPanel.java
+++ b/app/src/processing/app/contrib/ListPanel.java
@@ -34,7 +34,7 @@
import javax.swing.RowSorter.SortKey;
import javax.swing.table.*;
-import processing.app.Util;
+import processing.utils.Util;
import processing.app.ui.Theme;
import processing.app.laf.PdeScrollBarUI;
import processing.app.ui.Toolkit;
diff --git a/app/src/processing/app/contrib/LocalContribution.java b/app/src/processing/app/contrib/LocalContribution.java
index df0e8cbfdd..558003b781 100644
--- a/app/src/processing/app/contrib/LocalContribution.java
+++ b/app/src/processing/app/contrib/LocalContribution.java
@@ -30,13 +30,12 @@
import java.util.*;
import java.util.zip.*;
-import javax.swing.JOptionPane;
-
import processing.app.*;
import processing.app.ui.Editor;
import processing.core.PApplet;
import processing.data.StringDict;
import processing.data.StringList;
+import processing.utils.Util;
/**
diff --git a/app/src/processing/app/contrib/ModeContribution.java b/app/src/processing/app/contrib/ModeContribution.java
index b794d12b3c..8f601eabb0 100644
--- a/app/src/processing/app/contrib/ModeContribution.java
+++ b/app/src/processing/app/contrib/ModeContribution.java
@@ -34,7 +34,7 @@
import processing.app.Base;
import processing.app.Messages;
import processing.app.Mode;
-import processing.app.Util;
+import processing.utils.Util;
public class ModeContribution extends LocalContribution {
diff --git a/app/src/processing/app/contrib/StatusPanel.java b/app/src/processing/app/contrib/StatusPanel.java
index ff2511b5a3..3847595d85 100644
--- a/app/src/processing/app/contrib/StatusPanel.java
+++ b/app/src/processing/app/contrib/StatusPanel.java
@@ -40,7 +40,7 @@
import javax.swing.text.html.HTMLDocument;
import processing.app.Language;
-import processing.app.Util;
+import processing.utils.Util;
import processing.app.laf.PdeButtonUI;
import processing.app.laf.PdeProgressBarUI;
import processing.app.ui.Theme;
diff --git a/app/src/processing/app/platform/DefaultPlatform.java b/app/src/processing/app/platform/DefaultPlatform.java
index 18997755b7..de965a6007 100644
--- a/app/src/processing/app/platform/DefaultPlatform.java
+++ b/app/src/processing/app/platform/DefaultPlatform.java
@@ -32,8 +32,6 @@
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatLightLaf;
-import com.sun.jna.Library;
-import com.sun.jna.Native;
import processing.app.Base;
import processing.app.Preferences;
@@ -206,23 +204,6 @@ public void setInterfaceZoom() throws Exception {
public void saveLanguage(String languageCode) { }
- /**
- * This function should throw an exception or return a value.
- * Do not return null.
- */
- public File getSettingsFolder() throws Exception {
- File override = Base.getSettingsOverride();
- if (override != null) {
- return override;
- }
-
- // If no subclass has a behavior, default to making a
- // ".processing" directory in the user's home directory.
- File home = new File(System.getProperty("user.home"));
- return new File(home, ".processing");
- }
-
-
/**
* @return if not overridden, a folder named "sketchbook" in user.home.
* @throws Exception so that subclasses can throw a fit
diff --git a/app/src/processing/app/platform/LinuxPlatform.java b/app/src/processing/app/platform/LinuxPlatform.java
index 3426144cae..9e0eab46bc 100644
--- a/app/src/processing/app/platform/LinuxPlatform.java
+++ b/app/src/processing/app/platform/LinuxPlatform.java
@@ -26,9 +26,8 @@
import java.awt.Desktop;
import java.awt.Toolkit;
-import processing.app.Base;
-import processing.app.Messages;
import processing.app.Preferences;
+import processing.app.Base;
import processing.core.PApplet;
import javax.swing.*;
@@ -90,39 +89,6 @@ static public String getHomeDir(String user) throws Exception {
}
- @Override
- public File getSettingsFolder() throws Exception {
- File override = Base.getSettingsOverride();
- if (override != null) {
- return override;
- }
-
- // https://github.com/processing/processing4/issues/203
- // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
-
- File configHome = null;
-
- // Check to see if the user has set a different location for their config
- String configHomeEnv = System.getenv("XDG_CONFIG_HOME");
- if (configHomeEnv != null && !configHomeEnv.isBlank()) {
- configHome = new File(configHomeEnv);
- if (!configHome.exists()) {
- Messages.err("XDG_CONFIG_HOME is set to " + configHomeEnv + " but does not exist.");
- configHome = null; // don't use non-existent folder
- }
- }
- String snapUserCommon = System.getenv("SNAP_USER_COMMON");
- if (snapUserCommon != null && !snapUserCommon.isBlank()) {
- configHome = new File(snapUserCommon);
- }
- // If not set properly, use the default
- if (configHome == null) {
- configHome = new File(getHomeDir(), ".config");
- }
- return new File(configHome, "processing");
- }
-
-
@Override
public File getDefaultSketchbookFolder() throws Exception {
return new File(getHomeDir(), "sketchbook");
diff --git a/app/src/processing/app/platform/MacPlatform.java b/app/src/processing/app/platform/MacPlatform.java
index f26c8f2c66..cb4fa82bd8 100644
--- a/app/src/processing/app/platform/MacPlatform.java
+++ b/app/src/processing/app/platform/MacPlatform.java
@@ -23,7 +23,6 @@
package processing.app.platform;
import java.awt.*;
-import java.awt.desktop.AppReopenedEvent;
import java.awt.desktop.AppReopenedListener;
import java.io.File;
import java.io.FileNotFoundException;
@@ -112,15 +111,6 @@ public void initBase(Base base) {
}
- public File getSettingsFolder() throws Exception {
- File override = Base.getSettingsOverride();
- if (override != null) {
- return override;
- }
- return new File(getLibraryFolder(), "Processing");
- }
-
-
public File getDefaultSketchbookFolder() throws Exception {
return new File(getDocumentsFolder(), "Processing");
}
diff --git a/app/src/processing/app/platform/WindowsPlatform.java b/app/src/processing/app/platform/WindowsPlatform.java
index b74a1674c3..b463887f0f 100644
--- a/app/src/processing/app/platform/WindowsPlatform.java
+++ b/app/src/processing/app/platform/WindowsPlatform.java
@@ -24,7 +24,6 @@
import java.awt.*;
import java.io.File;
-import java.io.IOException;
import java.io.UnsupportedEncodingException;
import com.sun.jna.Library;
@@ -351,55 +350,6 @@ protected void checkPath() {
}
- // looking for Documents and Settings/blah/Application Data/Processing
- public File getSettingsFolder() throws Exception {
- File override = Base.getSettingsOverride();
- if (override != null) {
- return override;
- }
-
- try {
- String appDataRoaming = getAppDataPath();
- if (appDataRoaming != null) {
- File settingsFolder = new File(appDataRoaming, APP_NAME);
- if (settingsFolder.exists() || settingsFolder.mkdirs()) {
- return settingsFolder;
- }
- }
-
- String appDataLocal = getLocalAppDataPath();
- if (appDataLocal != null) {
- File settingsFolder = new File(appDataLocal, APP_NAME);
- if (settingsFolder.exists() || settingsFolder.mkdirs()) {
- return settingsFolder;
- }
- }
-
- if (appDataRoaming == null && appDataLocal == null) {
- throw new IOException("Could not get the AppData folder");
- }
-
- // https://github.com/processing/processing/issues/3838
- throw new IOException("Permissions error: make sure that " +
- appDataRoaming + " or " + appDataLocal +
- " is writable.");
-
- } catch (UnsatisfiedLinkError ule) {
- String path = new File("lib").getCanonicalPath();
-
- String msg = Util.containsNonASCII(path) ?
- """
- Please move Processing to a location with only
- ASCII characters in the path and try again.
- https://github.com/processing/processing/issues/3543
- """ :
- "Could not find JNA support files, please reinstall Processing.";
- Messages.showError("Windows JNA Problem", msg, ule);
- return null; // unreachable
- }
- }
-
-
/*
What's happening internally with JNA https://github.com/java-native-access/jna/blob/master/contrib/platform/src/com/sun/jna/platform/win32/Shell32.java
diff --git a/app/src/processing/app/syntax/PdeInputHandler.java b/app/src/processing/app/syntax/PdeInputHandler.java
index cc763bd3d7..aca0633646 100644
--- a/app/src/processing/app/syntax/PdeInputHandler.java
+++ b/app/src/processing/app/syntax/PdeInputHandler.java
@@ -27,8 +27,8 @@
import java.awt.event.KeyEvent;
-import processing.app.Platform;
import processing.app.Preferences;
+import processing.app.Platform;
import processing.app.ui.Editor;
diff --git a/app/src/processing/app/tools/Archiver.java b/app/src/processing/app/tools/Archiver.java
index 98879d3085..0bbfb510fb 100644
--- a/app/src/processing/app/tools/Archiver.java
+++ b/app/src/processing/app/tools/Archiver.java
@@ -27,6 +27,7 @@
import processing.app.*;
import processing.app.ui.Editor;
import processing.awt.ShimAWT;
+import processing.utils.Util;
import java.io.*;
import java.text.*;
diff --git a/app/src/processing/app/tools/ThemeSelector.java b/app/src/processing/app/tools/ThemeSelector.java
index 1b146d02bb..5fd79bd293 100644
--- a/app/src/processing/app/tools/ThemeSelector.java
+++ b/app/src/processing/app/tools/ThemeSelector.java
@@ -30,6 +30,7 @@
import processing.app.ui.Toolkit;
import processing.core.PApplet;
import processing.data.StringDict;
+import processing.utils.Util;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java
index a06cbe2383..da4ae0bf7e 100644
--- a/app/src/processing/app/ui/Editor.java
+++ b/app/src/processing/app/ui/Editor.java
@@ -48,20 +48,9 @@
import javax.swing.text.html.*;
import javax.swing.undo.*;
-import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.SystemInfo;
-import processing.app.Base;
-import processing.app.Formatter;
-import processing.app.Language;
-import processing.app.Messages;
-import processing.app.Mode;
-import processing.app.Platform;
+import processing.app.*;
import processing.app.Preferences;
-import processing.app.Problem;
-import processing.app.RunnerListener;
-import processing.app.Sketch;
-import processing.app.SketchCode;
-import processing.app.SketchException;
import processing.app.contrib.ContributionManager;
import processing.app.laf.PdeMenuItemUI;
import processing.app.syntax.*;
diff --git a/app/src/processing/app/ui/EditorConsole.java b/app/src/processing/app/ui/EditorConsole.java
index c8c40ee487..996831a89c 100644
--- a/app/src/processing/app/ui/EditorConsole.java
+++ b/app/src/processing/app/ui/EditorConsole.java
@@ -36,8 +36,8 @@
import javax.swing.border.MatteBorder;
import javax.swing.text.*;
-import processing.app.Console;
import processing.app.Preferences;
+import processing.app.Console;
import processing.app.laf.PdeScrollBarUI;
diff --git a/app/src/processing/app/ui/EditorStatus.java b/app/src/processing/app/ui/EditorStatus.java
index 02a85f10e1..c97c6c22bf 100644
--- a/app/src/processing/app/ui/EditorStatus.java
+++ b/app/src/processing/app/ui/EditorStatus.java
@@ -40,8 +40,8 @@
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI;
-import processing.app.Platform;
import processing.app.Preferences;
+import processing.app.Platform;
import processing.core.PApplet;
diff --git a/app/src/processing/app/ui/ExamplesFrame.java b/app/src/processing/app/ui/ExamplesFrame.java
index 0d8e89be9a..fff0f04e15 100644
--- a/app/src/processing/app/ui/ExamplesFrame.java
+++ b/app/src/processing/app/ui/ExamplesFrame.java
@@ -52,13 +52,8 @@
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
-import processing.app.Base;
-import processing.app.Language;
-import processing.app.Library;
-import processing.app.Mode;
-import processing.app.Platform;
+import processing.app.*;
import processing.app.Preferences;
-import processing.app.SketchReference;
import processing.app.contrib.Contribution;
import processing.app.contrib.ContributionManager;
import processing.app.contrib.ContributionType;
diff --git a/app/src/processing/app/ui/ExportPrompt.java b/app/src/processing/app/ui/ExportPrompt.java
index f4a8cece99..5669e1df5f 100644
--- a/app/src/processing/app/ui/ExportPrompt.java
+++ b/app/src/processing/app/ui/ExportPrompt.java
@@ -34,9 +34,9 @@
import java.util.ArrayList;
import java.util.List;
+import processing.app.Preferences;
import processing.app.Language;
import processing.app.Platform;
-import processing.app.Preferences;
import processing.app.platform.MacPlatform;
import processing.core.PApplet;
diff --git a/app/src/processing/app/ui/PreferencesFrame.java b/app/src/processing/app/ui/PreferencesFrame.java
index a8cf68c27d..7b206686e2 100644
--- a/app/src/processing/app/ui/PreferencesFrame.java
+++ b/app/src/processing/app/ui/PreferencesFrame.java
@@ -33,12 +33,8 @@
import com.formdev.flatlaf.FlatClientProperties;
-import processing.app.Base;
-import processing.app.Language;
-import processing.app.Messages;
-import processing.app.Platform;
+import processing.app.*;
import processing.app.Preferences;
-import processing.app.SketchName;
import processing.awt.ShimAWT;
import processing.core.PApplet;
diff --git a/app/src/processing/app/ui/Theme.java b/app/src/processing/app/ui/Theme.java
index edc14bf584..22b49f7a4f 100644
--- a/app/src/processing/app/ui/Theme.java
+++ b/app/src/processing/app/ui/Theme.java
@@ -22,9 +22,9 @@
package processing.app.ui;
+import processing.app.Preferences;
import processing.app.Base;
import processing.app.Messages;
-import processing.app.Preferences;
import processing.app.Settings;
import processing.app.syntax.SyntaxStyle;
import processing.core.PApplet;
diff --git a/app/src/processing/app/ui/Toolkit.java b/app/src/processing/app/ui/Toolkit.java
index 8a5ae418bb..18358e0516 100644
--- a/app/src/processing/app/ui/Toolkit.java
+++ b/app/src/processing/app/ui/Toolkit.java
@@ -72,7 +72,7 @@
import processing.app.Messages;
import processing.app.Platform;
import processing.app.Preferences;
-import processing.app.Util;
+import processing.utils.Util;
import processing.awt.PGraphicsJava2D;
import processing.awt.PShapeJava2D;
import processing.core.PApplet;
diff --git a/app/src/processing/app/ui/theme/Locale.kt b/app/src/processing/app/ui/theme/Locale.kt
index 254c0946c1..b720ef3782 100644
--- a/app/src/processing/app/ui/theme/Locale.kt
+++ b/app/src/processing/app/ui/theme/Locale.kt
@@ -3,11 +3,10 @@ package processing.app.ui.theme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
-import processing.app.LocalPreferences
-import processing.app.Messages
import processing.app.Platform
import processing.app.PlatformStart
import processing.app.watchFile
+import processing.utils.Messages
import java.io.File
import java.io.InputStream
import java.util.*
diff --git a/app/utils/build.gradle.kts b/app/utils/build.gradle.kts
new file mode 100644
index 0000000000..a1a9c97a53
--- /dev/null
+++ b/app/utils/build.gradle.kts
@@ -0,0 +1,45 @@
+plugins {
+ id("java")
+ kotlin("jvm") version libs.versions.kotlin
+}
+
+group = "processing.utils"
+
+repositories {
+ mavenCentral()
+ google()
+ maven { url = uri("https://jogamp.org/deployment/maven") }
+}
+
+sourceSets{
+ main{
+ java{
+ srcDirs("src")
+ }
+ kotlin{
+ srcDirs("src")
+ }
+ resources{
+ srcDirs("resources", listOf("languages", "fonts", "theme").map { "../../build/shared/lib/$it" })
+ }
+ }
+ test{
+ kotlin{
+ srcDirs("src/test")
+ }
+ }
+}
+
+dependencies {
+ implementation(project(":core"))
+
+ implementation(libs.jna)
+ implementation(libs.jnaplatform)
+
+ testImplementation(platform("org.junit:junit-bom:5.10.0"))
+ testImplementation("org.junit.jupiter:junit-jupiter")
+}
+
+tasks.test {
+ useJUnitPlatform()
+}
\ No newline at end of file
diff --git a/app/utils/src/main/java/processing/utils/Base.java b/app/utils/src/main/java/processing/utils/Base.java
new file mode 100644
index 0000000000..a265bcab69
--- /dev/null
+++ b/app/utils/src/main/java/processing/utils/Base.java
@@ -0,0 +1,72 @@
+package processing.utils;
+
+import java.io.File;
+import java.io.InputStream;
+
+public class Base {
+ static public boolean DEBUG = System.getenv().containsKey("DEBUG");
+
+
+ static public boolean isCommandLine() {
+ return Boolean.getBoolean("processing.cli");
+ }
+
+
+ /**
+ * Convenience method to get a File object for the specified filename inside
+ * the settings folder. Used to get preferences and recent sketch files.
+ *
+ * @param filename A file inside the settings folder.
+ * @return filename wrapped as a File object inside the settings folder
+ */
+ static public File getSettingsFile(String filename) {
+ File settingsFolder = null;
+
+ try {
+ settingsFolder = SettingsResolver.getSettingsFolder();
+
+ // create the folder if it doesn't exist already
+ if (!settingsFolder.exists()) {
+ if (!settingsFolder.mkdirs()) {
+ Messages.showError("Settings issues",
+ "Could not create the folder" +
+ settingsFolder, null);
+ }
+ }
+ } catch (Exception e) {
+ Messages.showError("An rare and unknowable thing happened",
+ "Could not get the settings folder.", e);
+ }
+ return new File(settingsFolder, filename);
+ }
+
+
+ /**
+ * Retrieves an InputStream to a resource file located within the JAR package.
+ * This method uses Java's resource loading system to fetch files bundled in the application,
+ * such as configuration files, data, or assets.
+ *
+ * @param resourceName The name or path of the resource file to be loaded.
+ * This should match the location in the JAR's structure.
+ * @return An InputStream that can be used to read the contents of the requested resource file,
+ * or null if the resource is not found.
+ * @throws IllegalArgumentException if the resource cannot be located.
+ */
+ public static InputStream getLibStream(String resourceName) {
+ if (resourceName == null || resourceName.isEmpty()) {
+ throw new IllegalArgumentException("Resource name cannot be null or empty");
+ }
+ // Ensure the resource name starts with exactly one "/"
+ if (!resourceName.startsWith("/")) {
+ resourceName = "/" + resourceName; // Prepend "/" if missing
+ } else {
+ resourceName = resourceName.replaceAll("^/+","/"); // Ensure only one "/" at start
+ }
+
+ InputStream stream = Base.class.getResourceAsStream(resourceName);
+ if (stream == null) {
+ throw new IllegalArgumentException("Resource not found: " + resourceName);
+ }
+ return stream;
+ }
+}
diff --git a/app/utils/src/main/java/processing/utils/Main.java b/app/utils/src/main/java/processing/utils/Main.java
new file mode 100644
index 0000000000..04449f5411
--- /dev/null
+++ b/app/utils/src/main/java/processing/utils/Main.java
@@ -0,0 +1,27 @@
+package processing.utils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+//TIP To Run code, press or
+// click the icon in the gutter.
+public class Main {
+ public static void main(String[] args) {
+ //TIP Press with your caret at the highlighted text
+ // to see how IntelliJ IDEA suggests fixing it.
+ InputStream inputStream = Base.getLibStream("/defaults.txt");
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ // Print each line from the InputStream
+ System.out.println(line);
+ }
+ } catch (IOException e) {
+ // Handle error if an issue occurs while reading
+ System.err.println("Error reading from InputStream: " + e.getMessage());
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/utils/src/main/java/processing/utils/Messages.kt b/app/utils/src/main/java/processing/utils/Messages.kt
new file mode 100644
index 0000000000..c6418e9df7
--- /dev/null
+++ b/app/utils/src/main/java/processing/utils/Messages.kt
@@ -0,0 +1,144 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /*
+ Part of the Processing project - http://processing.org
+
+ Copyright (c) 2015 The Processing Foundation
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ version 2, as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+package processing.utils
+
+import java.awt.Frame
+import javax.swing.JOptionPane
+
+open class Messages {
+ companion object {
+ /**
+ * "No cookie for you" type messages. Nothing fatal or all that
+ * much of a bummer, but something to notify the user about.
+ */
+ @JvmStatic
+ fun showMessage(title: String = "Message", message: String) {
+ if (Base.isCommandLine()) {
+ println("$title: $message")
+ } else {
+ JOptionPane.showMessageDialog(
+ Frame(), message, title,
+ JOptionPane.INFORMATION_MESSAGE
+ )
+ }
+ }
+
+
+ /**
+ * Non-fatal error message with optional stack trace side dish.
+ */
+ /**
+ * Non-fatal error message.
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun showWarning(title: String = "Warning", message: String, e: Throwable? = null) {
+ if (Base.isCommandLine()) {
+ println("$title: $message")
+ } else {
+ JOptionPane.showMessageDialog(
+ Frame(), message, title,
+ JOptionPane.WARNING_MESSAGE
+ )
+ }
+ e?.printStackTrace()
+ }
+
+
+ /**
+ * Show an error message that's actually fatal to the program.
+ * This is an error that can't be recovered. Use showWarning()
+ * for errors that allow P5 to continue running.
+ */
+ @JvmStatic
+ fun showError(title: String? = "Error", message: String, e: Throwable?) {
+ if (Base.isCommandLine()) {
+ System.err.println("$title: $message")
+ } else {
+ JOptionPane.showMessageDialog(
+ Frame(), message, title,
+ JOptionPane.ERROR_MESSAGE
+ )
+ }
+ e?.printStackTrace()
+ System.exit(1)
+ }
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+ @JvmStatic
+ @Deprecated("Use log() instead")
+ fun log(from: Any, message: String) {
+ if (Base.DEBUG) {
+ val callingClass = Throwable()
+ .stackTrace[2]
+ .className
+ .formatClassName()
+ println("$callingClass: $message")
+ }
+ }
+
+ @JvmStatic
+ fun log(message: String?) {
+ if (Base.DEBUG) {
+ val callingClass = Throwable()
+ .stackTrace[2]
+ .className
+ .formatClassName()
+ println("$callingClass$message")
+ }
+ }
+
+ @JvmStatic
+ fun logf(message: String?, vararg args: Any?) {
+ if (Base.DEBUG) {
+ val callingClass = Throwable()
+ .stackTrace[2]
+ .className
+ .formatClassName()
+ System.out.printf("$callingClass$message", *args)
+ }
+ }
+
+ @JvmStatic
+ @JvmOverloads
+ fun err(message: String?, e: Throwable? = null) {
+ if (Base.DEBUG) {
+ if (message != null) {
+ val callingClass = Throwable()
+ .stackTrace[4]
+ .className
+ .formatClassName()
+ System.err.println("$callingClass$message")
+ }
+ e?.printStackTrace()
+ }
+ }
+ }
+}
+
+// Helper functions to give the base classes a color
+fun String.formatClassName() = this
+ .replace("processing.", "")
+ .replace(".", "/")
+ .padEnd(40)
+ .colorizePathParts()
+fun String.colorizePathParts() = split("/").joinToString("/") { part ->
+ "\u001B[${31 + (part.hashCode() and 0x7).rem(6)}m$part\u001B[0m"
+}
\ No newline at end of file
diff --git a/app/utils/src/main/java/processing/utils/Preferences.java b/app/utils/src/main/java/processing/utils/Preferences.java
new file mode 100644
index 0000000000..faf153e75a
--- /dev/null
+++ b/app/utils/src/main/java/processing/utils/Preferences.java
@@ -0,0 +1,308 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Part of the Processing project - http://processing.org
+
+ Copyright (c) 2014-19 The Processing Foundation
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.utils;
+
+import java.awt.Color;
+import java.awt.SystemColor;
+import java.io.*;
+import java.util.*;
+
+import processing.core.*;
+
+
+/**
+ * Storage class for user preferences and environment settings.
+ *
+ * This class does not use the Properties class because .properties files use
+ * ISO 8859-1 encoding, which is highly likely to be a problem when trying to
+ * save sketch folders and locations. Like the rest of Processing, we use UTF8.
+ *
+ * We don't use the Java Preferences API because it would entail writing to
+ * the registry (on Windows), or an obscure file location (on Mac OS X) and
+ * make it far more difficult (impossible) to remove the preferences.txt to
+ * reset them (when they become corrupt), or to find the the file to make
+ * edits for numerous obscure preferences that are not part of the preferences
+ * window. If we added a generic editor (e.g. about:config in Mozilla) for
+ * such things, we could start using the Java Preferences API. But wow, that
+ * sounds like a lot of work. Not unlike writing this paragraph.
+ */
+public class Preferences {
+ // had to rename the defaults file because people were editing it
+ static final String DEFAULTS_FILE = "defaults.txt"; //$NON-NLS-1$
+ static final String PREFS_FILE = "preferences.txt"; //$NON-NLS-1$
+
+ static Map defaults;
+ static Map table = new HashMap<>();
+ static File preferencesFile;
+ private static boolean initialized = false;
+
+
+// /** @return true if the sketchbook file did not exist */
+// static public boolean init() {
+ static public void init() {
+ initialized = true;
+ // start by loading the defaults, in case something
+ // important was deleted from the user prefs
+ try {
+ // Name changed for 2.1b2 to avoid problems with users modifying or
+ // replacing the file after doing a search for "preferences.txt".
+ load(Base.getLibStream(DEFAULTS_FILE));
+ } catch (Exception e) {
+ Messages.showError(null, "Could not read default settings.\n" +
+ "You'll need to reinstall Processing.", e);
+ }
+
+ // Clone the defaults, then override any them with the user's preferences.
+ // This ensures that any new/added preference will be present.
+ defaults = new HashMap<>(table);
+
+ // next load user preferences file
+ preferencesFile = Base.getSettingsFile(PREFS_FILE);
+ boolean firstRun = !preferencesFile.exists();
+ if (!firstRun) {
+ try {
+ load(new FileInputStream(preferencesFile));
+
+ } catch (Exception ex) {
+ Messages.showError("Error reading preferences",
+ "Error reading the preferences file. " +
+ "Please delete (or move)\n" +
+ preferencesFile.getAbsolutePath() +
+ " and restart Processing.", ex);
+ }
+ }
+
+ if (firstRun) {
+ // create a new preferences file if none exists
+ // saves the defaults out to the file
+ save();
+ }
+
+ }
+
+
+ /**
+ * For testing, pretend to load preferences without a real file.
+ */
+ static public void skipInit() {
+ initialized = true;
+ }
+
+
+ static public String getPreferencesPath() {
+ return preferencesFile.getAbsolutePath();
+ }
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ /**
+ * Load a set of key/value pairs from a UTF-8 encoded file into 'table'.
+ * For 3.0a6, this removes any platform-specific extensions from keys, so
+ * that we don't have platform-specific entries in a user's preferences.txt
+ * file, which would require all prefs to be changed twice, or risk being
+ * overwritten by the unchanged platform-specific version on reload.
+ */
+ static public void load(InputStream input) throws IOException {
+ HashMap platformSpecific = new HashMap<>();
+
+ String[] lines = PApplet.loadStrings(input); // Reads as UTF-8
+ for (String line : lines) {
+ if ((line.length() == 0) ||
+ (line.charAt(0) == '#')) continue;
+
+ // this won't properly handle = signs being in the text
+ int equals = line.indexOf('=');
+ if (equals != -1) {
+ String key = line.substring(0, equals).trim();
+ String value = line.substring(equals + 1).trim();
+ if (!isPlatformSpecific(key, value, platformSpecific)) {
+ table.put(key, value);
+ }
+ }
+ }
+ // Now override the keys with any platform-specific defaults we've found.
+ for (String key : platformSpecific.keySet()) {
+ table.put(key, platformSpecific.get(key));
+ }
+ }
+
+
+ /**
+ * @param key original key (may include platform extension)
+ * @param value the value that goes with the key
+ * @param specific where to put the key/value pairs for *this* platform
+ * @return true if a platform-specific key
+ */
+ static protected boolean isPlatformSpecific(String key, String value,
+ Map specific) {
+ for (String platform : PConstants.platformNames) {
+ String ext = "." + platform;
+ if (key.endsWith(ext)) {
+ String thisPlatform = PConstants.platformNames[PApplet.platform];
+ if (platform.equals(thisPlatform)) {
+ key = key.substring(0, key.lastIndexOf(ext));
+ // store this for later overrides
+ specific.put(key, value);
+ //} else {
+ // ignore platform-specific defaults for other platforms,
+ // but return 'true' because it needn't be added to the big list
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ static public void save() {
+ // On startup, this is null, but ignore it. It's trying to update the
+ // prefs for the open sketch before Preferences.init() has been called.
+ if (preferencesFile != null) {
+ try {
+ File dir = preferencesFile.getParentFile();
+ File preferencesTemp = File.createTempFile("preferences", ".txt", dir);
+ if (!preferencesTemp.setWritable(true, false)) {
+ throw new IOException("Could not set " + preferencesTemp + " writable");
+ }
+
+ // Fix for 0163 to properly use Unicode when writing preferences.txt
+ PrintWriter writer = PApplet.createWriter(preferencesTemp);
+
+ String[] keyList = table.keySet().toArray(new String[0]);
+ // Sorting is really helpful for debugging, diffing, and finding keys
+ keyList = PApplet.sort(keyList);
+ for (String key : keyList) {
+ writer.println(key + "=" + table.get(key)); //$NON-NLS-1$
+ }
+ writer.flush();
+ writer.close();
+
+ // Rename preferences.txt to preferences.old
+ File oldPreferences = new File(dir, "preferences.old");
+ if (oldPreferences.exists()) {
+ if (!oldPreferences.delete()) {
+ throw new IOException("Could not delete preferences.old");
+ }
+ }
+ if (preferencesFile.exists() &&
+ !preferencesFile.renameTo(oldPreferences)) {
+ throw new IOException("Could not replace preferences.old");
+ }
+ // Make the temporary file into the real preferences
+ if (!preferencesTemp.renameTo(preferencesFile)) {
+ throw new IOException("Could not move preferences file into place");
+ }
+
+ } catch (IOException e) {
+ Messages.showWarning("Preferences",
+ "Could not save the Preferences file.", e);
+ }
+ }
+ }
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ // all the information from preferences.txt
+
+ static public String get(String attribute /*, String defaultValue */) {
+ if (!initialized) {
+ throw new RuntimeException(
+ "Tried reading preferences prior to initialization."
+ );
+ }
+ return table.get(attribute);
+ }
+
+
+ static public String getDefault(String attribute) {
+ return defaults.get(attribute);
+ }
+
+
+ static public void set(String attribute, String value) {
+ table.put(attribute, value);
+ }
+
+
+ static public void unset(String attribute) {
+ table.remove(attribute);
+ }
+
+
+ static public boolean getBoolean(String attribute) {
+ String value = get(attribute); //, null);
+ return Boolean.parseBoolean(value);
+
+ /*
+ supposedly not needed, because anything besides 'true'
+ (ignoring case) will just be false.. so if malformed -> false
+ if (value == null) return defaultValue;
+
+ try {
+ return (new Boolean(value)).booleanValue();
+ } catch (NumberFormatException e) {
+ System.err.println("expecting an integer: " + attribute + " = " + value);
+ }
+ return defaultValue;
+ */
+ }
+
+
+ static public void setBoolean(String attribute, boolean value) {
+ set(attribute, value ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+
+ static public int getInteger(String attribute /*, int defaultValue*/) {
+ return Integer.parseInt(get(attribute));
+ }
+
+
+ static public void setInteger(String key, int value) {
+ set(key, String.valueOf(value));
+ }
+
+
+ static public Color getColor(String name) {
+ Color parsed = Color.GRAY; // set a default
+ String s = get(name);
+ if ((s != null) && (s.indexOf("#") == 0)) { //$NON-NLS-1$
+ try {
+ parsed = new Color(Integer.parseInt(s.substring(1), 16));
+ } catch (Exception ignored) { }
+ }
+ return parsed;
+ }
+
+
+ static public void setColor(String attr, Color what) {
+ set(attr, "#" + PApplet.hex(what.getRGB() & 0xffffff, 6)); //$NON-NLS-1$
+ }
+
+}
diff --git a/app/utils/src/main/java/processing/utils/SettingsResolver.java b/app/utils/src/main/java/processing/utils/SettingsResolver.java
new file mode 100644
index 0000000000..b58f99ef4b
--- /dev/null
+++ b/app/utils/src/main/java/processing/utils/SettingsResolver.java
@@ -0,0 +1,35 @@
+package processing.utils;
+
+import processing.utils.settingslocation.*;
+
+import java.io.File;
+
+public class SettingsResolver {
+ /**
+ * Get the directory that can store settings. (Library on OS X, App Data or
+ * something similar on Windows, a dot folder on Linux.) Removed this as a
+ * preference for 3.0a3 because we need this to be stable, but adding back
+ * for 4.0 beta 4 so that folks can do 'portable' versions again.
+ */
+ static public File getSettingsFolder() throws Exception {
+ File settingsFolder = null;
+
+ String os = System.getProperty("os.name");
+ DefaultLocation loc = null;
+
+ if (os.contains("Mac")) {
+ loc = new MacLocation();
+ } else if (os.contains("Linux")) {
+ loc = new LinuxLocation();
+ } else if (os.contains("Windows")) {
+ loc = new WindowsLocation();
+ } else {
+ loc = new DefaultLocation();
+ }
+
+ settingsFolder = loc.getSettingsFolder();
+
+ return settingsFolder;
+
+ }
+}
diff --git a/app/src/processing/app/Util.java b/app/utils/src/main/java/processing/utils/Util.java
similarity index 99%
rename from app/src/processing/app/Util.java
rename to app/utils/src/main/java/processing/utils/Util.java
index 4c94af5fe5..281de6fb6d 100644
--- a/app/src/processing/app/Util.java
+++ b/app/utils/src/main/java/processing/utils/Util.java
@@ -20,7 +20,7 @@
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
-package processing.app;
+package processing.utils;
import java.io.*;
import java.net.HttpURLConnection;
@@ -278,13 +278,14 @@ static public File createTempFolder(String prefix, String suffix,
static public File getProcessingTemp() throws IOException {
+ String os = System.getProperty("os.name");
String tmpDir = System.getProperty("java.io.tmpdir");
File directory = new File(tmpDir, "processing");
if (!directory.exists()) {
if (directory.mkdirs()) {
// Set the parent directory writable for multi-user machines.
// https://github.com/processing/processing4/issues/666
- if (Platform.isLinux()) {
+ if (os.contains("Linux")) {
Path path = directory.toPath();
Files.setPosixFilePermissions(path, PosixFilePermissions.fromString("rwxrwxrwx"));
@@ -347,8 +348,9 @@ static public void copyDir(File sourceDir,
static public void copyDirNative(File sourceDir,
File targetDir) throws IOException {
+ String os = System.getProperty("os.name");
Process process;
- if (Platform.isMacOS() || Platform.isLinux()) {
+ if (os.contains("Mac") || os.contains("Linux")) {
process = Runtime.getRuntime().exec(new String[] {
"cp", "-a", sourceDir.getAbsolutePath(), targetDir.getAbsolutePath()
});
diff --git a/app/utils/src/main/java/processing/utils/settingslocation/DefaultLocation.java b/app/utils/src/main/java/processing/utils/settingslocation/DefaultLocation.java
new file mode 100644
index 0000000000..ecf8fbdd0e
--- /dev/null
+++ b/app/utils/src/main/java/processing/utils/settingslocation/DefaultLocation.java
@@ -0,0 +1,17 @@
+package processing.utils.settingslocation;
+
+import java.io.File;
+
+public class DefaultLocation {
+ /**
+ * This function should throw an exception or return a value.
+ * Do not return null.
+ */
+ public File getSettingsFolder() throws Exception {
+ // If no subclass has a behavior, default to making a
+ // ".processing" directory in the user's home directory.
+ File home = new File(System.getProperty("user.home"));
+ return new File(home, ".processing");
+ }
+
+}
diff --git a/app/utils/src/main/java/processing/utils/settingslocation/LinuxLocation.java b/app/utils/src/main/java/processing/utils/settingslocation/LinuxLocation.java
new file mode 100644
index 0000000000..439664de19
--- /dev/null
+++ b/app/utils/src/main/java/processing/utils/settingslocation/LinuxLocation.java
@@ -0,0 +1,60 @@
+package processing.utils.settingslocation;
+
+import processing.core.PApplet;
+import processing.utils.Messages;
+
+import java.io.File;
+
+public class LinuxLocation extends DefaultLocation{
+ String homeDir;
+
+ @Override
+ public File getSettingsFolder() throws Exception {
+ // https://github.com/processing/processing4/issues/203
+ // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+
+ File configHome = null;
+
+ // Check to see if the user has set a different location for their config
+ String configHomeEnv = System.getenv("XDG_CONFIG_HOME");
+ if (configHomeEnv != null && !configHomeEnv.isBlank()) {
+ configHome = new File(configHomeEnv);
+ if (!configHome.exists()) {
+ Messages.err("XDG_CONFIG_HOME is set to " + configHomeEnv + " but does not exist.");
+ configHome = null; // don't use non-existent folder
+ }
+ }
+ String snapUserCommon = System.getenv("SNAP_USER_COMMON");
+ if (snapUserCommon != null && !snapUserCommon.isBlank()) {
+ configHome = new File(snapUserCommon);
+ }
+ // If not set properly, use the default
+ if (configHome == null) {
+ configHome = new File(getHomeDir(), ".config");
+ }
+ return new File(configHome, "processing");
+ }
+
+
+ // Java sets user.home to be /root for execution with sudo.
+ // This method attempts to use the user's real home directory instead.
+ public String getHomeDir() {
+ if (homeDir == null) {
+ // get home directory of SUDO_USER if set, else use user.home
+ homeDir = System.getProperty("user.home");
+ String sudoUser = System.getenv("SUDO_USER");
+ if (sudoUser != null && sudoUser.length() != 0) {
+ try {
+ homeDir = getHomeDir(sudoUser);
+ } catch (Exception ignored) { }
+ }
+ }
+ return homeDir;
+ }
+
+
+ static public String getHomeDir(String user) throws Exception {
+ Process p = PApplet.exec("/bin/sh", "-c", "echo ~" + user);
+ return PApplet.createReader(p.getInputStream()).readLine();
+ }
+}
diff --git a/app/utils/src/main/java/processing/utils/settingslocation/MacLocation.java b/app/utils/src/main/java/processing/utils/settingslocation/MacLocation.java
new file mode 100644
index 0000000000..2029427870
--- /dev/null
+++ b/app/utils/src/main/java/processing/utils/settingslocation/MacLocation.java
@@ -0,0 +1,25 @@
+package processing.utils.settingslocation;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+public class MacLocation extends DefaultLocation{
+
+ public File getSettingsFolder() throws Exception {
+ return new File(getLibraryFolder(), "Processing");
+ }
+
+
+ // TODO I suspect this won't work much longer, since access to the user's
+ // home directory seems verboten on more recent macOS versions [fry 191008]
+ // However, anecdotally it seems that just using the name works,
+ // and the localization is handled transparently. [fry 220116]
+ // https://github.com/processing/processing4/issues/9
+ protected String getLibraryFolder() throws FileNotFoundException {
+ File folder = new File(System.getProperty("user.home"), "Library");
+ if (!folder.exists()) {
+ throw new FileNotFoundException("Folder missing: " + folder);
+ }
+ return folder.getAbsolutePath();
+ }
+}
diff --git a/app/utils/src/main/java/processing/utils/settingslocation/WindowsLocation.java b/app/utils/src/main/java/processing/utils/settingslocation/WindowsLocation.java
new file mode 100644
index 0000000000..e0f6f0dc35
--- /dev/null
+++ b/app/utils/src/main/java/processing/utils/settingslocation/WindowsLocation.java
@@ -0,0 +1,69 @@
+package processing.utils.settingslocation;
+
+import com.sun.jna.platform.win32.Shell32Util;
+import com.sun.jna.platform.win32.ShlObj;
+import processing.utils.Messages;
+import processing.utils.Util;
+
+import java.io.File;
+import java.io.IOException;
+
+public class WindowsLocation extends DefaultLocation{
+ static final String APP_NAME = "Processing";
+
+ // looking for Documents and Settings/blah/Application Data/Processing
+ public File getSettingsFolder() throws Exception {
+
+ try {
+ String appDataRoaming = getAppDataPath();
+ if (appDataRoaming != null) {
+ File settingsFolder = new File(appDataRoaming, APP_NAME);
+ if (settingsFolder.exists() || settingsFolder.mkdirs()) {
+ return settingsFolder;
+ }
+ }
+
+ String appDataLocal = getLocalAppDataPath();
+ if (appDataLocal != null) {
+ File settingsFolder = new File(appDataLocal, APP_NAME);
+ if (settingsFolder.exists() || settingsFolder.mkdirs()) {
+ return settingsFolder;
+ }
+ }
+
+ if (appDataRoaming == null && appDataLocal == null) {
+ throw new IOException("Could not get the AppData folder");
+ }
+
+ // https://github.com/processing/processing/issues/3838
+ throw new IOException("Permissions error: make sure that " +
+ appDataRoaming + " or " + appDataLocal +
+ " is writable.");
+
+ } catch (UnsatisfiedLinkError ule) {
+ String path = new File("lib").getCanonicalPath();
+
+ String msg = Util.containsNonASCII(path) ?
+ """
+ Please move Processing to a location with only
+ ASCII characters in the path and try again.
+ https://github.com/processing/processing/issues/3543
+ """ :
+ "Could not find JNA support files, please reinstall Processing.";
+ Messages.showError("Windows JNA Problem", msg, ule);
+ return null; // unreachable
+ }
+ }
+
+
+ /** Get the Users\name\AppData\Roaming path to write settings files. */
+ static private String getAppDataPath() {
+ return Shell32Util.getSpecialFolderPath(ShlObj.CSIDL_APPDATA, true);
+ }
+
+
+ /** Get the Users\name\AppData\Local path as a settings fallback. */
+ static private String getLocalAppDataPath() {
+ return Shell32Util.getSpecialFolderPath(ShlObj.CSIDL_LOCAL_APPDATA, true);
+ }
+}
diff --git a/app/utils/src/main/resources/defaults.txt b/app/utils/src/main/resources/defaults.txt
new file mode 100644
index 0000000000..6e3e00f0d6
--- /dev/null
+++ b/app/utils/src/main/resources/defaults.txt
@@ -0,0 +1,309 @@
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+
+# DO NOT MAKE CHANGES TO THIS FILE!!!
+
+# These are the default preferences. If you want to modify
+# them directly, use the per-user local version of the file:
+
+# Users -> [username] -> AppData -> Roaming ->
+# Processing -> preferences.txt (on Windows 10)
+
+# ~/Library -> Processing -> preferences.txt (on macOS)
+
+# ~/.config/processing -> preferences.txt (on Linux)
+
+# The exact location of your preferences file can be found at
+# the bottom of the Preferences window inside Processing.
+
+# Because AppData and Application Data may be considered
+# hidden or system folders on Windows, you'll have to ensure
+# that they're visible in order to get at preferences.txt
+
+# You'll have problems running Processing if you incorrectly
+# modify lines in this file. It will probably not start at all.
+
+# AGAIN, DO NOT ALTER THIS FILE! I'M ONLY YELLING BECAUSE I LOVE YOU!
+
+
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+
+# If you don't want users to have their sketchbook default to
+# "My Documents/Processing" on Windows and "Documents/Processing" on OS X,
+# set this to another path that will be used by default.
+# Note that this path must exist already otherwise it won't see
+# the sketchbook folder, and will instead assume the sketchbook
+# has gone missing, and that it should instead use the default.
+# In 4.0, the location has changed.
+#sketchbook.path.four=
+
+# Whether or not to show the Welcome screen for 4.0
+# (It's always available under Help → Welcome)
+welcome.four.show = true
+welcome.four.seen = false
+
+# Set 'true' for the default behavior before 4.0, where the
+# main tab must have the same name as the sketch folder
+editor.sync_folder_and_filename = true
+
+# By default, contributions are moved to backup folders when
+# they are removed or replaced. The backups can be found at
+# sketchbook/libraries/old, sketchbook/tools/old, and sketchbook/modes/old
+
+# true to backup contributions when "Remove" button is pressed
+contribution.backup.on_remove = true
+# true to backup contributions when installing a newer version
+contribution.backup.on_install = true
+
+recent.count = 10
+
+# Default to the native (AWT) file selector where possible
+chooser.files.native = true
+# We were shutting this off on macOS because it broke Copy/Paste:
+# https://github.com/processing/processing/issues/1035
+# But removing again for 4.0 alpha 5, because the JFileChooser is awful,
+# and worse on Big Sur, so a bigger problem than the Copy/Paste issue.
+# https://github.com/processing/processing4/issues/77
+#chooser.files.native.macos = false
+
+# set to 'lab' to interpolate theme gradients using L*a*b* color space
+theme.gradient.method = rgb
+
+
+# by default, check the processing server for any updates
+# (please avoid disabling, this also helps us know basic numbers
+# on how many people are using Processing)
+update.check = true
+
+# on windows, automatically associate .pde files with processing.exe
+platform.auto_file_type_associations = true
+
+
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+
+# default size for the main window
+editor.window.width.default = 700
+editor.window.height.default = 600
+
+editor.window.width.min = 400
+editor.window.height.min = 500
+# tested as approx 440 on OS X
+editor.window.height.min.macos = 450
+# tested to be 515 on Windows XP, this leaves some room
+editor.window.height.min.windows = 530
+# tested with Raspberry Pi display
+editor.window.height.min.linux = 480
+
+# scaling for the interface (to handle Windows and Linux HiDPI displays)
+editor.zoom = 100%
+# automatically set based on system dpi (only helps on Windows)
+editor.zoom.auto = true
+
+# Use the default monospace font included in lib/fonts.
+# (As of Processing 4 alpha 5, that's Source Code Pro)
+editor.font.family = processing.mono
+editor.font.size = 12
+
+# To reset everyone's default, replaced editor.antialias with editor.smooth
+# for 2.1. Fonts are unusably gross on OS X (and Linux) w/o smoothing and
+# the Oracle JVM, and many longtime users have anti-aliasing turned off.
+editor.smooth = true
+
+# blink the caret by default
+editor.caret.blink = true
+# change to true to use a block (instead of a bar)
+editor.caret.block = false
+
+# enable ctrl-ins, shift-ins, shift-delete for cut/copy/paste
+# on windows and linux, but disable on the mac
+editor.keys.alternative_cut_copy_paste = true
+editor.keys.alternative_cut_copy_paste.macos = false
+
+# true if shift-backspace sends the delete character,
+# false if shift-backspace just means backspace
+editor.keys.shift_backspace_is_delete = false
+
+# home and end keys should only travel to the start/end of the current line
+editor.keys.home_and_end_travel_far = false
+# home and end keys move to the first/last non-whitespace character,
+# and move to the actual start/end when pressed a second time.
+# Only works if editor.keys.home_and_end_travel_far is false.
+editor.keys.home_and_end_travel_smart = true
+# The OS X HI Guidelines say that home/end are relative to the document,
+# but that drives some people nuts. This pref enables/disables it.
+editor.keys.home_and_end_travel_far.macos = true
+
+# Enable/disable support for complex scripts. Used for Japanese and others,
+# but disable when not needed, otherwise basic Western European chars break.
+editor.input_method_support = false
+
+# convert tabs to spaces? how many spaces?
+editor.tabs.expand = true
+editor.tabs.size = 2
+
+# Set to true to automatically close [ { ( " and '
+editor.completion.auto_close = false
+
+# automatically indent each line
+editor.indent = true
+
+# Whether to check files to see if they've been modified externally
+editor.watcher = true
+# Set true to enable debugging, since this is quirky on others' machines
+editor.watcher.debug = false
+# The window of time (in milliseconds) in which a change won't be counted
+editor.watcher.window = 1500
+
+# Format and search engine to use for online queries
+search.format = https://google.com/search?q=%s
+
+# font choice and size for the console
+console.font.size = 12
+
+# number of lines to show by default
+console.lines = 4
+
+# Number of blank lines to advance/clear console.
+# Note that those lines are also printed in the terminal when
+# Processing is executed there.
+# Setting to 0 stops this behavior.
+console.head_padding = 10
+
+# Set to false to disable automatically clearing the console
+# each time 'run' is hit
+# If one sets it to false, one may also want to set 'console.head_padding'
+# to a positive number to separate outputs from different runs.
+console.auto_clear = true
+
+# number of days of history to keep around before cleaning
+# setting to 0 will never clean files
+console.temp.days = 7
+
+# set the maximum number of lines remembered by the console
+# the default is 500, lengthen at your own peril
+console.scrollback.lines = 500
+console.scrollback.chars = 40000
+
+# Any additional Java options when running.
+# If you change this and can't run things, it's your own durn fault.
+run.options =
+
+# settings for the -XmsNNNm and -XmxNNNm command line option
+run.options.memory = false
+run.options.memory.initial = 64
+run.options.memory.maximum = 512
+
+# Index of the display to use for running sketches (starts at 1).
+# Kept this 1-indexed because older vesions of Processing were setting
+# the preference even before it was being used.
+# -1 means the default display, 0 means all displays
+run.display = -1
+
+# set internally because it comes from the system
+#run.window.bgcolor=
+
+# set to false to open a new untitled window when closing the last window
+# (otherwise, the environment will quit)
+# default to the relative norm for the different platforms,
+# but the setting can be changed in the prefs dialog anyway
+#sketchbook.closing_last_window_quits = true
+#sketchbook.closing_last_window_quits.macos = false
+
+editor.untitled.prefix=sketch_
+# The old (pre-1.0, back for 2.0) style for default sketch name.
+# If you change this, be careful that this will work with your language
+# settings. For instance, MMMdd won't work on Korean-language systems
+# because it'll insert non-ASCII characters and break the environment.
+# https://github.com/processing/processing/issues/322
+editor.untitled.suffix=yyMMdd
+
+# replace underscores in .pde file names with spaces
+sketch.name.replace_underscore = true
+
+# what to use for generating sketch names (change in the prefs window)
+#sketch.name.approach =
+
+# number of days of build history and other temp files to keep around
+# these are kept around for debugging purposes, and in case code is lost
+temp.days = 7
+
+
+# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+
+# whether or not to export as full screen (present) mode
+export.application.fullscreen = false
+
+# whether to show the stop button when exporting to application
+export.application.stop = true
+
+# embed Java by default for lower likelihood of problems
+export.application.embed_java = true
+
+# set to false to no longer delete application folders before export
+# (removed from the Preferences windows in 4.0 beta 9)
+export.delete_target_folder = true
+
+# may be useful when attempting to debug the preprocessor
+preproc.save_build_files=false
+
+# allows various preprocessor features to be toggled
+# in case they are causing problems
+
+# preprocessor: pde.g
+preproc.color_datatype = true
+preproc.web_colors = true
+preproc.enhanced_casting = true
+
+# preprocessor: PdeEmitter.java
+preproc.substitute_floats = true
+
+# PdePreproc.java
+# writes out the parse tree as parseTree.xml, which can be usefully
+# viewed in (at least) Mozilla or IE. useful when debugging the preprocessor.
+preproc.output_parse_tree = false
+
+# set to the program to be used for opening HTML files, folders, etc.
+#launcher.linux = xdg-open
+
+# FULL SCREEN (PRESENT MODE)
+run.present.bgcolor = #666666
+run.present.stop.color = #cccccc
+
+# PROXIES
+# Set a proxy server for folks that require it. This will allow the update
+# checker and the contrib manager to run properly in those environments.
+# This changed from proxy.host and proxy.port to proxy.http.host and
+# proxy.http.port in 3.0a8. In addition, https and socks were added.
+proxy.http.host=
+proxy.http.port=
+proxy.https.host=
+proxy.https.port=
+proxy.socks.host=
+proxy.socks.port=
+# Example of usage (replace 'http' with 'https' or 'socks' as needed)
+#proxy.http.host=proxy.example.com
+#proxy.http.port=8080
+# Whether to use the system proxy by default
+proxy.system=true
+
+# PDE X
+pdex.errorCheckEnabled = true
+pdex.warningsEnabled = true
+pdex.writeErrorLogs = false
+
+pdex.autoSave.autoSaveEnabled = false
+pdex.autoSaveInterval = 5
+pdex.autoSave.promptDisplay = true
+pdex.autoSave.autoSaveByDefault = true
+
+# Enable auto-completion when hitting ctrl-space
+pdex.completion = false
+# Setting this true will show completions whenever available, not just after ctrl-space
+pdex.completion.trigger = false
+# Suggest libraries to import when a class is undefined/unavailable
+pdex.suggest.imports = true
+# Set to false to disable ctrl/cmd-click jump to definition
+pdex.inspectMode.hotkey = true
diff --git a/java/build.gradle.kts b/java/build.gradle.kts
index 0f8e052780..d2b74abc4f 100644
--- a/java/build.gradle.kts
+++ b/java/build.gradle.kts
@@ -24,6 +24,7 @@ sourceSets{
dependencies{
implementation(project(":app"))
+ implementation(project(":app:utils"))
implementation(project(":core"))
implementation(project(":java:preprocessor"))
diff --git a/java/src/processing/mode/java/Commander.java b/java/src/processing/mode/java/Commander.java
index 2a2ee9bc0c..d74e3a3041 100644
--- a/java/src/processing/mode/java/Commander.java
+++ b/java/src/processing/mode/java/Commander.java
@@ -30,17 +30,16 @@
import processing.app.Base;
import processing.app.Platform;
-import processing.app.Preferences;
import processing.app.RunnerListener;
import processing.app.Sketch;
import processing.mode.java.preproc.SketchException;
-import processing.app.Util;
+import processing.utils.Util;
import processing.app.contrib.ModeContribution;
import processing.core.PApplet;
import processing.data.StringDict;
import processing.mode.java.runner.Runner;
-
+import processing.utils.Preferences;
/**
* Class to handle running Processing from the command line.
*/
diff --git a/java/src/processing/mode/java/Compiler.java b/java/src/processing/mode/java/Compiler.java
index 3e97931699..a94e0982c6 100644
--- a/java/src/processing/mode/java/Compiler.java
+++ b/java/src/processing/mode/java/Compiler.java
@@ -23,10 +23,10 @@
package processing.mode.java;
-import processing.app.*;
import processing.app.ui.Editor;
import processing.core.*;
import processing.mode.java.preproc.SketchException;
+import processing.utils.Util;
import java.io.*;
import java.lang.reflect.Method;
diff --git a/java/src/processing/mode/java/CompletionPanel.java b/java/src/processing/mode/java/CompletionPanel.java
index 2ba1eb29ef..e0780776d6 100644
--- a/java/src/processing/mode/java/CompletionPanel.java
+++ b/java/src/processing/mode/java/CompletionPanel.java
@@ -41,7 +41,6 @@
import javax.swing.ListSelectionModel;
import javax.swing.border.EmptyBorder;
-import processing.app.Base;
import processing.app.Messages;
import processing.app.Mode;
import processing.app.syntax.JEditTextArea;
diff --git a/java/src/processing/mode/java/JavaBuild.java b/java/src/processing/mode/java/JavaBuild.java
index aed0bc1327..28601325eb 100644
--- a/java/src/processing/mode/java/JavaBuild.java
+++ b/java/src/processing/mode/java/JavaBuild.java
@@ -47,6 +47,7 @@
import processing.mode.java.preproc.PdePreprocessor;
import processing.mode.java.preproc.PreprocessorResult;
import processing.mode.java.preproc.SketchException;
+import processing.utils.Util;
public class JavaBuild {
diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java
index 815792955d..a53c6fd3cc 100644
--- a/java/src/processing/mode/java/JavaEditor.java
+++ b/java/src/processing/mode/java/JavaEditor.java
@@ -29,7 +29,6 @@
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -63,6 +62,9 @@
import processing.mode.java.tweak.SketchParser;
import processing.mode.java.tweak.TweakClient;
+import processing.utils.Util;
+import processing.utils.Preferences;
+
public class JavaEditor extends Editor {
JavaMode jmode;
diff --git a/java/src/processing/mode/java/JavaMode.java b/java/src/processing/mode/java/JavaMode.java
index ca448368eb..5f3dce839b 100644
--- a/java/src/processing/mode/java/JavaMode.java
+++ b/java/src/processing/mode/java/JavaMode.java
@@ -37,6 +37,8 @@
import processing.mode.java.runner.Runner;
import processing.mode.java.tweak.SketchParser;
+import processing.utils.Preferences;
+
public class JavaMode extends Mode {
diff --git a/java/src/processing/mode/java/PreprocService.java b/java/src/processing/mode/java/PreprocService.java
index 4f91505613..1e3fde1789 100644
--- a/java/src/processing/mode/java/PreprocService.java
+++ b/java/src/processing/mode/java/PreprocService.java
@@ -56,7 +56,7 @@
import processing.app.Messages;
import processing.app.Sketch;
import processing.app.SketchCode;
-import processing.app.Util;
+import processing.utils.Util;
import processing.mode.java.preproc.*;
import processing.mode.java.preproc.TextTransform.OffsetMapper;
import processing.data.IntList;
diff --git a/java/src/processing/mode/java/RuntimePathBuilder.java b/java/src/processing/mode/java/RuntimePathBuilder.java
index f60471e0af..715fa0a79e 100644
--- a/java/src/processing/mode/java/RuntimePathBuilder.java
+++ b/java/src/processing/mode/java/RuntimePathBuilder.java
@@ -38,6 +38,7 @@
import processing.app.*;
import processing.mode.java.preproc.ImportStatement;
+import processing.utils.Util;
/**
diff --git a/java/src/processing/mode/java/lsp/PdeAdapter.java b/java/src/processing/mode/java/lsp/PdeAdapter.java
index f4f1f450d2..6b0fc2bd53 100644
--- a/java/src/processing/mode/java/lsp/PdeAdapter.java
+++ b/java/src/processing/mode/java/lsp/PdeAdapter.java
@@ -32,7 +32,6 @@
import processing.app.Base;
import processing.app.contrib.ModeContribution;
import processing.app.Platform;
-import processing.app.Preferences;
import processing.app.Problem;
import processing.app.Sketch;
import processing.app.SketchCode;
@@ -45,6 +44,8 @@
import processing.mode.java.PreprocService;
import processing.mode.java.PreprocSketch;
+import processing.utils.Preferences;
+
import static java.util.Arrays.copyOfRange;
class PdeAdapter {
diff --git a/java/src/processing/mode/java/runner/Runner.java b/java/src/processing/mode/java/runner/Runner.java
index b4dc517707..942a5f10a0 100644
--- a/java/src/processing/mode/java/runner/Runner.java
+++ b/java/src/processing/mode/java/runner/Runner.java
@@ -45,6 +45,8 @@
import com.sun.jdi.request.*;
import processing.mode.java.preproc.SketchException;
+import processing.utils.Preferences;
+
/**
* Runs a compiled sketch. As of release 0136, all sketches are run externally
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 4bdcd880e8..7eacb06877 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -3,6 +3,7 @@ include(
"core",
"core:examples",
"app",
+ "app:utils",
"java",
"java:preprocessor",
"java:libraries:dxf",