diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 2551a54d64..41370918ba 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -51,11 +51,15 @@ * 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); + run(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); - } + /** + * 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); } - }); + 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. -// 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 - - 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]; - } - } - } + checkVersion(); - // 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 @@ static private void createAndShowGUI(String[] args) { // 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,56 +207,26 @@ static private void createAndShowGUI(String[] args) { 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 { - // 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. - // https://github.com/processing/processing4/issues/549 - // The UUID also prevents collisions when restarting the - // software. Otherwise, after using up the a-z naming options - // it was not possible for users to restart (without manually - // finding and deleting the TEMP files). - // https://github.com/processing/processing4/issues/582 - String uuid = UUID.randomUUID().toString(); - untitledFolder = new File(Util.getProcessingTemp(), uuid); - - } 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 { + Messages.log("About to create Base..."); + 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); @@ -308,7 +235,7 @@ static private void createAndShowGUI(String[] args) { handleCrustyDisplay(); handleTempCleaning(); - } catch (Throwable t) { + } catch (Throwable t) { // Catch-all to pick up badness during startup. Throwable err = t; if (t.getCause() != null) { @@ -317,24 +244,51 @@ static private void createAndShowGUI(String[] args) { 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$ + "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. + // https://github.com/processing/processing4/issues/549 + // The UUID also prevents collisions when restarting the + // software. Otherwise, after using up the a-z naming options + // it was not possible for users to restart (without manually + // finding and deleting the TEMP files). + // https://github.com/processing/processing4/issues/582 + String uuid = UUID.randomUUID().toString(); + untitledFolder = new File(Util.getProcessingTemp(), uuid); -// long t10 = System.currentTimeMillis(); -// System.out.println("startup took " + (t2-t1) + " " + (t3-t2) + " " + (t4-t3) + " " + (t5-t4) + " " + (t6-t5) + " " + (t10-t6) + " ms"); + } 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); } } + 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 @@ static public void cleanTempFolders() { } } + /** + * 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 @@ static public boolean isCommandLine() { 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 Base(String[] args) throws Exception { // 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 Base(String[] args) throws Exception { } } - 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 Base(String[] args) throws Exception { } 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$ + } + } } diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt index 6bc6b64a7e..08ad763775 100644 --- a/app/src/processing/app/Processing.kt +++ b/app/src/processing/app/Processing.kt @@ -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){ Processing() .subcommands( @@ -32,6 +39,10 @@ suspend fun main(args: Array){ .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): SuspendingCliktCommand("cli") { override val treatUnknownOptionsAsArgs = true @@ -99,6 +118,16 @@ class LegacyCLI(val args: Array): 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", "") diff --git a/app/src/processing/app/UpdateCheck.java b/app/src/processing/app/UpdateCheck.java index e18daee3eb..20c91dd38c 100644 --- a/app/src/processing/app/UpdateCheck.java +++ b/app/src/processing/app/UpdateCheck.java @@ -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; diff --git a/app/src/processing/app/contrib/ContributionManager.java b/app/src/processing/app/contrib/ContributionManager.java index c4d45f7d7d..79a7b54eb3 100644 --- a/app/src/processing/app/contrib/ContributionManager.java +++ b/app/src/processing/app/contrib/ContributionManager.java @@ -694,15 +694,9 @@ static private void clearRestartFlags(File root) { 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)); } diff --git a/app/src/processing/app/contrib/ManagerFrame.java b/app/src/processing/app/contrib/ManagerFrame.java index ab68fd1db5..bc15a439e8 100644 --- a/app/src/processing/app/contrib/ManagerFrame.java +++ b/app/src/processing/app/contrib/ManagerFrame.java @@ -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. " + (t2-t1) + " " + (t3-t2) + " " + (t4-t3) + " " + (t5-t4) + " " + (t6-t5)); } diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java index 3fab2c8b17..8e4d023b9c 100644 --- a/java/src/processing/mode/java/JavaEditor.java +++ b/java/src/processing/mode/java/JavaEditor.java @@ -110,8 +110,6 @@ protected JavaEditor(Base base, String path, EditorState state, Mode mode) throws EditorException { super(base, path, state, mode); -// long t1 = System.currentTimeMillis(); - jmode = (JavaMode) mode; debugger = new Debugger(this); @@ -127,8 +125,6 @@ protected JavaEditor(Base base, String path, EditorState state, 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 @@ protected JavaEditor(Base base, String path, EditorState state, 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 void windowLostFocus(WindowEvent e) { public void windowGainedFocus(WindowEvent e) { } }); - -// long t10 = System.currentTimeMillis(); -// System.out.println("java editor was " + (t10-t9) + " " + (t9-t7) + " " + (t7-t5) + " " + (t5-t1)); }