Merge pull request #1299 from Stefterv/base-vacuum

This commit is contained in:
Stef Tervelde
2025-10-28 08:34:09 +01:00
committed by GitHub
6 changed files with 219 additions and 226 deletions
+185 -195
View File
@@ -51,11 +51,15 @@ import processing.data.StringList;
* files and images, etc.) that comes from that.
*/
public class Base {
// Added accessors for 0218 because the UpdateCheck class was not properly
// updating the values, due to javac inlining the static final values.
/**
* Revision number, used for update checks and contribution compatibility.
*/
static private final int REVISION = Integer.parseInt(System.getProperty("processing.revision", "1295"));
/** This might be replaced by main() if there's a lib/version.txt file. */
static private String VERSION_NAME = System.getProperty("processing.version", "1295"); //$NON-NLS-1$
/**
* This might be replaced by main() if there's a lib/version.txt file.
*
*/
static private String VERSION_NAME = System.getProperty("processing.version", "1295");
static final public String SKETCH_BUNDLE_EXT = ".pdez";
static final public String CONTRIB_BUNDLE_EXT = ".pdex";
@@ -65,11 +69,12 @@ public class Base {
* if an empty file named 'debug' is found in the settings folder.
* See implementation in createAndShowGUI().
*/
static public boolean DEBUG = Boolean.parseBoolean(System.getenv().getOrDefault("DEBUG", "false"));
/** True if running via Commander. */
/**
* is Processing being run from the command line (true) or from the GUI (false)?
*/
static private boolean commandLine;
/**
@@ -128,105 +133,59 @@ public class Base {
static public void main(final String[] args) {
Messages.log("Starting Processing version" + VERSION_NAME + " revision "+ REVISION);
EventQueue.invokeLater(() -> {
try {
createAndShowGUI(args);
} catch (Throwable t) {
// Windows Defender has been insisting on destroying each new
// release by removing core.jar and other files. Yay!
// https://github.com/processing/processing/issues/5537
if (Platform.isWindows()) {
String mess = t.getMessage();
String missing = null;
if (mess.contains("Could not initialize class com.sun.jna.Native")) {
//noinspection SpellCheckingInspection
missing = "jnidispatch.dll";
} else if (t instanceof NoClassDefFoundError &&
mess.contains("processing/core/PApplet")) {
// Had to change how this was called
// https://github.com/processing/processing4/issues/154
missing = "core.jar";
}
if (missing != null) {
Messages.showError("Necessary files are missing",
"A file required by Processing (" + missing + ") is missing.\n\n" +
"Make sure that you're not trying to run Processing from inside\n" +
"the .zip file you downloaded, and check that Windows Defender\n" +
"has not removed files from the Processing folder.\n\n" +
"(Defender sometimes flags parts of Processing as malware.\n" +
"It is not, but Microsoft has ignored our pleas for help.)", t);
}
}
Messages.showTrace("Unknown Problem",
"A serious error happened during startup. Please report:\n" +
"http://github.com/processing/processing4/issues/new", t, true);
}
run(args);
});
}
/**
* The main run() method, wrapped in a try/catch to
* provide a graceful error message if something goes wrong.
*/
private static void run(String[] args) {
try {
createAndShowGUI(args);
} catch (Throwable t) {
// Windows Defender has been insisting on destroying each new
// release by removing core.jar and other files. Yay!
// https://github.com/processing/processing/issues/5537
if (Platform.isWindows()) {
String mess = t.getMessage();
String missing = null;
if (mess.contains("Could not initialize class com.sun.jna.Native")) {
//noinspection SpellCheckingInspection
missing = "jnidispatch.dll";
} else if (t instanceof NoClassDefFoundError &&
mess.contains("processing/core/PApplet")) {
// Had to change how this was called
// https://github.com/processing/processing4/issues/154
missing = "core.jar";
}
if (missing != null) {
Messages.showError("Necessary files are missing",
"A file required by Processing (" + missing + ") is missing.\n\n" +
"Make sure that you're not trying to run Processing from inside\n" +
"the .zip file you downloaded, and check that Windows Defender\n" +
"has not removed files from the Processing folder.\n\n" +
"(Defender sometimes flags parts of Processing as malware.\n" +
"It is not, but Microsoft has ignored our pleas for help.)", t);
}
}
Messages.showTrace("Unknown Problem",
"A serious error happened during startup. Please report:\n" +
"http://github.com/processing/processing4/issues/new", t, true);
}
}
static private void createAndShowGUI(String[] args) {
// these times are fairly negligible relative to Base.<init>
// long t1 = System.currentTimeMillis();
// TODO: Cleanup old locations if no longer installed
// TODO: Cleanup old locations if current version is installed in the same location
checkVersion();
File versionFile = Platform.getContentFile("lib/version.txt");
if (versionFile != null && versionFile.exists()) {
String[] lines = PApplet.loadStrings(versionFile);
if (lines != null && lines.length > 0) {
if (!VERSION_NAME.equals(lines[0])) {
VERSION_NAME = lines[0];
}
}
}
// Detect settings.txt in the lib folder for portable versions
File settingsFile = Platform.getContentFile("lib/settings.txt");
if (settingsFile != null && settingsFile.exists()) {
try {
Settings portable = new Settings(settingsFile);
String path = portable.get("settings.path");
File folder = new File(path);
boolean success = true;
if (!folder.exists()) {
success = folder.mkdirs();
if (!success) {
Messages.err("Could not create " + folder + " to store settings.");
}
}
if (success) {
if (!folder.canRead()) {
Messages.err("Cannot read from " + folder);
} else if (!folder.canWrite()) {
Messages.err("Cannot write to " + folder);
} else {
settingsOverride = folder.getAbsoluteFile();
}
}
} catch (IOException e) {
Messages.err("Error while reading the settings.txt file", e);
}
}
checkPortable();
Platform.init();
// call after Platform.init() because we need the settings folder
Console.startup();
// Set the debug flag based on a file being present in the settings folder
File debugFile = getSettingsFile("debug");
// If it's a directory, it's a leftover from much older releases
// (2.x? 3.x?) that wrote DebugMode.log files into this directory.
// Could remove the directory, but it's harmless enough that it's
// not worth deleting files in case something could go wrong.
if (debugFile.exists() && debugFile.isFile()) {
DEBUG = true;
}
// Use native popups to avoid looking crappy on macOS
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
// Don't put anything above this line that might make GUI,
// because the platform has to be inited properly first.
@@ -239,8 +198,6 @@ public class Base {
// run static initialization that grabs all the prefs
Preferences.init();
// long t2 = System.currentTimeMillis();
// boolean flag indicating whether to create new server instance or not
boolean createNewInstance = DEBUG || !SingleInstance.alreadyRunning(args);
@@ -250,29 +207,50 @@ public class Base {
return;
}
if (createNewInstance) {
// Set the look and feel before opening the window
try {
Platform.setLookAndFeel();
Platform.setInterfaceZoom();
} catch (Exception e) {
Messages.err("Error while setting up the interface", e); //$NON-NLS-1$
}
// long t3 = System.currentTimeMillis();
// Set the look and feel before opening the window
setLookAndFeel();
// Get the sketchbook path, and make sure it's set properly
locateSketchbookFolder();
// Get the sketchbook path, and make sure it's set properly
locateSketchbookFolder();
// long t4 = System.currentTimeMillis();
// Load colors for UI elements. This must happen after Preferences.init()
// (so that fonts are set) and locateSketchbookFolder() so that a
// theme.txt file in the user's sketchbook folder is picked up.
Theme.init();
// Load colors for UI elements. This must happen after Preferences.init()
// (so that fonts are set) and locateSketchbookFolder() so that a
// theme.txt file in the user's sketchbook folder is picked up.
Theme.init();
// Create a location for untitled sketches
setupUntitleSketches();
// Create a location for untitled sketches
try {
Messages.log("About to create Base...");
try {
final Base base = new Base(args);
base.updateTheme();
Messages.log("Base() constructor succeeded");
// Prevent more than one copy of the PDE from running.
SingleInstance.startServer(base);
handleWelcomeScreen(base);
handleCrustyDisplay();
handleTempCleaning();
} catch (Throwable t) {
// Catch-all to pick up badness during startup.
Throwable err = t;
if (t.getCause() != null) {
// Usually this is the more important piece of information. We'll
// show this one so that it's not truncated in the error window.
err = t.getCause();
}
Messages.showTrace("We're off on the wrong foot",
"An error occurred during startup.", err, true);
}
Messages.log("Done creating Base...");
}
private static void setupUntitleSketches() {
try {
// Users on a shared machine may also share a TEMP folder,
// which can cause naming collisions; use a UUID as the name
// for the subfolder to introduce another layer of indirection.
@@ -285,56 +263,32 @@ public class Base {
String uuid = UUID.randomUUID().toString();
untitledFolder = new File(Util.getProcessingTemp(), uuid);
} catch (IOException e) {
} catch (IOException e) {
Messages.showError("Trouble without a name",
"Could not create a place to store untitled sketches.\n" +
"That's gonna prevent us from continuing.", e);
}
// long t5 = System.currentTimeMillis();
// long t6 = 0; // replaced below, just needs decl outside try { }
Messages.log("About to create Base..."); //$NON-NLS-1$
try {
final Base base = new Base(args);
base.updateTheme();
Messages.log("Base() constructor succeeded");
// t6 = System.currentTimeMillis();
// Prevent more than one copy of the PDE from running.
SingleInstance.startServer(base);
handleWelcomeScreen(base);
handleCrustyDisplay();
handleTempCleaning();
} catch (Throwable t) {
// Catch-all to pick up badness during startup.
Throwable err = t;
if (t.getCause() != null) {
// Usually this is the more important piece of information. We'll
// show this one so that it's not truncated in the error window.
err = t.getCause();
}
Messages.showTrace("We're off on the wrong foot",
"An error occurred during startup.", err, true);
}
Messages.log("Done creating Base..."); //$NON-NLS-1$
// long t10 = System.currentTimeMillis();
// System.out.println("startup took " + (t2-t1) + " " + (t3-t2) + " " + (t4-t3) + " " + (t5-t4) + " " + (t6-t5) + " " + (t10-t6) + " ms");
}
}
private static void setLookAndFeel() {
try {
// Use native popups to avoid looking crappy on macOS
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
Platform.setLookAndFeel();
Platform.setInterfaceZoom();
} catch (Exception e) {
Messages.err("Error while setting up the interface", e); //$NON-NLS-1$
}
}
public void updateTheme() {
try {
//System.out.println("updating theme");
FlatLaf laf = "dark".equals(Theme.get("laf.mode")) ?
new FlatDarkLaf() : new FlatLightLaf();
laf.setExtraDefaults(Collections.singletonMap("@accentColor",
Theme.get("laf.accent.color")));
//System.out.println(laf.getExtraDefaults());
//UIManager.setLookAndFeel(laf);
FlatLaf.setup(laf);
// updateUI() will wipe out our custom components
@@ -449,6 +403,54 @@ public class Base {
}
}
/**
* Check for a version.txt file in the lib folder to override
*/
private static void checkVersion() {
File versionFile = Platform.getContentFile("lib/version.txt");
if (versionFile != null && versionFile.exists()) {
String[] lines = PApplet.loadStrings(versionFile);
if (lines != null && lines.length > 0) {
if (!VERSION_NAME.equals(lines[0])) {
VERSION_NAME = lines[0];
}
}
}
}
/**
* Check for portable settings.txt file in the lib folder
* to override the location of the settings folder.
*/
static void checkPortable() {
// Detect settings.txt in the lib folder for portable versions
File settingsFile = Platform.getContentFile("lib/settings.txt");
if (settingsFile != null && settingsFile.exists()) {
try {
Settings portable = new Settings(settingsFile);
String path = portable.get("settings.path");
File folder = new File(path);
boolean success = true;
if (!folder.exists()) {
success = folder.mkdirs();
if (!success) {
Messages.err("Could not create " + folder + " to store settings.");
}
}
if (success) {
if (!folder.canRead()) {
Messages.err("Cannot read from " + folder);
} else if (!folder.canWrite()) {
Messages.err("Cannot write to " + folder);
} else {
settingsOverride = folder.getAbsoluteFile();
}
}
} catch (IOException e) {
Messages.err("Error while reading the settings.txt file", e);
}
}
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
@@ -484,44 +486,21 @@ public class Base {
public Base(String[] args) throws Exception {
long t1 = System.currentTimeMillis();
ContributionManager.init(this);
long t2 = System.currentTimeMillis();
buildCoreModes();
long t2b = System.currentTimeMillis();
rebuildContribModes();
long t2c = System.currentTimeMillis();
rebuildContribExamples();
long t3 = System.currentTimeMillis();
// Needs to happen after the sketchbook folder has been located.
// Also relies on the modes to be loaded, so it knows what can be
// marked as an example.
Recent.init(this);
long t4 = System.currentTimeMillis();
String lastModeIdentifier = Preferences.get("mode.last"); //$NON-NLS-1$
if (lastModeIdentifier == null) {
nextMode = getDefaultMode();
Messages.log("Nothing set for last.sketch.mode, using default."); //$NON-NLS-1$
} else {
for (Mode m : getModeList()) {
if (m.getIdentifier().equals(lastModeIdentifier)) {
Messages.logf("Setting next mode to %s.", lastModeIdentifier); //$NON-NLS-1$
nextMode = m;
}
}
if (nextMode == null) {
nextMode = getDefaultMode();
Messages.logf("Could not find mode %s, using default.", lastModeIdentifier); //$NON-NLS-1$
}
}
setupNextMode();
//contributionManagerFrame = new ContributionManagerDialog();
long t5 = System.currentTimeMillis();
// Make sure ThinkDifferent has library examples too
nextMode.rebuildLibraryList();
@@ -529,10 +508,20 @@ public class Base {
// menu works on Mac OS X (since it needs examplesFolder to be set).
Platform.initBase(this);
long t6 = System.currentTimeMillis();
// check for updates
UpdateCheck.doCheck(this);
// // Check if there were previously opened sketches to be restored
// boolean opened = restoreSketches();
ContributionListing cl = ContributionListing.getInstance();
cl.downloadAvailableList(this, new ContribProgress(null));
openFilesOrNew(args);
}
private void openFilesOrNew(String[] args) {
// Check if there were previously opened sketches to be restored
// boolean opened = restoreSketches();
boolean opened = false;
// Check if any files were passed in on the command line
@@ -558,8 +547,6 @@ public class Base {
}
}
long t7 = System.currentTimeMillis();
// Create a new empty window (will be replaced with any files to be opened)
if (!opened) {
Messages.log("Calling handleNew() to open a new window");
@@ -567,22 +554,25 @@ public class Base {
} else {
Messages.log("No handleNew(), something passed on the command line");
}
}
long t8 = System.currentTimeMillis();
// check for updates
new UpdateCheck(this);
ContributionListing cl = ContributionListing.getInstance();
cl.downloadAvailableList(this, new ContribProgress(null));
long t9 = System.currentTimeMillis();
Messages.log("core modes: " + (t2b-t2) +
", contrib modes: " + (t2c-t2b) +
", contrib ex: " + (t2c-t2b));
Messages.log("base took " + (t2-t1) + " " + (t3-t2) + " " + (t4-t3) +
" " + (t5-t4) + " t6-t5=" + (t6-t5) + " " + (t7-t6) +
" handleNew=" + (t8-t7) + " " + (t9-t8) + " ms");
private void setupNextMode() {
String lastModeIdentifier = Preferences.get("mode.last"); //$NON-NLS-1$
if (lastModeIdentifier == null) {
nextMode = getDefaultMode();
Messages.log("Nothing set for last.sketch.mode, using default."); //$NON-NLS-1$
} else {
for (Mode m : getModeList()) {
if (m.getIdentifier().equals(lastModeIdentifier)) {
Messages.logf("Setting next mode to %s.", lastModeIdentifier); //$NON-NLS-1$
nextMode = m;
}
}
if (nextMode == null) {
nextMode = getDefaultMode();
Messages.logf("Could not find mode %s, using default.", lastModeIdentifier); //$NON-NLS-1$
}
}
}
+31 -2
View File
@@ -19,7 +19,14 @@ import java.util.prefs.Preferences
import kotlin.concurrent.thread
/**
* This function is the new modern entry point for Processing
* It uses Clikt to provide a command line interface with subcommands
*
* If you want to add new functionality to the CLI, create a new subcommand
* and add it to the list of subcommands below.
*
*/
suspend fun main(args: Array<String>){
Processing()
.subcommands(
@@ -32,6 +39,10 @@ suspend fun main(args: Array<String>){
.main(args)
}
/**
* The main Processing command, will open the ide if no subcommand is provided
* Will also launch the `updateInstallLocations` function in a separate thread
*/
class Processing: SuspendingCliktCommand("processing"){
val version by option("-v","--version")
.flag()
@@ -61,7 +72,10 @@ class Processing: SuspendingCliktCommand("processing"){
}
}
/**
* A command to start the Processing Language Server
* This is used by IDEs to provide language support for Processing sketches
*/
class LSP: SuspendingCliktCommand("lsp"){
override fun help(context: Context) = "Start the Processing Language Server"
override suspend fun run(){
@@ -79,6 +93,11 @@ class LSP: SuspendingCliktCommand("lsp"){
}
}
/**
* A command to invoke the legacy CLI of Processing
* This is mainly for backwards compatibility with existing scripts
* that use the old CLI interface
*/
class LegacyCLI(val args: Array<String>): SuspendingCliktCommand("cli") {
override val treatUnknownOptionsAsArgs = true
@@ -99,6 +118,16 @@ class LegacyCLI(val args: Array<String>): SuspendingCliktCommand("cli") {
}
}
/**
* Update the install locations in preferences
* The install locations are stored in the preferences as a comma separated list of paths
* Each path is followed by a caret (^) and the version of Processing at that location
* This is used by other programs to find all installed versions of Processing
* works from 4.4.6 onwards
*
* Example:
* /path/to/processing-4.0^4.0,/path/to/processing-3.5.4^3.5.4
*/
fun updateInstallLocations(){
val preferences = Preferences.userRoot().node("org/processing/app")
val installLocations = preferences.get("installLocations", "")
+3
View File
@@ -63,6 +63,9 @@ public class UpdateCheck {
static private final long ONE_DAY = 24 * 60 * 60 * 1000;
public static void doCheck(Base base) {
new UpdateCheck(base);
}
public UpdateCheck(Base base) {
this.base = base;
@@ -694,15 +694,9 @@ public class ContributionManager {
static public void init(Base base) throws Exception {
// long t1 = System.currentTimeMillis();
// Moved here to make sure it runs on EDT [jv 170121]
contribListing = ContributionListing.getInstance();
// long t2 = System.currentTimeMillis();
managerFrame = new ManagerFrame(base);
// long t3 = System.currentTimeMillis();
cleanup(base);
// long t4 = System.currentTimeMillis();
// System.out.println("ContributionManager.init() " + (t2-t1) + " " + (t3-t2) + " " + (t4-t3));
}
@@ -61,27 +61,15 @@ public class ManagerFrame {
public ManagerFrame(Base base) {
this.base = base;
// TODO Optimize these inits... unfortunately it needs to run on the EDT,
// and Swing is a piece of s*t, so it's gonna be slow with lots of contribs.
// In particular, load everything and then fire the update events.
// Also, don't pull all the colors over and over again.
// long t1 = System.currentTimeMillis();
librariesTab = new ContributionTab(this, ContributionType.LIBRARY);
// long t2 = System.currentTimeMillis();
modesTab = new ContributionTab(this, ContributionType.MODE);
// long t3 = System.currentTimeMillis();
toolsTab = new ContributionTab(this, ContributionType.TOOL);
// long t4 = System.currentTimeMillis();
examplesTab = new ContributionTab(this, ContributionType.EXAMPLES);
// long t5 = System.currentTimeMillis();
updatesTab = new UpdateContributionTab(this);
// long t6 = System.currentTimeMillis();
tabList = new ContributionTab[] {
librariesTab, modesTab, toolsTab, examplesTab, updatesTab
};
// System.out.println("ManagerFrame.<init> " + (t2-t1) + " " + (t3-t2) + " " + (t4-t3) + " " + (t5-t4) + " " + (t6-t5));
}
@@ -110,8 +110,6 @@ public class JavaEditor extends Editor {
Mode mode) throws EditorException {
super(base, path, state, mode);
// long t1 = System.currentTimeMillis();
jmode = (JavaMode) mode;
debugger = new Debugger(this);
@@ -127,8 +125,6 @@ public class JavaEditor extends Editor {
preprocService = new PreprocService(this.jmode, this.sketch);
// long t5 = System.currentTimeMillis();
usage = new ShowUsage(this, preprocService);
inspect = new InspectMode(this, preprocService, usage);
rename = new Rename(this, preprocService, usage);
@@ -139,16 +135,12 @@ public class JavaEditor extends Editor {
errorChecker = new ErrorChecker(this::setProblemList, preprocService);
// long t7 = System.currentTimeMillis();
for (SketchCode code : getSketch().getCode()) {
Document document = code.getDocument();
addDocumentListener(document);
}
sketchChanged();
// long t9 = System.currentTimeMillis();
Toolkit.setMenuMnemonics(textarea.getRightClickPopup());
// ensure completion is hidden when editor loses focus
@@ -159,9 +151,6 @@ public class JavaEditor extends Editor {
public void windowGainedFocus(WindowEvent e) { }
});
// long t10 = System.currentTimeMillis();
// System.out.println("java editor was " + (t10-t9) + " " + (t9-t7) + " " + (t7-t5) + " " + (t5-t1));
}