diff --git a/ISSUE_TEMPLATE.md b/.github/issue_template.md similarity index 100% rename from ISSUE_TEMPLATE.md rename to .github/issue_template.md diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index 14e31af72..000000000 --- a/.github/lock.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Configuration for Lock Threads - https://github.com/dessant/lock-threads-app - -# Number of days of inactivity before a closed issue or pull request is locked -daysUntilLock: 30 - -# Skip issues and pull requests created before a given timestamp. Timestamp must -# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable -skipCreatedBefore: false - -# Issues and pull requests with these labels will be ignored. Set to `[]` to disable -exemptLabels: [] - -# Label to add before locking, such as `outdated`. Set to `false` to disable -lockLabel: false - -# Comment to post before locking. Set to `false` to disable -lockComment: > - This thread has been automatically locked since there has not been - any recent activity after it was closed. Please open a new issue for - related bugs. - -# Assign `resolved` as the reason for locking. Set to `false` to disable -# setLockReason: true -setLockReason: false - -# Limit to only `issues` or `pulls` -# only: issues - -# Optionally, specify configuration settings just for `issues` or `pulls` -# issues: -# exemptLabels: -# - help-wanted -# lockLabel: outdated - -# pulls: -# daysUntilLock: 30 - -# Repository to extend settings from -# _extends: repo diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml new file mode 100644 index 000000000..f46041e75 --- /dev/null +++ b/.github/workflows/lock.yml @@ -0,0 +1,22 @@ +name: 'Lock Threads' + +on: + schedule: + - cron: '0 * * * *' + +jobs: + lock: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v2.0.1 + with: + github-token: ${{ github.token }} + issue-lock-inactive-days: '30' + issue-lock-comment: > + This issue has been automatically locked since there + has not been any recent activity after it was closed. + Please open a new issue for related bugs. + pr-lock-comment: > + This pull request has been automatically locked since there + has not been any recent activity after it was closed. + Please open a new issue for related bugs. diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000..895125f05 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,41 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 1acd3f53e..21d9512d3 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -9,12 +9,14 @@ + + \ No newline at end of file diff --git a/README.md b/README.md index 437863c39..5536a5a56 100644 --- a/README.md +++ b/README.md @@ -5,40 +5,59 @@ Processing 4 makes important updates to the code to prepare the platform for its ## Roadmap -We don't have a schedule for a final release. This work is being done by a [tiny number of volunteers](https://github.com/processing/processing4/graphs/contributors?from=2019-10-01&to=2021-06-14&type=c) working in their personal free time. +We don't have a schedule for a final release. This work is being done by a [tiny number of people](https://github.com/processing/processing4/graphs/contributors?from=2019-10-01&to=2021-12-31&type=c) who continue working on it, unpaid, because they care about it. * We're currently using JDK 11, which is a “Long Term Support” (LTS) release. Java 17 is the next LTS, and we'll switch to that when it arrives in September 2021. +* The current release runs well on Apple Silicon using Rosetta. We are currently unable to move to a fully native version for Apple Silicon because of other libraries that we rely upon (JavaFX, JOGL, etc). Once those are ready, we'll need to do additional work to add Apple Silicon as a target (the same way we support both 64-bit and 32-bit, or ARM instead of Intel.) If there are updates on this issue, you'll find them [here](https://github.com/processing/processing4/issues/128). -## API Changes + +## API and Internal Changes As with all releases, we'll do everything possible to avoid breaking API. However, there will still be tweaks that have to be made. We'll try to keep them minor. Our goal is stability, and keeping everyone's code running. +The full list of changes can be seen in the release notes for each version, this is a list of things that may break existing projects (sketches, libraries, etc.) -### alpha 2 -* See `changes.md` if you're using `surface.setResizable()` with this release on macOS and with P2D or P3D renderers. -* The `static` versions of `selectInput()`, `selectOutput()`, and `selectFolder()` in `PApplet` have been removed. These were not documented, hopefully were not in use anywhere. -* The `frame` object has been removed from `PApplet`. We've been warning folks to use `surface` since 2015, but we still should [warn users](https://github.com/processing/processing4/issues/59) +### Alpha 5 + +* Known bug: code completion is currently broken. Any updates will be posted [here](https://github.com/processing/processing4/issues/177). + +* Moved from the 11.0.2 LTS version of JavaFX to the in-progress version 16. This fixes a [garbled text](https://bugs.openjdk.java.net/browse/JDK-8234916) issue that was breaking Tools that used JavaFX. + +* The minimum system version for macOS (for the PDE and exported applications) is now set to 10.14.6 (the last update of Mojave). 10.13 (High Sierra) is no longer supported by Apple as of September or December 2020 (depending on what you read), and for our sanity, we're dropping it as well. + +* JavaFX has been moved out of core and into a separate library. After Java 8, it's no longer part of the JDK, so it requires additional files that were formerly included by default. The Processing version of that library comes in at 180 MB, which seems excessive to include with every project, regardless of whether it's used. (And that's not including the full Webkit implementation, which adds \~80 MB per platform.) + + +### Alpha 4 + +* `EditorState(List editors)` changed to `EditorState.nextEditor(List editors)`, reflecting its nature as closer to a factory method (that makes use of the Editor list) than a constructor that will also be storing information about the list of Editor objects in the created object. + + +### Alpha 2 + +* ~~The minimum system version for macOS (for the PDE and exported applications) is now set to 10.13.6 (the last update of High Sierra). Apple will likely be dropping support for High Sierra in late 2020, so we may make the minimum 10.14.x by the time 4.x ships.~~ + +* ~~See `changes.md` if you're using `surface.setResizable()` with this release on macOS and with P2D or P3D renderers.~~ + +* The `static` versions of `selectInput()`, `selectOutput()`, and `selectFolder()` in `PApplet` have been removed. These were not documented, hopefully they were not in use anyway. + +* The `frame` object has been removed from `PApplet`. We've been warning folks to use `surface` since 2015, but maybe we can provide an [easy way](https://github.com/processing/processing4/issues/59) to update code from inside the PDE. + * `PImage.checkAlpha()` is now `public` instead of `protected` + * All AWT calls have been moved out of `PImage`, which may be a problem for anything that was relying on those internals - * For instance, `new PImage(java.awt.Image)` is no longer available. It was an undocumented method that was `public` only because it was required by subclasses. + * ~~For instance, `new PImage(java.awt.Image)` is no longer available. It was an undocumented method that was `public` only because it was required by subclasses.~~ As of alpha 4, this is back, because it wasn't deprecated in 3.x, and is likely to break too many things. + * Removed `MouseEvent.getClickCount()` and `MouseEvent.getAmount()`. These had been deprecated, not clear they were used anywhere. -### alpha 1 + +### Alpha 1 * `Base.defaultFileMenu` is now `protected` instead of `static public` - -## Other Changes - -### alpha 2 - -* The minimum system version for macOS (for the PDE and exported applications) is now set to 10.13.6 (the last update of High Sierra). Apple will likely be dropping support for High Sierra in late 2020, so we may make the minimum 10.14.x by the time 4.x ships. - -### alpha 1 - -* Processing 4 will be 64-bit only. This is the overwhelming majority of users, and we don't have the necessary help to maintain and support 32-bit systems. +* Processing 4 is 64-bit only. This is the overwhelming majority of users, and we don't have the necessary help to maintain and support 32-bit systems. ## Translation Updates diff --git a/app/.classpath b/app/.classpath index df37d3bba..3fb1a2481 100644 --- a/app/.classpath +++ b/app/.classpath @@ -1,7 +1,7 @@ - + @@ -11,7 +11,6 @@ - diff --git a/app/.gitignore b/app/.gitignore index 3456289ed..81e75864d 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,5 +1,2 @@ bin pde.jar - -jna.jar -jna-platform.jar diff --git a/app/build.xml b/app/build.xml index d73bb513d..d667aa2c8 100644 --- a/app/build.xml +++ b/app/build.xml @@ -1,31 +1,84 @@ - + - - - - + + + - + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + @@ -49,21 +102,17 @@ ignoreerrors="${jna.ignorable}" usetimestamp="true" /> - + - - - - + + + - - + + @@ -71,7 +120,8 @@ - + @@ -87,27 +137,28 @@ + https://github.com/processing/processing/issues/1792 --> + lib/VAqua9.jar" + debug="on" + nowarn="true"> + @@ -115,4 +166,23 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/lib/.gitignore b/app/lib/.gitignore index 44412b0d9..25b443b60 100644 --- a/app/lib/.gitignore +++ b/app/lib/.gitignore @@ -1 +1,7 @@ -VAqua*.jar +ant.jar +ant-launcher.jar + +jna.jar +jna-platform.jar + +VAqua*.jar \ No newline at end of file diff --git a/app/lib/ant-launcher.jar b/app/lib/ant-launcher.jar deleted file mode 100644 index 939abb579..000000000 Binary files a/app/lib/ant-launcher.jar and /dev/null differ diff --git a/app/lib/ant.jar b/app/lib/ant.jar deleted file mode 100644 index 7f5be4a4e..000000000 Binary files a/app/lib/ant.jar and /dev/null differ diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 9b7a663ec..d968dcc15 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-20 The Processing Foundation + Copyright (c) 2012-21 The Processing Foundation Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology @@ -25,7 +25,6 @@ package processing.app; import java.awt.EventQueue; import java.awt.FileDialog; -import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; import java.lang.reflect.InvocationTargetException; @@ -56,10 +55,9 @@ import processing.data.StringList; 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. - static private final int REVISION = 1273; + static private final int REVISION = 1275; /** This might be replaced by main() if there's a lib/version.txt file. */ - static private String VERSION_NAME = "1273"; //$NON-NLS-1$ - /** Set true if this a proper release rather than a numbered revision. */ + static private String VERSION_NAME = "1275"; //$NON-NLS-1$ /** * True if heavy debugging error/log messages are enabled. Set to true @@ -80,8 +78,8 @@ public class Base { static File untitledFolder; /** List of currently active editors. */ - protected List editors = - Collections.synchronizedList(new ArrayList()); + final protected List editors = + Collections.synchronizedList(new ArrayList<>()); protected Editor activeEditor; /** A lone file menu to be used when all sketch windows are closed. */ protected JMenu defaultFileMenu; @@ -98,10 +96,6 @@ public class Base { protected List modeContribs; protected List exampleContribs; - private JMenu sketchbookMenu; - -// private Recent recent; - // Used by handleOpen(), this saves the chooser to remember the directory. // Doesn't appear to be necessary with the AWT native file dialog. // https://github.com/processing/processing/pull/2366 @@ -112,53 +106,52 @@ public class Base { static public void main(final String[] args) { - EventQueue.invokeLater(new Runnable() { - public void run() { - try { - createAndShowGUI(args); + 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")) { - missing = "jnidispatch.dll"; - } else if (mess.contains("NoClassDefFoundError: processing/core/PApplet")) { - 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" + - "hasn't removed files from the Processing folder.\n\n" + - "(It sometimes flags parts of Processing as a trojan or virus.\n" + - "It is neither, 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/processing/issues/new", t, true); + } 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")) { + 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/processing/issues/new", t, true); + } }); } static private void createAndShowGUI(String[] args) { - try { - File versionFile = Platform.getContentFile("lib/version.txt"); - if (versionFile.exists()) { - String version = PApplet.loadStrings(versionFile)[0]; - if (!version.equals(VERSION_NAME)) { - VERSION_NAME = version; + 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]; } } - } catch (Exception e) { - e.printStackTrace(); } Platform.init(); @@ -166,7 +159,7 @@ public class Base { Console.startup(); // Set the debug flag based on a file being present in the settings folder - File debugFile = getSettingsFile("debug.txt"); + File debugFile = getSettingsFile("debug"); /* if (debugFile.isDirectory()) { // if it's a directory, it's a leftover from older releases, clear it @@ -278,40 +271,30 @@ public class Base { // https://github.com/processing/processing/issues/4997 static private void checkDriverBug() { if (System.getProperty("os.name").contains("Windows 10")) { - new Thread(new Runnable() { - public void run() { - try { - Process p = Runtime.getRuntime().exec("powershell Get-WmiObject Win32_PnPSignedDriver| select devicename, driverversion | where {$_.devicename -like \\\"*nvidia*\\\"}"); - BufferedReader reader = PApplet.createReader(p.getInputStream()); - String line = null; - while ((line = reader.readLine()) != null) { - if (line.contains("3.7849")) { - EventQueue.invokeLater(new Runnable() { - public void run() { - Messages.showWarning("NVIDIA screwed up", - "Due to an NVIDIA bug, you need to update your graphics drivers,\n" + - "otherwise you won't be able to run any sketches. Update here:\n" + - "http://nvidia.custhelp.com/app/answers/detail/a_id/4378\n" + - "or read background about the issue at this link:\n" + - "https://github.com/processing/processing/issues/4853"); - } - }); - } else if (line.contains("3.8165")) { - EventQueue.invokeLater(new Runnable() { - public void run() { - Messages.showWarning("NVIDIA screwed up again", - "Due to an NVIDIA bug, you need to update your graphics drivers,\n" + - "otherwise you won't be able to run any sketches. Update here:\n" + - "http://nvidia.custhelp.com/app/answers/detail/a_id/4453/\n" + - "or read background about the issue at this link:\n" + - "https://github.com/processing/processing/issues/4997"); - } - }); - } + new Thread(() -> { + try { + Process p = Runtime.getRuntime().exec("powershell Get-WmiObject Win32_PnPSignedDriver| select devicename, driverversion | where {$_.devicename -like \\\"*nvidia*\\\"}"); + BufferedReader reader = PApplet.createReader(p.getInputStream()); + String line; + while ((line = reader.readLine()) != null) { + if (line.contains("3.7849")) { + EventQueue.invokeLater(() -> Messages.showWarning("NVIDIA screwed up", + "Due to an NVIDIA bug, you need to update your graphics drivers,\n" + + "otherwise you won't be able to run any sketches. Update here:\n" + + "http://nvidia.custhelp.com/app/answers/detail/a_id/4378\n" + + "or read background about the issue at this link:\n" + + "https://github.com/processing/processing/issues/4853")); + } else if (line.contains("3.8165")) { + EventQueue.invokeLater(() -> Messages.showWarning("NVIDIA screwed up again", + "Due to an NVIDIA bug, you need to update your graphics drivers,\n" + + "otherwise you won't be able to run any sketches. Update here:\n" + + "http://nvidia.custhelp.com/app/answers/detail/a_id/4453/\n" + + "or read background about the issue at this link:\n" + + "https://github.com/processing/processing/issues/4997")); } - } catch (Exception e) { - Messages.loge("Problem checking NVIDIA driver", e); } + } catch (Exception e) { + Messages.loge("Problem checking NVIDIA driver", e); } }).start(); } @@ -390,8 +373,6 @@ public class Base { // menu works on Mac OS X (since it needs examplesFolder to be set). Platform.initBase(this); -// toolsFolder = getContentFile("tools"); - // // Check if there were previously opened sketches to be restored // boolean opened = restoreSketches(); boolean opened = false; @@ -445,36 +426,19 @@ public class Base { defaultFileMenu = new JMenu(Language.text("menu.file")); JMenuItem item = Toolkit.newJMenuItem(Language.text("menu.file.new"), 'N'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleNew(); - } - }); + item.addActionListener(e -> handleNew()); defaultFileMenu.add(item); item = Toolkit.newJMenuItem(Language.text("menu.file.open"), 'O'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleOpenPrompt(); - } - }); + item.addActionListener(e -> handleOpenPrompt()); defaultFileMenu.add(item); item = Toolkit.newJMenuItemShift(Language.text("menu.file.sketchbook"), 'K'); - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - getNextMode().showSketchbookFrame(); - } - }); + item.addActionListener(e -> getNextMode().showSketchbookFrame()); defaultFileMenu.add(item); item = Toolkit.newJMenuItemShift(Language.text("menu.file.examples"), 'O'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - thinkDifferentExamples(); - } - }); + item.addActionListener(e -> thinkDifferentExamples()); defaultFileMenu.add(item); return defaultFileMenu; @@ -482,22 +446,18 @@ public class Base { void buildCoreModes() { - Mode javaMode = + ModeContribution javaModeContrib = ModeContribution.load(this, Platform.getContentFile("modes/java"), - getDefaultModeIdentifier()).getMode(); + getDefaultModeIdentifier()); + if (javaModeContrib == null) { + Messages.showError("Startup Error", + "Could not load Java Mode, please reinstall Processing.", + new Exception("ModeContribution.load() was null")); - // PDE X calls getModeList() while it's loading, so coreModes must be set - coreModes = new Mode[] { javaMode }; - - /* - Mode pdexMode = - ModeContribution.load(this, getContentFile("modes/ExperimentalMode"), //$NON-NLS-1$ - "processing.mode.experimental.ExperimentalMode").getMode(); //$NON-NLS-1$ - - // Safe to remove the old Java mode here? - //coreModes = new Mode[] { pdexMode }; - coreModes = new Mode[] { pdexMode, javaMode }; - */ + } else { + // PDE X calls getModeList() while it's loading, so coreModes must be set + coreModes = new Mode[] { javaModeContrib.getMode() }; + } } @@ -525,12 +485,9 @@ public class Base { if (!known.containsKey(folder)) { try { contribModes.add(new ModeContribution(this, folder, null)); - } catch (NoSuchMethodError nsme) { + } catch (NoSuchMethodError | NoClassDefFoundError ne) { System.err.println(folder.getName() + " is not compatible with this version of Processing"); - if (DEBUG) nsme.printStackTrace(); - } catch (NoClassDefFoundError ncdfe) { - System.err.println(folder.getName() + " is not compatible with this version of Processing"); - if (DEBUG) ncdfe.printStackTrace(); + if (DEBUG) ne.printStackTrace(); } catch (InvocationTargetException ite) { System.err.println(folder.getName() + " could not be loaded and may not compatible with this version of Processing"); if (DEBUG) ite.printStackTrace(); @@ -706,9 +663,11 @@ public class Base { } + /* public void removeToolContrib(ToolContribution tc) { contribTools.remove(tc); } + */ public void rebuildToolList() { @@ -783,25 +742,6 @@ public class Base { } - /* - Iterator editorIter = base.getEditors().iterator(); - while (editorIter.hasNext()) { - Editor editor = editorIter.next(); - List contribTools = editor.getToolContribs(); - for (ToolContribution toolContrib : contribTools) { - if (toolContrib.getName().equals(this.name)) { - try { - ((URLClassLoader) toolContrib.loader).close(); - editor.removeToolContrib(toolContrib); - break; - } catch (IOException e) { - e.printStackTrace(); - } - } - } - */ - - public void clearToolMenus() { for (Editor ed : editors) { ed.clearToolMenu(); @@ -814,17 +754,7 @@ public class Base { if (internalTools == null) { rebuildToolList(); } -// coreTools = ToolContribution.loadAll(Base.getToolsFolder()); -// contribTools = ToolContribution.loadAll(Base.getSketchbookToolsFolder()); -// Collections.sort(coreTools); -// Collections.sort(contribTools); -// Collections.sort(coreTools, new Comparator() { -// @Override -// public int compare(ToolContribution o1, ToolContribution o2) { -// return o1.getMenuTitle().compareTo(o2.getMenuTitle()); -// } -// }); toolsMenu.removeAll(); for (Tool tool : internalTools) { toolsMenu.add(createToolItem(tool)); @@ -846,11 +776,7 @@ public class Base { } JMenuItem item = new JMenuItem(Language.text("menu.tools.add_tool")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - ContributionManager.openTools(); - } - }); + item.addActionListener(e -> ContributionManager.openTools()); toolsMenu.add(item); } @@ -881,24 +807,21 @@ public class Base { JMenuItem createToolItem(final Tool tool) { //, Map toolItems) { String title = tool.getMenuTitle(); final JMenuItem item = new JMenuItem(title); - item.addActionListener(new ActionListener() { + item.addActionListener(e -> { + try { + tool.run(); - public void actionPerformed(ActionEvent e) { - try { - tool.run(); + } catch (NoSuchMethodError | NoClassDefFoundError ne) { + Messages.showWarning("Tool out of date", + tool.getMenuTitle() + " is not compatible with this version of Processing.\n" + + "Try updating the Mode or contact its author for a new version.", ne); + Messages.loge("Incompatible tool found during tool.run()", ne); + item.setEnabled(false); - } catch (NoSuchMethodError | java.lang.NoClassDefFoundError ne) { - Messages.showWarning("Tool out of date", - tool.getMenuTitle() + " is not compatible with this version of Processing.\n" + - "Try updating the Mode or contact its author for a new version.", ne); - Messages.loge("Incompatible tool found during tool.run()", ne); - item.setEnabled(false); - - } catch (Exception ex) { - activeEditor.statusError("An error occurred inside \"" + tool.getMenuTitle() + "\""); - ex.printStackTrace(); - item.setEnabled(false); - } + } catch (Exception ex) { + activeEditor.statusError("An error occurred inside \"" + tool.getMenuTitle() + "\""); + ex.printStackTrace(); + item.setEnabled(false); } }); //toolItems.put(title, item); @@ -915,8 +838,7 @@ public class Base { public List getModeList() { - List allModes = new ArrayList<>(); - allModes.addAll(Arrays.asList(coreModes)); + List allModes = new ArrayList<>(Arrays.asList(coreModes)); if (modeContribs != null) { for (ModeContribution contrib : modeContribs) { allModes.add(contrib.getMode()); @@ -932,10 +854,8 @@ public class Base { private List getInstalledContribs() { - List contributions = new ArrayList<>(); - List modeContribs = getModeContribs(); - contributions.addAll(modeContribs); + List contributions = new ArrayList<>(modeContribs); for (ModeContribution modeContrib : modeContribs) { Mode mode = modeContrib.getMode(); @@ -1020,9 +940,7 @@ public class Base { /** - * The call has already checked to make sure this sketch is not modified, - * now change the mode. - * @return true if mode is changed. + * @return true if mode is changed within this window (false if new window) */ public boolean changeMode(Mode mode) { Mode oldMode = activeEditor.getMode(); @@ -1030,25 +948,22 @@ public class Base { Sketch sketch = activeEditor.getSketch(); nextMode = mode; - if (sketch.isUntitled()) { + if (sketch.isModified()) { + handleNew(); // don't bother with error messages, just switch + return false; + + } else if (sketch.isUntitled()) { // The current sketch is empty, just close and start fresh. // (Otherwise the editor would lose its 'untitled' status.) handleClose(activeEditor, true); handleNew(); } else { - // If the current editor contains file extensions that the new mode can handle, then - // write a sketch.properties file with the new mode specified, and reopen. - boolean newModeCanHandleCurrentSource = true; - for (final SketchCode code : sketch.getCode()) { - if (!mode.validExtension(code.getExtension())) { - newModeCanHandleCurrentSource = false; - break; - } - } - if (!newModeCanHandleCurrentSource) { - return false; - } else { + // If the current sketch contains file extensions that the new mode + // can handle, then write a sketch.properties file with the new mode + // specified, and reopen. (Really only useful for Java <-> Android) + //if (isCompatible(sketch, mode)) { + if (mode.canEdit(sketch)) { final File props = new File(sketch.getCodeFolder(), "sketch.properties"); saveModeSettings(props, nextMode); handleClose(activeEditor, true); @@ -1060,11 +975,27 @@ public class Base { handleOpen(sketch.getMainFilePath()); return false; } + } else { + handleNew(); // create a new window with the new Mode + return false; } } } + // Against all (or at least most) odds, we were able to reassign the Mode + return true; + } + + + /* + private boolean isCompatible(Sketch sketch, Mode mode) { + for (final SketchCode code : sketch.getCode()) { + if (!mode.validExtension(code.getExtension())) { + return false; + } + } return true; } + */ private static class ModeInfo { @@ -1128,7 +1059,7 @@ public class Base { } return null; } - final Mode[] modes = possibleModes.toArray(new Mode[possibleModes.size()]); + final Mode[] modes = possibleModes.toArray(new Mode[0]); final String message = preferredMode == null ? (nextMode.getTitle() + " Mode can't open ." + extension + " files, " + "but you have one or more modes\ninstalled that can. " + @@ -1178,8 +1109,8 @@ public class Base { */ public void handleNew() { try { - File newbieDir = null; - String newbieName = null; + File newbieDir; + String newbieName; // In 0126, untitled sketches will begin in the temp folder, // and then moved to a new location because Save will default to Save As. @@ -1191,7 +1122,7 @@ public class Base { // Use a generic name like sketch_031008a, the date plus a char int index = 0; String format = Preferences.get("editor.untitled.suffix"); - String suffix = null; + String suffix; if (format == null) { Calendar cal = Calendar.getInstance(); int day = cal.get(Calendar.DAY_OF_MONTH); // 1..31 @@ -1221,7 +1152,7 @@ public class Base { newbieName = prefix + suffix + ((char) ('a' + index)); // Also sanitize the name since it might do strange things on // non-English systems that don't use this sort of date format. - // http://code.google.com/p/processing/issues/detail?id=283 + // https://github.com/processing/processing/issues/322 newbieName = Sketch.sanitizeName(newbieName); newbieDir = new File(newbieParentDir, newbieName); index++; @@ -1278,16 +1209,14 @@ public class Base { new FileDialog(activeEditor, prompt, FileDialog.LOAD); // Only show .pde files as eligible bachelors - openDialog.setFilenameFilter(new FilenameFilter() { - public boolean accept(File dir, String name) { - // confirmed to be working properly [fry 110128] - for (String ext : extensions) { - if (name.toLowerCase().endsWith("." + ext)) { //$NON-NLS-1$ - return true; - } + openDialog.setFilenameFilter((dir, name) -> { + // confirmed to be working properly [fry 110128] + for (String ext : extensions) { + if (name.toLowerCase().endsWith("." + ext)) { //$NON-NLS-1$ + return true; } - return false; } + return false; }); openDialog.setVisible(true); @@ -1347,7 +1276,7 @@ public class Base { * can be set by the caller */ public Editor handleOpen(String path, boolean untitled) { - return handleOpen(path, untitled, new EditorState(editors)); + return handleOpen(path, untitled, EditorState.nextEditor(editors)); } @@ -1634,17 +1563,13 @@ public class Base { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - /** - * Asynchronous version of menu rebuild to be used on save and rename - * to prevent the interface from locking up until the menus are done. - */ - protected void rebuildSketchbookMenusAsync() { - EventQueue.invokeLater(new Runnable() { - public void run() { - rebuildSketchbookMenus(); - } - }); - } +// /** +// * Asynchronous version of menu rebuild to be used on save and rename +// * to prevent the interface from locking up until the menus are done. +// */ +// protected void rebuildSketchbookMenusAsync() { +// EventQueue.invokeLater(this::rebuildSketchbookMenus); +// } public void thinkDifferentExamples() { @@ -1667,17 +1592,11 @@ public class Base { } - protected void rebuildSketchbookMenu() { - sketchbookMenu.removeAll(); - populateSketchbookMenu(sketchbookMenu); - } - - public void populateSketchbookMenu(JMenu menu) { boolean found = false; try { - found = addSketches(menu, sketchbookFolder, false); - } catch (IOException e) { + found = addSketches(menu, sketchbookFolder); + } catch (Exception e) { Messages.showWarning("Sketchbook Menu Error", "An error occurred while trying to list the sketchbook.", e); } @@ -1724,8 +1643,7 @@ public class Base { * should replace the sketch in the current window, or false when the * sketch should open in a new window. */ - protected boolean addSketches(JMenu menu, File folder, - final boolean replaceExisting) throws IOException { + protected boolean addSketches(JMenu menu, File folder) { // skip .DS_Store files, etc (this shouldn't actually be necessary) if (!folder.isDirectory()) { return false; @@ -1753,27 +1671,23 @@ public class Base { // Alphabetize the list, since it's not always alpha order Arrays.sort(list, String.CASE_INSENSITIVE_ORDER); - ActionListener listener = new ActionListener() { - public void actionPerformed(ActionEvent e) { - String path = e.getActionCommand(); - if (new File(path).exists()) { - boolean replace = replaceExisting; - if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0) { - replace = !replace; - } -// if (replace) { -// handleOpenReplace(path); -// } else { - handleOpen(path); -// } - } else { - Messages.showWarning("Sketch Disappeared", - "The selected sketch no longer exists.\n" + - "You may need to restart Processing to update\n" + - "the sketchbook menu.", null); - } + ActionListener listener = e -> { + String path = e.getActionCommand(); + if (new File(path).exists()) { + /* + boolean replace = replaceExisting; + if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0) { + replace = !replace; } - }; + */ + handleOpen(path); + } else { + Messages.showWarning("Sketch Disappeared", + "The selected sketch no longer exists.\n" + + "You may need to restart Processing to update\n" + + "the sketchbook menu.", null); + } + }; // offers no speed improvement //menu.addActionListener(listener); @@ -1802,7 +1716,7 @@ public class Base { // not a sketch folder, but maybe a subfolder containing sketches JMenu submenu = new JMenu(name); // needs to be separate var otherwise would set ifound to false - boolean anything = addSketches(submenu, subfolder, replaceExisting); + boolean anything = addSketches(submenu, subfolder); if (anything && !name.equals("old")) { //Don't add old contributions menu.add(submenu); found = true; @@ -1921,6 +1835,7 @@ public class Base { /** * Return a File from inside the Processing 'lib' folder. */ + @SuppressWarnings("RedundantThrows") static public File getLibFile(String filename) throws IOException { return new File(Platform.getContentFile("lib"), filename); } @@ -2063,25 +1978,25 @@ public class Base { File sketchbookFolder = null; try { sketchbookFolder = Platform.getDefaultSketchbookFolder(); - } catch (Exception e) { } + } catch (Exception ignored) { } if (sketchbookFolder == null) { Messages.showError("No sketchbook", "Problem while trying to get the sketchbook", null); - } - // create the folder if it doesn't exist already - boolean result = true; - if (!sketchbookFolder.exists()) { - result = sketchbookFolder.mkdirs(); - } + } else { + // create the folder if it doesn't exist already + boolean result = true; + if (!sketchbookFolder.exists()) { + result = sketchbookFolder.mkdirs(); + } - if (!result) { - Messages.showError("You forgot your sketchbook", - "Processing cannot run because it could not\n" + - "create a folder to store your sketchbook.", null); + if (!result) { + Messages.showError("You forgot your sketchbook", + "Processing cannot run because it could not\n" + + "create a folder to store your sketchbook.", null); + } } - return sketchbookFolder; } } diff --git a/app/src/processing/app/Library.java b/app/src/processing/app/Library.java index c77e4101d..24cd3c636 100644 --- a/app/src/processing/app/Library.java +++ b/app/src/processing/app/Library.java @@ -13,7 +13,6 @@ import processing.data.StringList; public class Library extends LocalContribution { static final String[] platformNames = PConstants.platformNames; - //protected File folder; // /path/to/shortname protected File libraryFolder; // shortname/library protected File examplesFolder; // shortname/examples protected File referenceFile; // shortname/reference/index.html @@ -29,7 +28,7 @@ public class Library extends LocalContribution { StringList packageList; /** Per-platform exports for this library. */ - HashMap exportList; + Map exportList; /** Applet exports (cross-platform by definition). */ String[] appletExportList; diff --git a/app/src/processing/app/Mode.java b/app/src/processing/app/Mode.java index 739caaee0..6afa17c18 100644 --- a/app/src/processing/app/Mode.java +++ b/app/src/processing/app/Mode.java @@ -949,6 +949,17 @@ public abstract class Mode { return validExtension(f.getName().substring(dot + 1)); } + + public boolean canEdit(Sketch sketch) { + for (final SketchCode code : sketch.getCode()) { + if (!validExtension(code.getExtension())) { + return false; + } + } + return true; + } + + /** * Check this extension (no dots, please) against the list of valid * extensions. diff --git a/app/src/processing/app/Platform.java b/app/src/processing/app/Platform.java index 51ac0bab6..d46a6d533 100644 --- a/app/src/processing/app/Platform.java +++ b/app/src/processing/app/Platform.java @@ -210,7 +210,10 @@ public class Platform { * Return the value of the os.arch property */ static public String getNativeArch() { - // This will return "arm" for 32-bit ARM, "aarch64" for 64-bit ARM (both on Linux) + // This will return "arm" for 32-bit ARM on Linux, + // and "aarch64" for 64-bit ARM on Linux (rpi) and Apple Silicon + // (the latter only when using a native 64-bit ARM VM on macOS, + // which as of 4.0 alpha 5 is not being used b/c of missing libs). return System.getProperty("os.arch"); } diff --git a/app/src/processing/app/Preferences.java b/app/src/processing/app/Preferences.java index d3f85a118..ead30e3d3 100644 --- a/app/src/processing/app/Preferences.java +++ b/app/src/processing/app/Preferences.java @@ -55,13 +55,13 @@ public class Preferences { static Map defaults; static Map table = new HashMap<>(); static File preferencesFile; - private static boolean initalized = false; + private static boolean initialized = false; // /** @return true if the sketchbook file did not exist */ // static public boolean init() { static public void init() { - initalized = true; + initialized = true; // start by loading the defaults, in case something // important was deleted from the user prefs try { @@ -133,7 +133,7 @@ public class Preferences { * For testing, pretend to load preferences without a real file. */ static public void skipInit() { - initalized = true; + initialized = true; } @@ -191,7 +191,7 @@ public class Preferences { /** * @param key original key (may include platform extension) - * @param value + * @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 */ @@ -205,7 +205,7 @@ public class Preferences { key = key.substring(0, key.lastIndexOf(ext)); // store this for later overrides specific.put(key, value); - } else { + //} else { // ignore platform-specific defaults for other platforms, // but return 'true' because it needn't be added to the big list } @@ -226,12 +226,14 @@ public class Preferences { try { File dir = preferencesFile.getParentFile(); File preferencesTemp = File.createTempFile("preferences", ".txt", dir); - preferencesTemp.setWritable(true, false); + 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[table.size()]); + 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) { @@ -270,9 +272,9 @@ public class Preferences { // all the information from preferences.txt static public String get(String attribute /*, String defaultValue */) { - if (!initalized) { + if (!initialized) { throw new RuntimeException( - "Tried reading preferences prior to initalization." + "Tried reading preferences prior to initialization." ); } return table.get(attribute); @@ -320,22 +322,6 @@ public class Preferences { static public int getInteger(String attribute /*, int defaultValue*/) { return Integer.parseInt(get(attribute)); - - /* - String value = get(attribute, null); - if (value == null) return defaultValue; - - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - // ignored will just fall through to returning the default - System.err.println("expecting an integer: " + attribute + " = " + value); - } - return defaultValue; - //if (value == null) return defaultValue; - //return (value == null) ? defaultValue : - //Integer.parseInt(value); - */ } @@ -350,7 +336,7 @@ public class Preferences { if ((s != null) && (s.indexOf("#") == 0)) { //$NON-NLS-1$ try { parsed = new Color(Integer.parseInt(s.substring(1), 16)); - } catch (Exception e) { } + } catch (Exception ignored) { } } return parsed; } @@ -361,57 +347,15 @@ public class Preferences { } - // Identical version found in Settings.java - static public Font getFont(String attr) { - try { - boolean replace = false; - String value = get(attr); - if (value == null) { - // use the default font instead - value = getDefault(attr); - replace = true; - } + static public Font getFont(String familyAttr, String sizeAttr, int style) { + int fontSize = getInteger(sizeAttr); - String[] pieces = PApplet.split(value, ','); - - if (pieces.length != 3) { - value = getDefault(attr); - pieces = PApplet.split(value, ','); - replace = true; - } - - String name = pieces[0]; - int style = Font.PLAIN; // equals zero - if (pieces[1].indexOf("bold") != -1) { //$NON-NLS-1$ - style |= Font.BOLD; - } - if (pieces[1].indexOf("italic") != -1) { //$NON-NLS-1$ - style |= Font.ITALIC; - } - int size = PApplet.parseInt(pieces[2], 12); - - // replace bad font with the default from lib/preferences.txt - if (replace) { - set(attr, value); - } - - if (!name.startsWith("processing.")) { - return new Font(name, style, size); - - } else { - if (pieces[0].equals("processing.sans")) { - return Toolkit.getSansFont(size, style); - } else if (pieces[0].equals("processing.mono")) { - return Toolkit.getMonoFont(size, style); - } - } - - } catch (Exception e) { - // Adding try/catch block because this may be where - // a lot of startup crashes are happening. - Messages.log("Error with font " + get(attr) + " for attribute " + attr); + String fontFamily = get(familyAttr); + if ("processing.mono".equals(fontFamily) || + Toolkit.getMonoFontName().equals(fontFamily)) { + return Toolkit.getMonoFont(fontSize, style); } - return new Font("Dialog", Font.PLAIN, 12); + return new Font(fontFamily, style, fontSize); } diff --git a/app/src/processing/app/Settings.java b/app/src/processing/app/Settings.java index efd016f23..ca4aacad1 100644 --- a/app/src/processing/app/Settings.java +++ b/app/src/processing/app/Settings.java @@ -46,10 +46,10 @@ public class Settings { * parses properly, we need to be able to get back to a clean version of that * setting so we can recover. */ - HashMap defaults; + Map defaults; /** Table of attributes/values. */ - HashMap table = new HashMap();; + Map table = new HashMap<>(); /** Associated file for this settings data. */ File file; @@ -63,7 +63,7 @@ public class Settings { } // clone the hash table - defaults = (HashMap) table.clone(); + defaults = new HashMap<>(table); } @@ -74,17 +74,21 @@ public class Settings { public void load(File additions) { String[] lines = PApplet.loadStrings(additions); - for (String line : lines) { - if ((line.length() == 0) || + if (lines != null) { + 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(); - table.put(key, value); + // 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(); + table.put(key, value); + } } + } else { + Messages.loge(additions + " could not be read"); } // check for platform-specific properties in the defaults @@ -165,8 +169,7 @@ public class Settings { try { int v = Integer.parseInt(s.substring(1), 16); parsed = new Color(v); - } catch (Exception e) { - } + } catch (Exception ignored) { } } return parsed; } @@ -198,10 +201,10 @@ public class Settings { String name = pieces[0]; int style = Font.PLAIN; // equals zero - if (pieces[1].indexOf("bold") != -1) { //$NON-NLS-1$ + if (pieces[1].contains("bold")) { //$NON-NLS-1$ style |= Font.BOLD; } - if (pieces[1].indexOf("italic") != -1) { //$NON-NLS-1$ + if (pieces[1].contains("italic")) { //$NON-NLS-1$ style |= Font.ITALIC; } int size = PApplet.parseInt(pieces[2], 12); diff --git a/app/src/processing/app/WebServer.java b/app/src/processing/app/WebServer.java index 710fd7b2e..797ffc2b8 100644 --- a/app/src/processing/app/WebServer.java +++ b/app/src/processing/app/WebServer.java @@ -16,17 +16,17 @@ import java.util.zip.*; */ public class WebServer implements HttpConstants { - /* Where worker threads stand idle */ - static Vector threads = new Vector(); + /* Where worker threads stand idle */ + static final Vector threads = new Vector<>(); - /* the web server's virtual root */ - //static File root; + /* the web server's virtual root */ + //static File root; - /* timeout on client connections */ - static int timeout = 10000; + /* timeout on client connections */ + static int timeout = 10000; - /* max # worker threads */ - static int workers = 5; + /* max # worker threads */ + static int workers = 5; // static PrintStream log = System.out; @@ -88,77 +88,57 @@ public class WebServer implements HttpConstants { */ - /* print to stdout */ -// protected static void p(String s) { -// System.out.println(s); -// } + /* print to the log file */ + protected static void log(String s) { + if (Base.DEBUG) { + System.out.println(s); + } + } - /* print to the log file */ - protected static void log(String s) { - if (false) { - System.out.println(s); - } -// synchronized (log) { -// log.println(s); -// log.flush(); -// } + + static public int launch(String zipPath) throws IOException { + final ZipFile zip = new ZipFile(zipPath); + final Map entries = new HashMap<>(); + Enumeration en = zip.entries(); + while (en.hasMoreElements()) { + ZipEntry entry = en.nextElement(); + entries.put(entry.getName(), entry); } + // start worker threads + for (int i = 0; i < workers; ++i) { + WebServerWorker w = new WebServerWorker(zip, entries); + Thread t = new Thread(w, "Web Server Worker #" + i); + t.start(); + threads.addElement(w); + } - //public static void main(String[] a) throws Exception { - static public int launch(String zipPath) throws IOException { - final ZipFile zip = new ZipFile(zipPath); - final Map entries = new HashMap(); - Enumeration en = zip.entries(); - while (en.hasMoreElements()) { - ZipEntry entry = (ZipEntry) en.nextElement(); - entries.put(entry.getName(), entry); - } + final int port = 8080; -// if (a.length > 0) { -// port = Integer.parseInt(a[0]); -// } -// loadProps(); -// printProps(); - // start worker threads - for (int i = 0; i < workers; ++i) { - WebServerWorker w = new WebServerWorker(zip, entries); - Thread t = new Thread(w, "Web Server Worker #" + i); - t.start(); - threads.addElement(w); - } - - final int port = 8080; - - //SwingUtilities.invokeLater(new Runnable() { - Runnable r = new Runnable() { - public void run() { - try { - ServerSocket ss = new ServerSocket(port); - while (true) { - Socket s = ss.accept(); - WebServerWorker w = null; - synchronized (threads) { - if (threads.isEmpty()) { - WebServerWorker ws = new WebServerWorker(zip, entries); - ws.setSocket(s); - (new Thread(ws, "additional worker")).start(); - } else { - w = (WebServerWorker) threads.elementAt(0); - threads.removeElementAt(0); - w.setSocket(s); - } - } - } - } catch (IOException e) { - e.printStackTrace(); + Runnable r = () -> { + try { + ServerSocket ss = new ServerSocket(port); + while (true) { + Socket s = ss.accept(); + synchronized (threads) { + if (threads.isEmpty()) { + WebServerWorker ws = new WebServerWorker(zip, entries); + ws.setSocket(s); + (new Thread(ws, "additional worker")).start(); + } else { + WebServerWorker w = threads.elementAt(0); + threads.removeElementAt(0); + w.setSocket(s); } } - }; - new Thread(r).start(); -// }); - return port; - } + } + } catch (IOException e) { + e.printStackTrace(); + } + }; + new Thread(r).start(); + return port; + } } @@ -213,13 +193,12 @@ class WebServerWorker /*extends WebServer*/ implements HttpConstants, Runnable { * than numHandler connections. */ s = null; - Vector pool = WebServer.threads; - synchronized (pool) { - if (pool.size() >= WebServer.workers) { + synchronized (WebServer.threads) { + if (WebServer.threads.size() >= WebServer.workers) { /* too many threads, exit this one */ return; } else { - pool.addElement(this); + WebServer.threads.addElement(this); } } } @@ -241,7 +220,7 @@ class WebServerWorker /*extends WebServer*/ implements HttpConstants, Runnable { try { // We only support HTTP GET/HEAD, and don't support any fancy HTTP // options, so we're only interested really in the first line. - int nread = 0, r = 0; + int nread = 0, r; outerloop: while (nread < BUF_SIZE) { @@ -286,7 +265,7 @@ outerloop: return; } - int i = 0; + int i; /* find the file name, from: * GET /foo/bar.html HTTP/1.0 * extract "/foo/bar.html" @@ -339,8 +318,8 @@ outerloop: boolean printHeaders(ZipEntry targ, PrintStream ps) throws IOException { - boolean ret = false; - int rCode = 0; + boolean ret; + int rCode; if (targ == null) { rCode = HTTP_NOT_FOUND; ps.print("HTTP/1.0 " + HTTP_NOT_FOUND + " Not Found"); @@ -387,9 +366,10 @@ outerloop: } + /* boolean printHeaders(File targ, PrintStream ps) throws IOException { - boolean ret = false; - int rCode = 0; + boolean ret; + int rCode; if (!targ.exists()) { rCode = HTTP_NOT_FOUND; ps.print("HTTP/1.0 " + HTTP_NOT_FOUND + " Not Found"); @@ -430,6 +410,7 @@ outerloop: } return ret; } + */ void send404(PrintStream ps) throws IOException { @@ -442,8 +423,9 @@ outerloop: } + /* void sendFile(File targ, PrintStream ps) throws IOException { - InputStream is = null; + InputStream is; ps.write(EOL); if (targ.isDirectory()) { listDirectory(targ, ps); @@ -453,21 +435,20 @@ outerloop: } sendFile(is, ps); } + */ void sendFile(InputStream is, PrintStream ps) throws IOException { - try { - int n; - while ((n = is.read(buf)) > 0) { - ps.write(buf, 0, n); - } - } finally { - is.close(); + try (is) { + int n; + while ((n = is.read(buf)) > 0) { + ps.write(buf, 0, n); } + } } /* mapping of file extensions to content-types */ - static Map map = new HashMap(); + static Map map = new HashMap<>(); static { fillMap(); @@ -508,7 +489,8 @@ outerloop: setSuffix(".pl", "text/plain"); } - void listDirectory(File dir, PrintStream ps) throws IOException { + /* + void listDirectory(File dir, PrintStream ps) { ps.println("Directory listing

\n"); ps.println("Parent Directory
\n"); String[] list = dir.list(); @@ -522,51 +504,51 @@ outerloop: } ps.println("



" + (new Date()) + ""); } - + */ } interface HttpConstants { - /** 2XX: generally "OK" */ - public static final int HTTP_OK = 200; - public static final int HTTP_CREATED = 201; - public static final int HTTP_ACCEPTED = 202; - public static final int HTTP_NOT_AUTHORITATIVE = 203; - public static final int HTTP_NO_CONTENT = 204; - public static final int HTTP_RESET = 205; - public static final int HTTP_PARTIAL = 206; + /* 2XX: generally "OK" */ + int HTTP_OK = 200; +// int HTTP_CREATED = 201; +// int HTTP_ACCEPTED = 202; +// int HTTP_NOT_AUTHORITATIVE = 203; +// int HTTP_NO_CONTENT = 204; +// int HTTP_RESET = 205; +// int HTTP_PARTIAL = 206; - /** 3XX: relocation/redirect */ - public static final int HTTP_MULT_CHOICE = 300; - public static final int HTTP_MOVED_PERM = 301; - public static final int HTTP_MOVED_TEMP = 302; - public static final int HTTP_SEE_OTHER = 303; - public static final int HTTP_NOT_MODIFIED = 304; - public static final int HTTP_USE_PROXY = 305; + /* 3XX: relocation/redirect */ +// int HTTP_MULT_CHOICE = 300; +// int HTTP_MOVED_PERM = 301; +// int HTTP_MOVED_TEMP = 302; +// int HTTP_SEE_OTHER = 303; +// int HTTP_NOT_MODIFIED = 304; +// int HTTP_USE_PROXY = 305; - /** 4XX: client error */ - public static final int HTTP_BAD_REQUEST = 400; - public static final int HTTP_UNAUTHORIZED = 401; - public static final int HTTP_PAYMENT_REQUIRED = 402; - public static final int HTTP_FORBIDDEN = 403; - public static final int HTTP_NOT_FOUND = 404; - public static final int HTTP_BAD_METHOD = 405; - public static final int HTTP_NOT_ACCEPTABLE = 406; - public static final int HTTP_PROXY_AUTH = 407; - public static final int HTTP_CLIENT_TIMEOUT = 408; - public static final int HTTP_CONFLICT = 409; - public static final int HTTP_GONE = 410; - public static final int HTTP_LENGTH_REQUIRED = 411; - public static final int HTTP_PRECON_FAILED = 412; - public static final int HTTP_ENTITY_TOO_LARGE = 413; - public static final int HTTP_REQ_TOO_LONG = 414; - public static final int HTTP_UNSUPPORTED_TYPE = 415; + /* 4XX: client error */ +// int HTTP_BAD_REQUEST = 400; +// int HTTP_UNAUTHORIZED = 401; +// int HTTP_PAYMENT_REQUIRED = 402; +// int HTTP_FORBIDDEN = 403; + int HTTP_NOT_FOUND = 404; + int HTTP_BAD_METHOD = 405; +// int HTTP_NOT_ACCEPTABLE = 406; +// int HTTP_PROXY_AUTH = 407; +// int HTTP_CLIENT_TIMEOUT = 408; +// int HTTP_CONFLICT = 409; +// int HTTP_GONE = 410; +// int HTTP_LENGTH_REQUIRED = 411; +// int HTTP_PRECON_FAILED = 412; +// int HTTP_ENTITY_TOO_LARGE = 413; +// int HTTP_REQ_TOO_LONG = 414; +// int HTTP_UNSUPPORTED_TYPE = 415; - /** 5XX: server error */ - public static final int HTTP_SERVER_ERROR = 500; - public static final int HTTP_INTERNAL_ERROR = 501; - public static final int HTTP_BAD_GATEWAY = 502; - public static final int HTTP_UNAVAILABLE = 503; - public static final int HTTP_GATEWAY_TIMEOUT = 504; - public static final int HTTP_VERSION = 505; + /* 5XX: server error */ +// int HTTP_SERVER_ERROR = 500; +// int HTTP_INTERNAL_ERROR = 501; +// int HTTP_BAD_GATEWAY = 502; +// int HTTP_UNAVAILABLE = 503; +// int HTTP_GATEWAY_TIMEOUT = 504; +// int HTTP_VERSION = 505; } diff --git a/app/src/processing/app/contrib/ModeContribution.java b/app/src/processing/app/contrib/ModeContribution.java index 0005de247..672e188b0 100644 --- a/app/src/processing/app/contrib/ModeContribution.java +++ b/app/src/processing/app/contrib/ModeContribution.java @@ -78,7 +78,6 @@ public class ModeContribution extends LocalContribution { * @param base the base object that this will be tied to * @param folder location inside the sketchbook modes folder or contrib * @param className name of class and full package, or null to use default - * @throws Exception */ public ModeContribution(Base base, File folder, String className) throws Exception { @@ -87,7 +86,7 @@ public class ModeContribution extends LocalContribution { if (className != null) { Class modeClass = loader.loadClass(className); Messages.log("Got mode class " + modeClass); - Constructor con = modeClass.getConstructor(Base.class, File.class); + Constructor con = modeClass.getConstructor(Base.class, File.class); mode = (Mode) con.newInstance(base, folder); mode.setClassLoader(loader); if (base != null) { @@ -133,7 +132,7 @@ public class ModeContribution extends LocalContribution { File modeDirectory = new File(folder, getTypeName()); if (modeDirectory.exists()) { Messages.log("checking mode folder regarding class name " + className); - // If no class name specified, search the main .jar for the + // If no class name specified, search the main .jar for the // full name package and mode name. if (className == null) { String shortName = folder.getName(); @@ -167,9 +166,9 @@ public class ModeContribution extends LocalContribution { File modeFolder = installedModes.get(modeImport).getFolder(); File[] archives = Util.listJarFiles(new File(modeFolder, "mode")); if (archives != null && archives.length > 0) { - for (int i = 0; i < archives.length; i++) { + for (File archive : archives) { // Base.log("Adding jar dependency: " + archives[i].getAbsolutePath()); - extraUrls.add(archives[i].toURI().toURL()); + extraUrls.add(archive.toURI().toURL()); } } } else { @@ -199,8 +198,6 @@ public class ModeContribution extends LocalContribution { loader = new URLClassLoader(urlList); Messages.log("loading above JARs with loader " + loader); -// System.out.println("listing classes for loader " + loader); -// listClasses(loader); } } diff --git a/app/src/processing/app/platform/DefaultPlatform.java b/app/src/processing/app/platform/DefaultPlatform.java index 6cba3b164..e80adac32 100644 --- a/app/src/processing/app/platform/DefaultPlatform.java +++ b/app/src/processing/app/platform/DefaultPlatform.java @@ -29,6 +29,7 @@ import java.io.File; import java.net.URI; import javax.swing.UIManager; +import javax.swing.plaf.FontUIResource; import com.sun.jna.Library; import com.sun.jna.Native; @@ -110,6 +111,24 @@ public class DefaultPlatform { } else { UIManager.setLookAndFeel(laf); } + + // If the default has been overridden in the preferences, set the font + String fontName = Preferences.get("ui.font.family"); + int fontSize = Preferences.getInteger("ui.font.size"); + if (!"Dialog".equals(fontName) || fontSize != 12) { + setUIFont(new FontUIResource(fontName, Font.PLAIN, fontSize)); + } + } + + + // Rewritten from https://stackoverflow.com/a/7434935 + static private void setUIFont(FontUIResource f) { + for (Object key : UIManager.getLookAndFeelDefaults().keySet()) { + Object value = UIManager.get(key); + if (value instanceof FontUIResource) { + UIManager.put(key, f); + } + } } @@ -120,8 +139,11 @@ public class DefaultPlatform { scaleDefaultFont(widgetName); } - UIManager.put("Label.font", new Font("Dialog", Font.PLAIN, Toolkit.zoom(12))); - UIManager.put("TextField.font", new Font("Dialog", Font.PLAIN, Toolkit.zoom(12))); + String fontName = Preferences.get("ui.font.family"); + int fontSize = Preferences.getInteger("ui.font.size"); + FontUIResource uiFont = new FontUIResource(fontName, Font.PLAIN, Toolkit.zoom(fontSize)); + UIManager.put("Label.font", uiFont); + UIManager.put("TextField.font", uiFont); } } diff --git a/app/src/processing/app/platform/MacPlatform.java b/app/src/processing/app/platform/MacPlatform.java index d5eb731da..8a9cea518 100644 --- a/app/src/processing/app/platform/MacPlatform.java +++ b/app/src/processing/app/platform/MacPlatform.java @@ -22,10 +22,7 @@ package processing.app.platform; -import java.awt.Color; -import java.awt.Component; -import java.awt.Desktop; -import java.awt.Graphics; +import java.awt.*; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -35,10 +32,10 @@ import javax.swing.Icon; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.UIManager; +import javax.swing.plaf.FontUIResource; import processing.app.Base; import processing.app.Messages; -import processing.app.Preferences; import processing.app.ui.About; @@ -74,13 +71,9 @@ public class MacPlatform extends DefaultPlatform { defaultMenuBar.add(fileMenu); desktop.setDefaultMenuBar(defaultMenuBar); - desktop.setAboutHandler((event) -> { - new About(null); - }); + desktop.setAboutHandler((event) -> new About(null)); - desktop.setPreferencesHandler((event) -> { - base.handlePrefs(); - }); + desktop.setPreferencesHandler((event) -> base.handlePrefs()); desktop.setOpenFileHandler((event) -> { for (File file : event.getFiles()) { @@ -106,8 +99,15 @@ public class MacPlatform extends DefaultPlatform { public void setLookAndFeel() throws Exception { super.setLookAndFeel(); - String laf = Preferences.get("editor.laf"); - if ("org.violetlib.aqua.AquaLookAndFeel".equals(laf)) { + String laf = UIManager.getLookAndFeel().getClass().getName(); + if ("com.apple.laf.AquaLookAndFeel".equals(laf)) { + //setUIFont(new FontUIResource(".AppleSystemUIFont", Font.PLAIN, 12)); + // oh my god, the kerning, the tracking, my eyes... + //setUIFont(new FontUIResource(".SFNS-Regular", Font.PLAIN, 13)); + //setUIFont(new FontUIResource(Toolkit.getSansFont(14, Font.PLAIN))); + //setUIFont(new FontUIResource("Roboto-Regular", Font.PLAIN, 13)); + + } else if ("org.violetlib.aqua.AquaLookAndFeel".equals(laf)) { Icon collapse = new VAquaTreeIcon(true); Icon open = new VAquaTreeIcon(false); Icon leaf = new VAquaEmptyIcon(); @@ -149,13 +149,13 @@ public class MacPlatform extends DefaultPlatform { // 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] + // home directory seems verboten on more recent macOS versions [fry 191008] protected String getLibraryFolder() throws FileNotFoundException { return System.getProperty("user.home") + "/Library"; } - // see notes on getLibraryFolder() + // TODO see note on getLibraryFolder() protected String getDocumentsFolder() throws FileNotFoundException { return System.getProperty("user.home") + "/Documents"; } @@ -212,7 +212,7 @@ public class MacPlatform extends DefaultPlatform { * Swing components. Without this, some sizing calculations non-standard * components may fail or become unreliable. */ - class VAquaEmptyIcon implements Icon { + static class VAquaEmptyIcon implements Icon { private final int SIZE = 1; @Override @@ -238,7 +238,7 @@ public class MacPlatform extends DefaultPlatform { * Vaqua with non-standard swing components. Without this, some sizing * calculations within non-standard components may fail or become unreliable. */ - private class VAquaTreeIcon implements Icon { + static private class VAquaTreeIcon implements Icon { private final int SIZE = 12; private final boolean isOpen; diff --git a/app/src/processing/app/syntax/DefaultInputHandler.java b/app/src/processing/app/syntax/DefaultInputHandler.java index c8db2bf7d..4ceae15b7 100644 --- a/app/src/processing/app/syntax/DefaultInputHandler.java +++ b/app/src/processing/app/syntax/DefaultInputHandler.java @@ -11,10 +11,9 @@ package processing.app.syntax; import javax.swing.KeyStroke; import java.awt.event.*; -import java.awt.Toolkit; import java.util.Map; import java.util.HashMap; -import java.util.StringTokenizer; + /** * The default input handler. It maps sequences of keystrokes into actions @@ -22,357 +21,264 @@ import java.util.StringTokenizer; * @author Slava Pestov * @version $Id$ */ -public class DefaultInputHandler extends InputHandler -{ - /** - * Creates a new input handler with no key bindings defined. - */ - public DefaultInputHandler() - { - bindings = currentBindings = new HashMap(); +public class DefaultInputHandler extends InputHandler { + final private Map bindings; + + + /** + * Creates a new input handler with no key bindings defined. + */ + public DefaultInputHandler() { + bindings = new HashMap<>(); + } + + + /** + * Sets up the default key bindings. + */ + public void addDefaultKeyBindings() { + addKeyBinding("BACK_SPACE", BACKSPACE); + addKeyBinding("C+BACK_SPACE", BACKSPACE_WORD); + addKeyBinding("DELETE", DELETE); + addKeyBinding("C+DELETE", DELETE_WORD); + + addKeyBinding("ENTER", INSERT_BREAK); + addKeyBinding("TAB", INSERT_TAB); + + addKeyBinding("INSERT", OVERWRITE); + + addKeyBinding("HOME", HOME); + addKeyBinding("END", END); + addKeyBinding("S+HOME", SELECT_HOME); + addKeyBinding("S+END", SELECT_END); + addKeyBinding("C+HOME", DOCUMENT_HOME); + addKeyBinding("C+END", DOCUMENT_END); + addKeyBinding("CS+HOME", SELECT_DOC_HOME); + addKeyBinding("CS+END", SELECT_DOC_END); + + addKeyBinding("PAGE_UP", PREV_PAGE); + addKeyBinding("PAGE_DOWN", NEXT_PAGE); + addKeyBinding("S+PAGE_UP", SELECT_PREV_PAGE); + addKeyBinding("S+PAGE_DOWN", SELECT_NEXT_PAGE); + + addKeyBinding("LEFT", PREV_CHAR); + addKeyBinding("S+LEFT", SELECT_PREV_CHAR); + addKeyBinding("C+LEFT", PREV_WORD); + addKeyBinding("CS+LEFT", SELECT_PREV_WORD); + addKeyBinding("RIGHT", NEXT_CHAR); + addKeyBinding("S+RIGHT", SELECT_NEXT_CHAR); + addKeyBinding("C+RIGHT", NEXT_WORD); + addKeyBinding("CS+RIGHT", SELECT_NEXT_WORD); + addKeyBinding("UP", PREV_LINE); + addKeyBinding("S+UP", SELECT_PREV_LINE); + addKeyBinding("DOWN", NEXT_LINE); + addKeyBinding("S+DOWN", SELECT_NEXT_LINE); + + addKeyBinding("C+ENTER", REPEAT); + } + + + /** + * Adds a key binding to this input handler. The key binding is + * a list of white space separated key strokes of the form + * [modifiers+]key where modifier is C for Control, A for Alt, + * or S for Shift, and key is either a character (a-z) or a field + * name in the KeyEvent class prefixed with VK_ (e.g., BACK_SPACE) + * @param keyBinding The key binding + * @param action The action + */ + public void addKeyBinding(String keyBinding, ActionListener action) { + KeyStroke keyStroke = parseKeyStroke(keyBinding); + if (keyStroke != null) { + bindings.put(keyStroke, action); + } + } + + + /** + * Removes a key binding from this input handler. This is not yet + * implemented. + * @param keyBinding The key binding + */ + public void removeKeyBinding(String keyBinding) { + throw new InternalError("Not yet implemented"); + } + + + /** + * Removes all key bindings from this input handler. + */ + public void removeAllKeyBindings() { + bindings.clear(); + } + + + /** + * Returns a copy of this input handler that shares the same + * key bindings. Setting key bindings in the copy will also + * set them in the original. + */ + public InputHandler copy() { + return new DefaultInputHandler(this); + } + + + /** + * Handle a key pressed event. This will look up the binding for + * the key stroke and execute it. + */ + public void keyPressed(KeyEvent evt) { + int keyCode = evt.getKeyCode(); + int modifiers = evt.getModifiers(); + + // moved this earlier so it doesn't get random meta clicks + if (keyCode == KeyEvent.VK_CONTROL || + keyCode == KeyEvent.VK_SHIFT || + keyCode == KeyEvent.VK_ALT || + keyCode == KeyEvent.VK_META) { + return; + } + + // Don't get command-s or other menu key equivalents on macOS + // unless it's something that's specifically bound (cmd-left or right) + if ((modifiers & InputEvent.META_MASK) != 0) { + KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers); + if (bindings.get(keyStroke) == null) { + return; + } + } + + if ((modifiers & ~InputEvent.SHIFT_MASK) != 0 + || evt.isActionKey() + || keyCode == KeyEvent.VK_BACK_SPACE + || keyCode == KeyEvent.VK_DELETE + || keyCode == KeyEvent.VK_ENTER + || keyCode == KeyEvent.VK_TAB + || keyCode == KeyEvent.VK_ESCAPE) { + if (grabAction != null) { + handleGrabAction(evt); + return; + } + + KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers); + ActionListener o = bindings.get(keyStroke); + if (o != null) { + executeAction(o, evt.getSource(), null); + evt.consume(); + } + } + } + + + /** + * Handle a key typed event. This inserts the key into the text area. + */ + public void keyTyped(KeyEvent evt) { + int modifiers = evt.getModifiers(); + char c = evt.getKeyChar(); + + // this is the apple/cmd key on macosx.. so menu commands + // were being passed through as legit keys.. added this line + // in an attempt to prevent. + if ((modifiers & InputEvent.META_MASK) != 0) return; + + // Prevent CTRL-/ from going through as a typed '/' character + // http://code.google.com/p/processing/issues/detail?id=596 + if ((modifiers & InputEvent.CTRL_MASK) != 0 && c == '/') return; + + if (c != KeyEvent.CHAR_UNDEFINED) { + if (c >= 0x20 && c != 0x7f) { + KeyStroke keyStroke = KeyStroke.getKeyStroke(Character.toUpperCase(c)); + ActionListener o = bindings.get(keyStroke); + + if (o != null) { + executeAction(o, evt.getSource(), String.valueOf(c)); + return; } - /** - * Sets up the default key bindings. - */ - public void addDefaultKeyBindings() - { - addKeyBinding("BACK_SPACE",BACKSPACE); - addKeyBinding("C+BACK_SPACE",BACKSPACE_WORD); - addKeyBinding("DELETE",DELETE); - addKeyBinding("C+DELETE",DELETE_WORD); - - addKeyBinding("ENTER",INSERT_BREAK); - addKeyBinding("TAB",INSERT_TAB); - - addKeyBinding("INSERT",OVERWRITE); - - addKeyBinding("HOME",HOME); - addKeyBinding("END",END); - addKeyBinding("S+HOME",SELECT_HOME); - addKeyBinding("S+END",SELECT_END); - addKeyBinding("C+HOME",DOCUMENT_HOME); - addKeyBinding("C+END",DOCUMENT_END); - addKeyBinding("CS+HOME",SELECT_DOC_HOME); - addKeyBinding("CS+END",SELECT_DOC_END); - - addKeyBinding("PAGE_UP",PREV_PAGE); - addKeyBinding("PAGE_DOWN",NEXT_PAGE); - addKeyBinding("S+PAGE_UP",SELECT_PREV_PAGE); - addKeyBinding("S+PAGE_DOWN",SELECT_NEXT_PAGE); - - addKeyBinding("LEFT",PREV_CHAR); - addKeyBinding("S+LEFT",SELECT_PREV_CHAR); - addKeyBinding("C+LEFT",PREV_WORD); - addKeyBinding("CS+LEFT",SELECT_PREV_WORD); - addKeyBinding("RIGHT",NEXT_CHAR); - addKeyBinding("S+RIGHT",SELECT_NEXT_CHAR); - addKeyBinding("C+RIGHT",NEXT_WORD); - addKeyBinding("CS+RIGHT",SELECT_NEXT_WORD); - addKeyBinding("UP",PREV_LINE); - addKeyBinding("S+UP",SELECT_PREV_LINE); - addKeyBinding("DOWN",NEXT_LINE); - addKeyBinding("S+DOWN",SELECT_NEXT_LINE); - - addKeyBinding("C+ENTER",REPEAT); + if (grabAction != null) { + handleGrabAction(evt); + return; } - /** - * Adds a key binding to this input handler. The key binding is - * a list of white space separated key strokes of the form - * [modifiers+]key where modifier is C for Control, A for Alt, - * or S for Shift, and key is either a character (a-z) or a field - * name in the KeyEvent class prefixed with VK_ (e.g., BACK_SPACE) - * @param keyBinding The key binding - * @param action The action - */ - public void addKeyBinding(String keyBinding, ActionListener action) - { - Map current = bindings; - - StringTokenizer st = new StringTokenizer(keyBinding); - while(st.hasMoreTokens()) - { - KeyStroke keyStroke = parseKeyStroke(st.nextToken()); - if(keyStroke == null) - return; - - if(st.hasMoreTokens()) - { - Object o = current.get(keyStroke); - if(o instanceof Map) - current = (Map)o; - else - { - o = new HashMap(); - current.put(keyStroke,o); - current = (Map)o; - } - } - else - current.put(keyStroke,action); - } + // 0-9 adds another 'digit' to the repeat number + if (repeat && Character.isDigit(c)) { + repeatCount *= 10; + repeatCount += (c - '0'); + return; } - /** - * Removes a key binding from this input handler. This is not yet - * implemented. - * @param keyBinding The key binding - */ - public void removeKeyBinding(String keyBinding) - { - throw new InternalError("Not yet implemented"); + executeAction(INSERT_CHAR,evt.getSource(), + String.valueOf(evt.getKeyChar())); + + repeatCount = 0; + repeat = false; + } + } + } + + + /** + * Converts a string to a keystroke. The string should be of the + * form modifiers+shortcut where modifiers + * is any combination of A for Alt, C for Control, S for Shift + * or M for Meta, and shortcut is either a single character, + * or a keycode name from the KeyEvent class, without + * the VK_ prefix. + * @param keyStroke A string description of the key stroke + */ + static public KeyStroke parseKeyStroke(String keyStroke) { + if (keyStroke == null) { + return null; + } + int modifiers = 0; + int index = keyStroke.indexOf('+'); + if (index != -1) { + for (int i = 0; i < index; i++) { + switch (Character.toUpperCase(keyStroke.charAt(i))) { + case 'A': + modifiers |= InputEvent.ALT_MASK; + break; + case 'C': + modifiers |= InputEvent.CTRL_MASK; + break; + case 'M': + modifiers |= InputEvent.META_MASK; + break; + case 'S': + modifiers |= InputEvent.SHIFT_MASK; + break; } + } + } + String key = keyStroke.substring(index + 1); + if (key.length() == 1) { + char ch = Character.toUpperCase(key.charAt(0)); + if (modifiers == 0) { + return KeyStroke.getKeyStroke(ch); + } else { + return KeyStroke.getKeyStroke(ch,modifiers); + } - /** - * Removes all key bindings from this input handler. - */ - public void removeAllKeyBindings() - { - bindings.clear(); - } + } else if (key.length() == 0) { + System.err.println("Invalid key stroke: " + keyStroke); + return null; - /** - * Returns a copy of this input handler that shares the same - * key bindings. Setting key bindings in the copy will also - * set them in the original. - */ - public InputHandler copy() - { - return new DefaultInputHandler(this); - } + } else { + int ch; - /** - * Handle a key pressed event. This will look up the binding for - * the key stroke and execute it. - */ - public void keyPressed(KeyEvent evt) - { - int keyCode = evt.getKeyCode(); - int modifiers = evt.getModifiers(); + try { + ch = KeyEvent.class.getField("VK_".concat(key)).getInt(null); + } catch (Exception e) { + System.err.println("Invalid key stroke: " + keyStroke); + return null; + } + return KeyStroke.getKeyStroke(ch,modifiers); + } + } - // moved this earlier so it doesn't get random meta clicks - if (keyCode == KeyEvent.VK_CONTROL || - keyCode == KeyEvent.VK_SHIFT || - keyCode == KeyEvent.VK_ALT || - keyCode == KeyEvent.VK_META) { - return; - } - // don't get command-s or other menu key equivs on mac - // unless it's something that's specifically bound (cmd-left or right) - //if ((modifiers & KeyEvent.META_MASK) != 0) return; - if ((modifiers & InputEvent.META_MASK) != 0) { - KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers); - if (currentBindings.get(keyStroke) == null) { - return; - } - } - - /* - char keyChar = evt.getKeyChar(); - System.out.println("code=" + keyCode + " char=" + keyChar + - " charint=" + ((int)keyChar)); - System.out.println("other codes " + KeyEvent.VK_ALT + " " + - KeyEvent.VK_META); - */ - - if((modifiers & ~InputEvent.SHIFT_MASK) != 0 - || evt.isActionKey() - || keyCode == KeyEvent.VK_BACK_SPACE - || keyCode == KeyEvent.VK_DELETE - || keyCode == KeyEvent.VK_ENTER - || keyCode == KeyEvent.VK_TAB - || keyCode == KeyEvent.VK_ESCAPE) - { - if(grabAction != null) - { - handleGrabAction(evt); - return; - } - - KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, - modifiers); - Object o = currentBindings.get(keyStroke); - if(o == null) - { - // Don't beep if the user presses some - // key we don't know about unless a - // prefix is active. Otherwise it will - // beep when caps lock is pressed, etc. - if(currentBindings != bindings) - { - Toolkit.getDefaultToolkit().beep(); - // F10 should be passed on, but C+e F10 - // shouldn't - repeatCount = 0; - repeat = false; - evt.consume(); - } - currentBindings = bindings; - return; - } - else if(o instanceof ActionListener) - { - currentBindings = bindings; - - executeAction(((ActionListener)o), - evt.getSource(),null); - - evt.consume(); - return; - } - else if(o instanceof Map) - { - currentBindings = (Map)o; - evt.consume(); - return; - } - } - } - - /** - * Handle a key typed event. This inserts the key into the text area. - */ - public void keyTyped(KeyEvent evt) - { - int modifiers = evt.getModifiers(); - char c = evt.getKeyChar(); - - // this is the apple/cmd key on macosx.. so menu commands - // were being passed through as legit keys.. added this line - // in an attempt to prevent. - if ((modifiers & InputEvent.META_MASK) != 0) return; - - // Prevent CTRL-/ from going through as a typed '/' character - // http://code.google.com/p/processing/issues/detail?id=596 - if ((modifiers & InputEvent.CTRL_MASK) != 0 && c == '/') return; - - if (c != KeyEvent.CHAR_UNDEFINED) // && - // (modifiers & KeyEvent.ALT_MASK) == 0) - { - if(c >= 0x20 && c != 0x7f) - { - KeyStroke keyStroke = KeyStroke.getKeyStroke( - Character.toUpperCase(c)); - Object o = currentBindings.get(keyStroke); - - if(o instanceof Map) - { - currentBindings = (Map)o; - return; - } - else if(o instanceof ActionListener) - { - currentBindings = bindings; - executeAction((ActionListener)o, - evt.getSource(), - String.valueOf(c)); - return; - } - - currentBindings = bindings; - - if(grabAction != null) - { - handleGrabAction(evt); - return; - } - - // 0-9 adds another 'digit' to the repeat number - if(repeat && Character.isDigit(c)) - { - repeatCount *= 10; - repeatCount += (c - '0'); - return; - } - - executeAction(INSERT_CHAR,evt.getSource(), - String.valueOf(evt.getKeyChar())); - - repeatCount = 0; - repeat = false; - } - } - } - - /** - * Converts a string to a keystroke. The string should be of the - * form modifiers+shortcut where modifiers - * is any combination of A for Alt, C for Control, S for Shift - * or M for Meta, and shortcut is either a single character, - * or a keycode name from the KeyEvent class, without - * the VK_ prefix. - * @param keyStroke A string description of the key stroke - */ - public static KeyStroke parseKeyStroke(String keyStroke) - { - if(keyStroke == null) - return null; - int modifiers = 0; - int index = keyStroke.indexOf('+'); - if(index != -1) - { - for(int i = 0; i < index; i++) - { - switch(Character.toUpperCase(keyStroke - .charAt(i))) - { - case 'A': - modifiers |= InputEvent.ALT_MASK; - break; - case 'C': - modifiers |= InputEvent.CTRL_MASK; - break; - case 'M': - modifiers |= InputEvent.META_MASK; - break; - case 'S': - modifiers |= InputEvent.SHIFT_MASK; - break; - } - } - } - String key = keyStroke.substring(index + 1); - if(key.length() == 1) - { - char ch = Character.toUpperCase(key.charAt(0)); - if(modifiers == 0) - return KeyStroke.getKeyStroke(ch); - else - return KeyStroke.getKeyStroke(ch,modifiers); - } - else if(key.length() == 0) - { - System.err.println("Invalid key stroke: " + keyStroke); - return null; - } - else - { - int ch; - - try - { - ch = KeyEvent.class.getField("VK_".concat(key)) - .getInt(null); - } - catch(Exception e) - { - System.err.println("Invalid key stroke: " - + keyStroke); - return null; - } - - return KeyStroke.getKeyStroke(ch,modifiers); - } - } - - // private members - private Map bindings; - private Map currentBindings; - - private DefaultInputHandler(DefaultInputHandler copy) - { - bindings = currentBindings = copy.bindings; - } + private DefaultInputHandler(DefaultInputHandler copy) { + bindings = copy.bindings; + } } diff --git a/app/src/processing/app/syntax/JEditTextArea.java b/app/src/processing/app/syntax/JEditTextArea.java index 8554078f4..dbe954f8d 100644 --- a/app/src/processing/app/syntax/JEditTextArea.java +++ b/app/src/processing/app/syntax/JEditTextArea.java @@ -80,9 +80,9 @@ public class JEditTextArea extends JComponent private InputMethodSupport inputMethodSupport; - private TextAreaDefaults defaults; + private final TextAreaDefaults defaults; - private Brackets bracketHelper = new Brackets(); + private final Brackets bracketHelper = new Brackets(); private FontMetrics cachedPartialPixelWidthFont; private float partialPixelWidth; @@ -102,11 +102,9 @@ public class JEditTextArea extends JComponent enableEvents(AWTEvent.KEY_EVENT_MASK); if (!DISABLE_CARET) { - caretTimer = new Timer(500, new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (hasFocus()) { - blinkCaret(); - } + caretTimer = new Timer(500, e -> { + if (hasFocus()) { + blinkCaret(); } }); caretTimer.setInitialDelay(500); @@ -152,13 +150,10 @@ public class JEditTextArea extends JComponent // We don't seem to get the initial focus event? // focusedComponent = this; - addMouseWheelListener(new MouseWheelListener() { - - @Override - public void mouseWheelMoved(MouseWheelEvent e) { - if (scrollBarsInitialized) { - if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) { - int scrollAmount = e.getUnitsToScroll(); + addMouseWheelListener(e -> { + if (scrollBarsInitialized) { + if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) { + int scrollAmount = e.getUnitsToScroll(); // System.out.println("rot/amt = " + e.getWheelRotation() + " " + amt); // int max = vertical.getMaximum(); // System.out.println("UNIT SCROLL of " + amt + " at value " + vertical.getValue() + " and max " + max); @@ -174,14 +169,13 @@ public class JEditTextArea extends JComponent // } // System.out.println(" " + e); - // inertia scrolling on OS X will fire several shift-wheel events - // that are negative values.. this makes the scrolling area jump. - boolean isHorizontal = Platform.isMacOS() && e.isShiftDown(); - if (isHorizontal) { - horizontal.setValue(horizontal.getValue() + scrollAmount); - }else{ - vertical.setValue(vertical.getValue() + scrollAmount); - } + // inertia scrolling on OS X will fire several shift-wheel events + // that are negative values.. this makes the scrolling area jump. + boolean isHorizontal = Platform.isMacOS() && e.isShiftDown(); + if (isHorizontal) { + horizontal.setValue(horizontal.getValue() + scrollAmount); + }else{ + vertical.setValue(vertical.getValue() + scrollAmount); } } } @@ -191,7 +185,6 @@ public class JEditTextArea extends JComponent /** * Override this to provide your own painter for this {@link JEditTextArea}. - * @param defaults * @return a newly constructed {@link TextAreaPainter}. */ protected TextAreaPainter createPainter(final TextAreaDefaults defaults) { @@ -922,9 +915,11 @@ public class JEditTextArea extends JComponent int length = getLineLength(line); String str = getText(offset, length); - for(int i = 0; i < str.length(); i++) { - if(!Character.isWhitespace(str.charAt(i))) { - return offset + i; + if (str != null) { + for (int i = 0; i < str.length(); i++) { + if (!Character.isWhitespace(str.charAt(i))) { + return offset + i; + } } } return offset + length; @@ -948,9 +943,11 @@ public class JEditTextArea extends JComponent int length = getLineLength(line); String str = getText(offset - length - 1, length); - for (int i = 0; i < length; i++) { - if(!Character.isWhitespace(str.charAt(length - i - 1))) { - return offset - i; + if (str != null) { + for (int i = 0; i < length; i++) { + if (!Character.isWhitespace(str.charAt(length - i - 1))) { + return offset - i; + } } } return offset - length; @@ -1654,11 +1651,7 @@ public class JEditTextArea extends JComponent String selection = getSelectedText(); if (selection != null) { int repeatCount = inputHandler.getRepeatCount(); - StringBuilder sb = new StringBuilder(); - for(int i = 0; i < repeatCount; i++) - sb.append(selection); - - clipboard.setContents(new StringSelection(sb.toString()), null); + clipboard.setContents(new StringSelection(selection.repeat(Math.max(0, repeatCount))), null); } } } @@ -1689,10 +1682,8 @@ public class JEditTextArea extends JComponent + getTextAsHtml(null) + "\n"); Clipboard clipboard = processing.app.ui.Toolkit.getSystemClipboard(); - clipboard.setContents(formatted, new ClipboardOwner() { - public void lostOwnership(Clipboard clipboard, Transferable contents) { - // I don't care about ownership - } + clipboard.setContents(formatted, (clipboard1, contents) -> { + // I don't care about ownership }); } @@ -1824,7 +1815,9 @@ public class JEditTextArea extends JComponent } else if (c == '"') { buffer.append("""); } else if (c > 127) { - buffer.append("&#" + ((int) c) + ";"); // use unicode entity + buffer.append("&#"); // use unicode entity + buffer.append((int) c); // use unicode entity + buffer.append(';'); // use unicode entity } else { buffer.append(c); // normal character } @@ -1876,11 +1869,7 @@ public class JEditTextArea extends JComponent } int repeatCount = inputHandler.getRepeatCount(); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < repeatCount; i++) { - sb.append(selection); - } - selection = sb.toString(); + selection = selection.repeat(Math.max(0, repeatCount)); setSelectedText(selection); } catch (Exception e) { @@ -2189,9 +2178,9 @@ public class JEditTextArea extends JComponent centerHeight); // Lay out all status components, in order - Enumeration status = leftOfScrollBar.elements(); + Enumeration status = leftOfScrollBar.elements(); while (status.hasMoreElements()) { - Component comp = (Component)status.nextElement(); + Component comp = status.nextElement(); Dimension dim = comp.getPreferredSize(); comp.setBounds(ileft, itop + centerHeight, @@ -2210,19 +2199,9 @@ public class JEditTextArea extends JComponent private Component center; private Component right; private Component bottom; - private Vector leftOfScrollBar = new Vector(); + private final Vector leftOfScrollBar = new Vector<>(); } -// static class CaretBlinker implements ActionListener -// { -// public void actionPerformed(ActionEvent evt) -// { -// if(focusedComponent != null -// && focusedComponent.hasFocus()) -// focusedComponent.blinkCaret(); -// } -// } - class MutableCaretEvent extends CaretEvent { MutableCaretEvent() @@ -2251,14 +2230,11 @@ public class JEditTextArea extends JComponent // If this is not done, mousePressed events accumulate // and the result is that scrolling doesn't stop after // the mouse is released - SwingUtilities.invokeLater(new Runnable() { - public void run() - { - if (evt.getAdjustable() == vertical) { - setFirstLine(vertical.getValue()); - } else { - setHorizontalOffset(-horizontal.getValue()); - } + SwingUtilities.invokeLater(() -> { + if (evt.getAdjustable() == vertical) { + setFirstLine(vertical.getValue()); + } else { + setHorizontalOffset(-horizontal.getValue()); } }); } diff --git a/app/src/processing/app/syntax/TextAreaPainter.java b/app/src/processing/app/syntax/TextAreaPainter.java index 86e1ce2dc..9fa8ee6df 100644 --- a/app/src/processing/app/syntax/TextAreaPainter.java +++ b/app/src/processing/app/syntax/TextAreaPainter.java @@ -118,6 +118,7 @@ public class TextAreaPainter extends JComponent implements TabExpander { setForeground(defaults.fgcolor); setBackground(defaults.bgcolor); + /* // Ensure that our monospaced font is loaded // https://github.com/processing/processing/pull/4639 Toolkit.getMonoFontName(); @@ -132,6 +133,9 @@ public class TextAreaPainter extends JComponent implements TabExpander { plainFont = new Font(fontFamily, Font.PLAIN, fontSize); } boldFont = new Font(fontFamily, Font.BOLD, fontSize); + */ + plainFont = Preferences.getFont("editor.font.family", "editor.font.size", Font.PLAIN); + boldFont = Preferences.getFont("editor.font.family", "editor.font.size", Font.BOLD); antialias = Preferences.getBoolean("editor.smooth"); // moved from setFont() override (never quite comfortable w/ that override) @@ -693,8 +697,6 @@ public class TextAreaPainter extends JComponent implements TabExpander { * @param line The line segment * @param tokens The token list for the line * @param styles The syntax style list - * @param expander The tab expander used to determine tab stops. May - * be null * @param gfx The graphics context * @param x The x co-ordinate * @param y The y co-ordinate diff --git a/app/src/processing/app/tools/CreateFont.java b/app/src/processing/app/tools/CreateFont.java index 2fd5737a1..1ef55c2c0 100644 --- a/app/src/processing/app/tools/CreateFont.java +++ b/app/src/processing/app/tools/CreateFont.java @@ -106,7 +106,7 @@ public class CreateFont extends JFrame implements Tool { pain.add(textarea); // don't care about families starting with . or # - // also ignore dialog, dialoginput, monospaced, serif, sansserif + // also ignore Dialog, DialogInput, Monospaced, Serif, SansSerif // getFontList is deprecated in 1.4, so this has to be used //long t = System.currentTimeMillis(); @@ -115,55 +115,14 @@ public class CreateFont extends JFrame implements Tool { Font[] fonts = ge.getAllFonts(); //System.out.println("font startup took " + (System.currentTimeMillis() - t) + " ms"); - /* - if (false) { - ArrayList fontList = new ArrayList(); - File folderList = new File("/Users/fry/coconut/sys/fonts/web"); - for (File folder : folderList.listFiles()) { - if (folder.isDirectory()) { - File[] items = folder.listFiles(new FilenameFilter() { - public boolean accept(File dir, String name) { - if (name.charAt(0) == '.') return false; - return (name.toLowerCase().endsWith(".ttf") || - name.toLowerCase().endsWith(".otf")); - } - }); - for (File fontFile : items) { - try { - FileInputStream fis = new FileInputStream(fontFile); - BufferedInputStream input = new BufferedInputStream(fis); - Font font = Font.createFont(Font.TRUETYPE_FONT, input); - input.close(); - fontList.add(font); - - } catch (Exception e) { - System.out.println("Ignoring " + fontFile.getName()); - } - } - } - } -// fonts = new Font[fontList.size()]; -// fontList.toArray(fonts); - fonts = fontList.toArray(new Font[fontList.size()]); - } - */ - String[] fontList = new String[fonts.length]; - table = new HashMap(); + table = new HashMap<>(); int index = 0; - for (int i = 0; i < fonts.length; i++) { - //String psname = fonts[i].getPSName(); - //if (psname == null) System.err.println("ps name is null"); - + for (Font value : fonts) { try { - fontList[index++] = fonts[i].getPSName(); - table.put(fonts[i].getPSName(), fonts[i]); - // Checking into http://processing.org/bugs/bugzilla/407.html - // and https://github.com/processing/processing/issues/1727 -// if (fonts[i].getPSName().contains("Helv")) { -// System.out.println(fonts[i].getPSName() + " -> " + fonts[i]); -// } + fontList[index++] = value.getPSName(); + table.put(value.getPSName(), value); } catch (Exception e) { // Sometimes fonts cause lots of trouble. // http://code.google.com/p/processing/issues/detail?id=442 @@ -174,16 +133,14 @@ public class CreateFont extends JFrame implements Tool { list = new String[index]; System.arraycopy(fontList, 0, list, 0, index); - fontSelector = new JList(list); - fontSelector.addListSelectionListener(new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - if (e.getValueIsAdjusting() == false) { - selection = fontSelector.getSelectedIndex(); - okButton.setEnabled(true); - update(); - } - } - }); + fontSelector = new JList<>(list); + fontSelector.addListSelectionListener(e -> { + if (!e.getValueIsAdjusting()) { + selection = fontSelector.getSelectedIndex(); + okButton.setEnabled(true); + update(); + } + }); fontSelector.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); fontSelector.setVisibleRowCount(12); @@ -215,53 +172,30 @@ public class CreateFont extends JFrame implements Tool { panel.add(sizeSelector); smoothBox = new JCheckBox(Language.text("create_font.smooth")); - smoothBox.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - smooth = smoothBox.isSelected(); - update(); - } - }); + smoothBox.addActionListener(e -> { + smooth = smoothBox.isSelected(); + update(); + }); smoothBox.setSelected(smooth); panel.add(smoothBox); -// allBox = new JCheckBox("All Characters"); -// allBox.addActionListener(new ActionListener() { -// public void actionPerformed(ActionEvent e) { -// all = allBox.isSelected(); -// } -// }); -// allBox.setSelected(all); -// panel.add(allBox); charsetButton = new JButton(Language.text("create_font.characters")); - charsetButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - //showCharacterList(); - charSelector.setVisible(true); - } - }); + charsetButton.addActionListener(e -> charSelector.setVisible(true)); panel.add(charsetButton); pain.add(panel); - JPanel filestuff = new JPanel(); - filestuff.add(new JLabel(Language.text("create_font.filename") + ":")); - filestuff.add(filenameField = new JTextField(20)); - filestuff.add(new JLabel(".vlw")); - pain.add(filestuff); + JPanel fileStuff = new JPanel(); + fileStuff.add(new JLabel(Language.text("create_font.filename") + ":")); + fileStuff.add(filenameField = new JTextField(20)); + fileStuff.add(new JLabel(".vlw")); + pain.add(fileStuff); JPanel buttons = new JPanel(); JButton cancelButton = new JButton(Language.text("prompt.cancel")); - cancelButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - setVisible(false); - } - }); + cancelButton.addActionListener(e -> setVisible(false)); okButton = new JButton(Language.text("prompt.ok")); - okButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - build(); - } - }); + okButton.addActionListener(e -> build()); okButton.setEnabled(false); buttons.add(cancelButton); @@ -270,11 +204,7 @@ public class CreateFont extends JFrame implements Tool { JRootPane root = getRootPane(); root.setDefaultButton(okButton); - ActionListener disposer = new ActionListener() { - public void actionPerformed(ActionEvent actionEvent) { - setVisible(false); - } - }; + ActionListener disposer = actionEvent -> setVisible(false); Toolkit.registerWindowCloseKeys(root, disposer); Toolkit.setIcon(this); @@ -287,10 +217,6 @@ public class CreateFont extends JFrame implements Tool { fontSelector.setSelectedIndex(0); -// Dimension screen = Toolkit.getScreenSize(); -// windowSize = getSize(); -// setLocation((screen.width - windowSize.width) / 2, -// (screen.height - windowSize.height) / 2); setLocationRelativeTo(null); // create this behind the scenes @@ -304,35 +230,35 @@ public class CreateFont extends JFrame implements Tool { public void update() { - int fontsize = 0; + int fontSize = 0; try { - fontsize = Integer.parseInt(sizeSelector.getText().trim()); + fontSize = Integer.parseInt(sizeSelector.getText().trim()); //System.out.println("'" + sizeSelector.getText() + "'"); - } catch (NumberFormatException e2) { } + } catch (NumberFormatException ignored) { } // if a deselect occurred, selection will be -1 - if ((fontsize > 0) && (fontsize < 256) && (selection != -1)) { - //font = new Font(list[selection], Font.PLAIN, fontsize); + if ((fontSize > 0) && (fontSize < 256) && (selection != -1)) { + //font = new Font(list[selection], Font.PLAIN, fontSize); Font instance = table.get(list[selection]); -// font = instance.deriveFont(Font.PLAIN, fontsize); - font = instance.deriveFont((float) fontsize); +// font = instance.deriveFont(Font.PLAIN, fontSize); + font = instance.deriveFont((float) fontSize); //System.out.println("setting font to " + font); sample.setFont(font); String filenameSuggestion = list[selection].replace(' ', '_'); - filenameSuggestion += "-" + fontsize; + filenameSuggestion += "-" + fontSize; filenameField.setText(filenameSuggestion); } } public void build() { - int fontsize = 0; + int fontSize = 0; try { - fontsize = Integer.parseInt(sizeSelector.getText().trim()); - } catch (NumberFormatException e) { } + fontSize = Integer.parseInt(sizeSelector.getText().trim()); + } catch (NumberFormatException ignored) { } - if (fontsize <= 0) { + if (fontSize <= 0) { JOptionPane.showMessageDialog(this, "Bad font size, try again.", "Badness", JOptionPane.WARNING_MESSAGE); return; @@ -350,7 +276,7 @@ public class CreateFont extends JFrame implements Tool { try { Font instance = table.get(list[selection]); - font = instance.deriveFont(Font.PLAIN, fontsize); + font = instance.deriveFont(Font.PLAIN, fontSize); //PFont f = new PFont(font, smooth, all ? null : PFont.CHARSET); PFont f = new PFont(font, smooth, charSelector.getCharacters()); @@ -370,27 +296,6 @@ public class CreateFont extends JFrame implements Tool { setVisible(false); } - - -// /** -// * make the window vertically resizable -// */ -// public Dimension getMaximumSize() { -// return new Dimension(windowSize.width, 2000); -// } -// -// -// public Dimension getMinimumSize() { -// return windowSize; -// } - - - /* - public void show(File targetFolder) { - this.targetFolder = targetFolder; - show(); - } - */ } @@ -487,7 +392,7 @@ class CharacterSelector extends JFrame { super(Language.text("create_font.character_selector")); charsetList = new CheckBoxList(); - DefaultListModel model = new DefaultListModel(); + DefaultListModel model = new DefaultListModel<>(); charsetList.setModel(model); for (String item : blockNames) { model.addElement(new JCheckBox(item)); @@ -516,13 +421,8 @@ class CharacterSelector extends JFrame { textarea.setFont(new Font("Dialog", Font.PLAIN, 12)); pain.add(textarea); - ActionListener listener = new ActionListener() { - public void actionPerformed(ActionEvent e) { - //System.out.println("action " + unicodeCharsButton.isSelected()); - //unicodeBlockScroller.setEnabled(unicodeCharsButton.isSelected()); - charsetList.setEnabled(unicodeCharsButton.isSelected()); - } - }; + ActionListener listener = + e -> charsetList.setEnabled(unicodeCharsButton.isSelected()); defaultCharsButton = new JRadioButton(Language.text("create_font.default_characters")); allCharsButton = @@ -553,12 +453,6 @@ class CharacterSelector extends JFrame { pain.add(rightStuff); pain.add(Box.createVerticalStrut(13)); -// pain.add(radioPanel); - -// pain.add(defaultCharsButton); -// pain.add(allCharsButton); -// pain.add(unicodeCharsButton); - defaultCharsButton.setSelected(true); charsetList.setEnabled(false); @@ -568,22 +462,14 @@ class CharacterSelector extends JFrame { JPanel buttons = new JPanel(); JButton okButton = new JButton(Language.text("prompt.ok")); - okButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - setVisible(false); - } - }); + okButton.addActionListener(e -> setVisible(false)); okButton.setEnabled(true); buttons.add(okButton); pain.add(buttons); JRootPane root = getRootPane(); root.setDefaultButton(okButton); - ActionListener disposer = new ActionListener() { - public void actionPerformed(ActionEvent actionEvent) { - setVisible(false); - } - }; + ActionListener disposer = actionEvent -> setVisible(false); Toolkit.registerWindowCloseKeys(root, disposer); Toolkit.setIcon(this); @@ -608,10 +494,10 @@ class CharacterSelector extends JFrame { charset[i] = (char) i; } } else { - DefaultListModel model = (DefaultListModel) charsetList.getModel(); + ListModel model = charsetList.getModel(); int index = 0; for (int i = 0; i < BLOCKS.length; i++) { - if (((JCheckBox) model.get(i)).isSelected()) { + if (model.getElementAt(i).isSelected()) { for (int j = blockStart[i]; j <= blockStop[i]; j++) { charset[index++] = (char) j; } @@ -792,8 +678,6 @@ class CharacterSelector extends JFrame { blockStop[i] = PApplet.unhex(line.substring(6, 10)); blockNames[i] = line.substring(12); } -// PApplet.println(codePointStop); -// PApplet.println(codePoints); } } @@ -827,14 +711,12 @@ class CheckBoxList extends JList { } - protected class CellRenderer implements ListCellRenderer { - public Component getListCellRendererComponent(JList list, Object value, + protected class CellRenderer implements ListCellRenderer { + public Component getListCellRendererComponent(JList list, JCheckBox checkbox, int index, boolean isSelected, boolean cellHasFocus) { - JCheckBox checkbox = (JCheckBox) value; checkbox.setBackground(isSelected ? getSelectionBackground() : getBackground()); checkbox.setForeground(isSelected ? getSelectionForeground() : getForeground()); - //checkbox.setEnabled(isEnabled()); checkbox.setEnabled(list.isEnabled()); checkbox.setFont(getFont()); checkbox.setFocusPainted(false); diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index 9e4ce8cb8..9bd34b43c 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -47,7 +47,6 @@ import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Font; import java.awt.Frame; -import java.awt.Image; import java.awt.Point; import java.awt.datatransfer.*; import java.awt.event.*; @@ -147,7 +146,7 @@ public abstract class Editor extends JFrame implements RunnerListener { // groups multi-character inputs into a single undos. private CompoundEdit compoundEdit; // timer to decide when to group characters into an undo - private Timer timer; + private final Timer timer; private TimerTask endUndoEvent; // true if inserting text, false if removing text private boolean isInserting; @@ -156,14 +155,14 @@ public abstract class Editor extends JFrame implements RunnerListener { JMenu toolsMenu; JMenu modePopup; - Image backgroundGradient; + //Image backgroundGradient; protected List problems = Collections.emptyList(); protected Editor(final Base base, String path, final EditorState state, final Mode mode) throws EditorException { - super("Processing", state.checkConfig()); + super("Processing", state.getConfig()); this.base = base; this.state = state; this.mode = mode; @@ -280,11 +279,7 @@ public abstract class Editor extends JFrame implements RunnerListener { } }); } - textarea.addCaretListener(new CaretListener() { - public void caretUpdate(CaretEvent e) { - updateEditorStatus(); - } - }); + textarea.addCaretListener(e -> updateEditorStatus()); footer = createFooter(); @@ -468,8 +463,8 @@ public abstract class Editor extends JFrame implements RunnerListener { if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { List list = (List) transferable.getTransferData(DataFlavor.javaFileListFlavor); - for (int i = 0; i < list.size(); i++) { - File file = (File) list.get(i); + for (Object o : list) { + File file = (File) o; if (sketch.addFile(file)) { successful++; } @@ -479,17 +474,21 @@ public abstract class Editor extends JFrame implements RunnerListener { // this method of moving files. String data = (String)transferable.getTransferData(uriListFlavor); String[] pieces = PApplet.splitTokens(data, "\r\n"); - for (int i = 0; i < pieces.length; i++) { - if (pieces[i].startsWith("#")) continue; + for (String piece : pieces) { + if (piece.startsWith("#")) continue; String path = null; - if (pieces[i].startsWith("file:///")) { - path = pieces[i].substring(7); - } else if (pieces[i].startsWith("file:/")) { - path = pieces[i].substring(5); + if (piece.startsWith("file:///")) { + path = piece.substring(7); + } else if (piece.startsWith("file:/")) { + path = piece.substring(5); } - if (sketch.addFile(new File(path))) { - successful++; + if (path != null) { + if (sketch.addFile(new File(path))) { + successful++; + } + } else { + System.err.println("Path not found for: " + data); } } } @@ -529,19 +528,26 @@ public abstract class Editor extends JFrame implements RunnerListener { ButtonGroup modeGroup = new ButtonGroup(); for (final Mode m : base.getModeList()) { JRadioButtonMenuItem item = new JRadioButtonMenuItem(m.getTitle()); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (!sketch.isModified()) { - if (!base.changeMode(m)) { - reselectMode(); - Messages.showWarning(Language.text("warn.cannot_change_mode.title"), - Language.interpolate("warn.cannot_change_mode.body", m)); - } - } else { + item.addActionListener(e -> { + /* + if (!sketch.isModified()) { + if (!base.changeMode(m)) { reselectMode(); - Messages.showWarning("Save", - "Please save the sketch before changing the mode."); + Messages.showWarning(Language.text("warn.cannot_change_mode.title"), + Language.interpolate("warn.cannot_change_mode.body", m)); } + } else { + reselectMode(); + Messages.showWarning("Save", + "Please save the sketch before changing the mode."); + } + */ + //if (sketch.isModified() || !base.changeMode(m)) { + if (!base.changeMode(m)) { + // Returns false if unable to change the mode in this window + // (which will open a new window with the new Mode), in which case + // re-select the menu item b/c Java changes it automatically. + reselectMode(); } }); modePopup.add(item); @@ -553,11 +559,7 @@ public abstract class Editor extends JFrame implements RunnerListener { modePopup.addSeparator(); JMenuItem addLib = new JMenuItem(Language.text("toolbar.add_mode")); - addLib.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - ContributionManager.openModes(); - } - }); + addLib.addActionListener(e -> ContributionManager.openModes()); modePopup.add(addLib); Toolkit.setMenuMnemsInside(modePopup); @@ -569,7 +571,7 @@ public abstract class Editor extends JFrame implements RunnerListener { private void reselectMode() { for (Component c : getModePopup().getComponents()) { if (c instanceof JRadioButtonMenuItem) { - if (((JRadioButtonMenuItem)c).getText() == mode.getTitle()) { + if (((JRadioButtonMenuItem) c).getText().equals(mode.getTitle())) { ((JRadioButtonMenuItem)c).setSelected(true); break; } @@ -663,7 +665,7 @@ public abstract class Editor extends JFrame implements RunnerListener { * the app is just starting up, or the user just finished messing * with things in the Preferences window. */ - protected void applyPreferences() { + public void applyPreferences() { // Update fonts and other items controllable from the prefs textarea.getPainter().updateAppearance(); textarea.repaint(); @@ -752,63 +754,34 @@ public abstract class Editor extends JFrame implements RunnerListener { JMenu fileMenu = new JMenu(Language.text("menu.file")); item = Toolkit.newJMenuItem(Language.text("menu.file.new"), 'N'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - base.handleNew(); - } - }); + item.addActionListener(e -> base.handleNew()); fileMenu.add(item); item = Toolkit.newJMenuItem(Language.text("menu.file.open"), 'O'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - base.handleOpenPrompt(); - } - }); + item.addActionListener(e -> base.handleOpenPrompt()); fileMenu.add(item); // fileMenu.add(base.getSketchbookMenu()); item = Toolkit.newJMenuItemShift(Language.text("menu.file.sketchbook"), 'K'); - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - mode.showSketchbookFrame(); - } - }); + item.addActionListener(e -> mode.showSketchbookFrame()); fileMenu.add(item); item = Toolkit.newJMenuItemShift(Language.text("menu.file.examples"), 'O'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - mode.showExamplesFrame(); - } - }); + item.addActionListener(e -> mode.showExamplesFrame()); fileMenu.add(item); item = Toolkit.newJMenuItem(Language.text("menu.file.close"), 'W'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - base.handleClose(Editor.this, false); - } - }); + item.addActionListener(e -> base.handleClose(Editor.this, false)); fileMenu.add(item); item = Toolkit.newJMenuItem(Language.text("menu.file.save"), 'S'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleSave(false); - } - }); + item.addActionListener(e -> handleSave(false)); // saveMenuItem = item; fileMenu.add(item); item = Toolkit.newJMenuItemShift(Language.text("menu.file.save_as"), 'S'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleSaveAs(); - } - }); + item.addActionListener(e -> handleSaveAs()); // saveAsMenuItem = item; fileMenu.add(item); @@ -820,19 +793,11 @@ public abstract class Editor extends JFrame implements RunnerListener { fileMenu.addSeparator(); item = Toolkit.newJMenuItemShift(Language.text("menu.file.page_setup"), 'P'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handlePageSetup(); - } - }); + item.addActionListener(e -> handlePageSetup()); fileMenu.add(item); item = Toolkit.newJMenuItem(Language.text("menu.file.print"), 'P'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handlePrint(); - } - }); + item.addActionListener(e -> handlePrint()); fileMenu.add(item); // Mac OS X already has its own preferences and quit menu. @@ -841,21 +806,13 @@ public abstract class Editor extends JFrame implements RunnerListener { fileMenu.addSeparator(); item = Toolkit.newJMenuItem(Language.text("menu.file.preferences"), ','); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - base.handlePrefs(); - } - }); + item.addActionListener(e -> base.handlePrefs()); fileMenu.add(item); fileMenu.addSeparator(); item = Toolkit.newJMenuItem(Language.text("menu.file.quit"), 'Q'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - base.handleQuit(); - } - }); + item.addActionListener(e -> base.handleQuit()); fileMenu.add(item); } return fileMenu; @@ -902,11 +859,7 @@ public abstract class Editor extends JFrame implements RunnerListener { menu.add(item); item = Toolkit.newJMenuItem(Language.text("menu.edit.select_all"), 'A'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - textarea.selectAll(); - } - }); + item.addActionListener(e -> textarea.selectAll()); menu.add(item); /* @@ -942,54 +895,36 @@ public abstract class Editor extends JFrame implements RunnerListener { menu.addSeparator(); item = Toolkit.newJMenuItem(Language.text("menu.edit.auto_format"), 'T'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleAutoFormat(); - } - }); + item.addActionListener(e -> handleAutoFormat()); menu.add(item); item = Toolkit.newJMenuItemExt("menu.edit.comment_uncomment"); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleCommentUncomment(); - } - }); + item.addActionListener(e -> handleCommentUncomment()); menu.add(item); item = Toolkit.newJMenuItemExt("menu.edit.increase_indent"); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleIndentOutdent(true); - } - }); + item.addActionListener(e -> handleIndentOutdent(true)); menu.add(item); item = Toolkit.newJMenuItemExt("menu.edit.decrease_indent"); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleIndentOutdent(false); - } - }); + item.addActionListener(e -> handleIndentOutdent(false)); menu.add(item); menu.addSeparator(); item = Toolkit.newJMenuItem(Language.text("menu.edit.find"), 'F'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (find == null) { - find = new FindReplace(Editor.this); - } - // https://github.com/processing/processing/issues/3457 - String selection = getSelectedText(); - if (selection != null && selection.length() != 0 && - !selection.contains("\n")) { - find.setFindText(selection); - } - find.setVisible(true); - } - }); + item.addActionListener(e -> { + if (find == null) { + find = new FindReplace(Editor.this); + } + // https://github.com/processing/processing/issues/3457 + String selection = getSelectedText(); + if (selection != null && selection.length() != 0 && + !selection.contains("\n")) { + find.setFindText(selection); + } + find.setVisible(true); + }); menu.add(item); UpdatableAction action; @@ -1049,20 +984,12 @@ public abstract class Editor extends JFrame implements RunnerListener { sketchMenu.add(mode.getImportMenu()); item = Toolkit.newJMenuItem(Language.text("menu.sketch.show_sketch_folder"), 'K'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - Platform.openFolder(sketch.getFolder()); - } - }); + item.addActionListener(e -> Platform.openFolder(sketch.getFolder())); sketchMenu.add(item); item.setEnabled(Platform.openFolderAvailable()); item = new JMenuItem(Language.text("menu.sketch.add_file")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - sketch.handleAddFile(); - } - }); + item.addActionListener(e -> sketch.handleAddFile()); sketchMenu.add(item); if (runItems != null && runItems.length != 0) { @@ -1071,11 +998,11 @@ public abstract class Editor extends JFrame implements RunnerListener { sketchMenu.addMenuListener(new MenuListener() { // Menu Listener that populates the menu only when the menu is opened - Map itemMap = new HashMap<>(); + final Map itemMap = new HashMap<>(); @Override public void menuSelected(MenuEvent event) { - Set unseen = new HashSet(itemMap.values()); + Set unseen = new HashSet<>(itemMap.values()); for (final Editor editor : base.getEditors()) { Sketch sketch = editor.getSketch(); @@ -1099,13 +1026,10 @@ public abstract class Editor extends JFrame implements RunnerListener { item.setText(name); // Action listener to bring the appropriate sketch in front - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - editor.setState(Frame.NORMAL); - editor.setVisible(true); - editor.toFront(); - } + item.addActionListener(e -> { + editor.setState(Frame.NORMAL); + editor.setVisible(true); + editor.toFront(); }); // Disabling for now, might be problematic [fry 200117] @@ -1288,8 +1212,6 @@ public abstract class Editor extends JFrame implements RunnerListener { /** * Given the .html file, displays it in the default browser. - * - * @param file */ public void showReferenceFile(File file) { try { @@ -1317,7 +1239,7 @@ public abstract class Editor extends JFrame implements RunnerListener { * Subclass if you want to have setEnabled(canDo()); called when your menu * is opened. */ - abstract class UpdatableAction extends AbstractAction { + static abstract class UpdatableAction extends AbstractAction { public UpdatableAction(String name) { super(name); } @@ -1873,27 +1795,24 @@ public abstract class Editor extends JFrame implements RunnerListener { }); // connect the undo listener to the editor - document.addUndoableEditListener(new UndoableEditListener() { + document.addUndoableEditListener(e -> { + // if an edit is in progress, reset the timer + if (endUndoEvent != null) { + endUndoEvent.cancel(); + endUndoEvent = null; + startTimerEvent(); + } - public void undoableEditHappened(UndoableEditEvent e) { - // if an edit is in progress, reset the timer - if (endUndoEvent != null) { - endUndoEvent.cancel(); - endUndoEvent = null; - startTimerEvent(); - } + // if this edit is just getting started, create a compound edit + if (compoundEdit == null) { + startCompoundEdit(); + startTimerEvent(); + } - // if this edit is just getting started, create a compound edit - if (compoundEdit == null) { - startCompoundEdit(); - startTimerEvent(); - } - - compoundEdit.addEdit(e.getEdit()); - undoAction.updateUndoState(); - redoAction.updateRedoState(); - } - }); + compoundEdit.addEdit(e.getEdit()); + undoAction.updateUndoState(); + redoAction.updateRedoState(); + }); } // update the document object that's in use @@ -2312,7 +2231,6 @@ public abstract class Editor extends JFrame implements RunnerListener { /** * Check the current selection for reference. If no selection is active, * expand the current selection. - * @return */ protected String referenceCheck(boolean selectIfFound) { int start = textarea.getSelectionStart(); @@ -2509,8 +2427,7 @@ public abstract class Editor extends JFrame implements RunnerListener { // on macosx, setting the destructive property places this option // away from the others at the lefthand side - pane.putClientProperty("Quaqua.OptionPane.destructiveOption", - Integer.valueOf(2)); + pane.putClientProperty("Quaqua.OptionPane.destructiveOption", 2); JDialog dialog = pane.createDialog(this, null); dialog.setVisible(true); @@ -2665,11 +2582,7 @@ public abstract class Editor extends JFrame implements RunnerListener { handleSaveImpl(); } else { - EventQueue.invokeLater(new Runnable() { - public void run() { - handleSaveImpl(); - } - }); + EventQueue.invokeLater(this::handleSaveImpl); } return true; } @@ -2852,8 +2765,7 @@ public abstract class Editor extends JFrame implements RunnerListener { if (sc.getDocument() != null) { try { sc.setProgram(sc.getDocumentText()); - } catch (BadLocationException e) { - } + } catch (BadLocationException ignored) { } } } @@ -3241,79 +3153,43 @@ public abstract class Editor extends JFrame implements RunnerListener { JMenuItem item; cutItem = new JMenuItem(Language.text("menu.edit.cut")); - cutItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleCut(); - } - }); + cutItem.addActionListener(e -> handleCut()); this.add(cutItem); copyItem = new JMenuItem(Language.text("menu.edit.copy")); - copyItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleCopy(); - } - }); + copyItem.addActionListener(e -> handleCopy()); this.add(copyItem); discourseItem = new JMenuItem(Language.text("menu.edit.copy_as_html")); - discourseItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleCopyAsHTML(); - } - }); + discourseItem.addActionListener(e -> handleCopyAsHTML()); this.add(discourseItem); pasteItem = new JMenuItem(Language.text("menu.edit.paste")); - pasteItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handlePaste(); - } - }); + pasteItem.addActionListener(e -> handlePaste()); this.add(pasteItem); item = new JMenuItem(Language.text("menu.edit.select_all")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleSelectAll(); - } - }); + item.addActionListener(e -> handleSelectAll()); this.add(item); this.addSeparator(); item = new JMenuItem(Language.text("menu.edit.comment_uncomment")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleCommentUncomment(); - } - }); + item.addActionListener(e -> handleCommentUncomment()); this.add(item); item = new JMenuItem("\u2192 " + Language.text("menu.edit.increase_indent")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleIndentOutdent(true); - } - }); + item.addActionListener(e -> handleIndentOutdent(true)); this.add(item); item = new JMenuItem("\u2190 " + Language.text("menu.edit.decrease_indent")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleIndentOutdent(false); - } - }); + item.addActionListener(e -> handleIndentOutdent(false)); this.add(item); this.addSeparator(); referenceItem = new JMenuItem(Language.text("find_in_reference")); - referenceItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleFindReference(); - } - }); + referenceItem.addActionListener(e -> handleFindReference()); this.add(referenceItem); Toolkit.setMenuMnemonics(this); diff --git a/app/src/processing/app/ui/EditorButton.java b/app/src/processing/app/ui/EditorButton.java index 538a3ddc0..0141210a2 100644 --- a/app/src/processing/app/ui/EditorButton.java +++ b/app/src/processing/app/ui/EditorButton.java @@ -146,8 +146,9 @@ implements MouseListener, MouseMotionListener, ActionListener { // It looks like ActionEvent expects old-style modifiers, // so the e.getModifiers() call here may be correct. - // TODO Look into how this is getting used in Modes, - // and either update or add ignore to the deprecation [fry 191008] + // TODO Look into how this is getting used in Modes, and either + // update or add ignore to the deprecation [fry 191008] + // https://github.com/processing/processing4/issues/67 actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, null, e.getModifiers())); } diff --git a/app/src/processing/app/ui/EditorConsole.java b/app/src/processing/app/ui/EditorConsole.java index ecdd6e897..105fc05ea 100644 --- a/app/src/processing/app/ui/EditorConsole.java +++ b/app/src/processing/app/ui/EditorConsole.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-19 The Processing Foundation + Copyright (c) 2012-21 The Processing Foundation Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology @@ -28,8 +28,6 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.io.OutputStream; import java.io.PrintStream; import java.util.concurrent.LinkedBlockingQueue; @@ -107,11 +105,7 @@ public class EditorConsole extends JScrollPane { if (flushTimer == null) { // periodically post buffered messages to the console // should the interval come from the preferences file? - flushTimer = new Timer(250, new ActionListener() { - public void actionPerformed(ActionEvent evt) { - flush(); - } - }); + flushTimer = new Timer(250, evt -> flush()); flushTimer.start(); } } @@ -140,12 +134,13 @@ public class EditorConsole extends JScrollPane { * Update the font family and sizes based on the Preferences window. */ protected void updateAppearance() { - String fontFamily = Preferences.get("editor.font.family"); - int fontSize = Toolkit.zoom(Preferences.getInteger("console.font.size")); - StyleConstants.setFontFamily(stdStyle, fontFamily); - StyleConstants.setFontSize(stdStyle, fontSize); - StyleConstants.setFontFamily(errStyle, fontFamily); - StyleConstants.setFontSize(errStyle, fontSize); + Font font = Preferences.getFont("editor.font.family", "console.font.size", Font.PLAIN); + + StyleConstants.setFontFamily(stdStyle, font.getFamily()); + StyleConstants.setFontSize(stdStyle, font.getSize()); + StyleConstants.setFontFamily(errStyle, font.getFamily()); + StyleConstants.setFontSize(errStyle, font.getSize()); + clear(); // otherwise we'll have mixed fonts } @@ -161,8 +156,6 @@ public class EditorConsole extends JScrollPane { StyleConstants.setAlignment(standard, StyleConstants.ALIGN_LEFT); consoleDoc.setParagraphAttributes(0, 0, standard, true); - Font font = Preferences.getFont("console.font"); - // build styles for different types of console output Color bgColor = mode.getColor("console.color"); Color fgColorOut = mode.getColor("console.output.color"); @@ -172,6 +165,8 @@ public class EditorConsole extends JScrollPane { // setBorder(null) should be called instead. The defaults are nasty. setBorder(new MatteBorder(0, Editor.LEFT_GUTTER, 0, 0, bgColor)); + Font font = Preferences.getFont("editor.font.family", "console.font.size", Font.PLAIN); + stdStyle = new SimpleAttributeSet(); StyleConstants.setForeground(stdStyle, fgColorOut); StyleConstants.setBackground(stdStyle, bgColor); @@ -223,27 +218,51 @@ public class EditorConsole extends JScrollPane { } - public void message(String what, boolean err) { + /** + * Check whether this console message should be suppressed, usually + * to avoid user confusion with irrelevant debugging messages. + * @param what Text of the error message + * @param err true if stderr, false if stdout + * @return true if the message can be ignored/skipped + */ + private boolean suppressMessage(String what, boolean err) { if (err && (what.contains("invalid context 0x0") || (what.contains("invalid drawable")))) { // Respectfully declining... This is a quirk of more recent releases of // Java on Mac OS X, but is widely reported as the source of any other // bug or problem that a user runs into. It may well be a Processing // bug, but until we know, we're suppressing the messages. + return true; + } else if (err && what.contains("is calling TIS/TSM in non-main thread environment")) { // Error message caused by JOGL since macOS 10.13.4, cannot fix at the moment so silencing it: // https://github.com/processing/processing/issues/5462 // Some discussion on the Apple's developer forums seems to suggest that is not serious: // https://forums.developer.apple.com/thread/105244 + return true; + } else if (err && what.contains("NSWindow drag regions should only be invalidated on the Main Thread")) { // Keep hiding warnings triggered by JOGL on recent macOS versions (this is from 10.14 onwards I think). + return true; + } else if (err && what.contains("Make pbuffer:")) { - // Remove initalization warning from LWJGL. + // Remove initialization warning from LWJGL. + return true; + } else if (err && what.contains("XInitThreads() called for concurrent")) { // "Info: XInitThreads() called for concurrent Thread support" message on Linux + return true; + } else if (!err && what.contains("Listening for transport dt_socket at address")) { // Message from the JVM about the socket launch for debug // Listening for transport dt_socket at address: 8727 - } else { + return true; + } + return false; + } + + + public void message(String what, boolean err) { + if (!suppressMessage(what, err)) { // Append a piece of text to the console. Swing components are NOT // thread-safe, and since the MessageSiphon instantiates new threads, // and in those callbacks, they often print output to stdout and stderr, @@ -277,7 +296,7 @@ public class EditorConsole extends JScrollPane { this.err = err; } - public void write(byte b[], int offset, int length) { + public void write(byte[] b, int offset, int length) { message(new String(b, offset, length), err); } @@ -367,42 +386,9 @@ class BufferedStyledDocument extends DefaultStyledDocument { /** insert the buffered strings */ public void insertAll() { - /* - // each line is ~3 elements - int tooMany = elements.size() - maxLineCount*3; - if (tooMany > 0) { - try { - remove(0, getLength()); // clear the document first - } catch (BadLocationException ble) { - ble.printStackTrace(); - } - Console.systemOut("skipping " + elements.size()); - for (int i = 0; i < tooMany; i++) { - elements.remove(); - } - } - */ ElementSpec[] elementArray = elements.toArray(new ElementSpec[0]); try { - /* - // check how many lines have been used so far - // if too many, shave off a few lines from the beginning - Element element = super.getDefaultRootElement(); - int lineCount = element.getElementCount(); - int overage = lineCount - maxLineCount; - if (overage > 0) { - // if 1200 lines, and 1000 lines is max, - // find the position of the end of the 200th line - //systemOut.println("overage is " + overage); - Element lineElement = element.getElement(overage); - if (lineElement == null) return; // do nuthin - - int endOffset = lineElement.getEndOffset(); - // remove to the end of the 200th line - super.remove(0, endOffset); - } - */ synchronized (insertLock) { checkLength(); insert(getLength(), elementArray); diff --git a/app/src/processing/app/ui/EditorFooter.java b/app/src/processing/app/ui/EditorFooter.java index deb0da718..7d9258c31 100644 --- a/app/src/processing/app/ui/EditorFooter.java +++ b/app/src/processing/app/ui/EditorFooter.java @@ -289,8 +289,8 @@ public class EditorFooter extends Box { FontRenderContext frc = g2.getFontRenderContext(); final int GAP = Toolkit.zoom(5); final String updateLabel = "Updates"; - //String updatesStr = " " + updateCount + " "; - String updatesStr = " " + ((int) (Math.random() * 25)) + " "; + // String updatesStr = " " + ((int) (Math.random() * 25)) + " "; // testing + String updatesStr = " " + updateCount + " "; double countWidth = font.getStringBounds(updatesStr, frc).getWidth(); double countHeight = font.getStringBounds(updatesStr, frc).getHeight(); if (fontAscent > countWidth) { diff --git a/app/src/processing/app/ui/EditorHeader.java b/app/src/processing/app/ui/EditorHeader.java index 50fa1b8bf..e64990a54 100644 --- a/app/src/processing/app/ui/EditorHeader.java +++ b/app/src/processing/app/ui/EditorHeader.java @@ -63,8 +63,8 @@ public class EditorHeader extends JComponent { // (total tab width will be this plus TEXT_MARGIN*2) static final int NO_TEXT_WIDTH = Toolkit.zoom(16); - Color textColor[] = new Color[2]; - Color tabColor[] = new Color[2]; + Color[] textColor = new Color[2]; + Color[] tabColor = new Color[2]; Color modifiedColor; Color arrowColor; @@ -100,44 +100,44 @@ public class EditorHeader extends JComponent { updateMode(); addMouseListener(new MouseAdapter() { - public void mousePressed(MouseEvent e) { - int x = e.getX(); - int y = e.getY(); + public void mousePressed(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); - if ((x > menuLeft) && (x < menuRight)) { - popup.show(EditorHeader.this, x, y); - } else { - Sketch sketch = editor.getSketch(); - for (Tab tab : tabs) { - if (tab.contains(x)) { - sketch.setCurrentCode(tab.index); - repaint(); - } + if ((x > menuLeft) && (x < menuRight)) { + popup.show(EditorHeader.this, x, y); + } else { + Sketch sketch = editor.getSketch(); + for (Tab tab : tabs) { + if (tab.contains(x)) { + sketch.setCurrentCode(tab.index); + repaint(); } } } + } - public void mouseExited(MouseEvent e) { - // only clear if it's been set - if (lastNoticeName != null) { - // only clear if it's the same as what we set it to - editor.clearNotice(lastNoticeName); - lastNoticeName = null; - } + public void mouseExited(MouseEvent e) { + // only clear if it's been set + if (lastNoticeName != null) { + // only clear if it's the same as what we set it to + editor.clearNotice(lastNoticeName); + lastNoticeName = null; } + } }); addMouseMotionListener(new MouseMotionAdapter() { public void mouseMoved(MouseEvent e) { - int x = e.getX(); - for (Tab tab : tabs) { - if (tab.contains(x) && !tab.textVisible) { - lastNoticeName = editor.getSketch().getCode(tab.index).getPrettyName(); - editor.statusNotice(lastNoticeName); - } - } + int x = e.getX(); + for (Tab tab : tabs) { + if (tab.contains(x) && !tab.textVisible) { + lastNoticeName = editor.getSketch().getCode(tab.index).getPrettyName(); + editor.statusNotice(lastNoticeName); } - }); + } + } + }); } @@ -238,8 +238,8 @@ public class EditorHeader extends JComponent { Arrays.sort(visitOrder); // sort on when visited // Keep shrinking the tabs one-by-one until things fit properly - for (int i = 0; i < visitOrder.length; i++) { - tabs[visitOrder[i].index].textVisible = false; + for (Tab tab : visitOrder) { + tabs[tab.index].textVisible = false; if (placeTabs(Editor.LEFT_GUTTER, tabMax, null)) { break; } @@ -526,11 +526,8 @@ public class EditorHeader extends JComponent { if (sketch != null) { menu.addSeparator(); - ActionListener jumpListener = new ActionListener() { - public void actionPerformed(ActionEvent e) { - editor.getSketch().setCurrentCode(e.getActionCommand()); - } - }; + ActionListener jumpListener = + e -> editor.getSketch().setCurrentCode(e.getActionCommand()); for (SketchCode code : sketch.getCode()) { item = new JMenuItem(code.getPrettyName()); item.addActionListener(jumpListener); @@ -542,9 +539,11 @@ public class EditorHeader extends JComponent { } + /* public void deselectMenu() { repaint(); } + */ public Dimension getPreferredSize() { @@ -565,7 +564,7 @@ public class EditorHeader extends JComponent { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - static class Tab implements Comparable { + static class Tab implements Comparable { int index; int left; int right; @@ -583,8 +582,7 @@ public class EditorHeader extends JComponent { } // sort by the last time visited - public int compareTo(Object o) { - Tab other = (Tab) o; + public int compareTo(Tab other) { // do this here to deal with situation where both are 0 if (lastVisited == other.lastVisited) { return 0; diff --git a/app/src/processing/app/ui/EditorState.java b/app/src/processing/app/ui/EditorState.java index b560bf8ed..d9960346d 100644 --- a/app/src/processing/app/ui/EditorState.java +++ b/app/src/processing/app/ui/EditorState.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-19 The Processing Foundation + Copyright (c) 2012-21 The Processing Foundation Copyright (c) 2011-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify @@ -27,80 +27,73 @@ import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Rectangle; -import java.io.*; import java.util.List; import processing.app.Preferences; -import processing.core.PApplet; - -//import processing.core.PApplet; - - -// scenarios: -// 1) new untitled sketch (needs device, needs bounds) -// 2) restoring sketch from recent menu -// - device cannot be found -// - device is found but it's a different size -// - device is found and size is correct -// 3) re-opening sketch in a new mode +/** + * Stores state information (location, size, display device) for an Editor window. + * Originally was used to restore sketch windows on startup, though that was removed + * at some point (3.0?) because it was unreliable and not always appreciated. + * + * This version is primarily just used to get the location (and display) to open + * a new Editor window, though some of the vestigial bits are still in there in case + * we want to restore the ability to restore windows on startup. + * + * Previous scenarios: + *
    + *
  • new untitled sketch (needs device, needs bounds)
  • + *
  • restoring sketch from recent menu + *
      + *
    • device is found and size matches
    • + *
    • device cannot be found
    • + *
    • device is found but its size has changed
    • + *
    + *
  • + *
  • re-opening sketch in a new mode
  • + *
+ */ public class EditorState { - // path to the main .pde file for the sketch -// String path; - // placement of the window -// int windowX, windowY, windowW, windowH; Rectangle editorBounds; int dividerLocation; - // width/height of the screen on which this window was placed -// int displayW, displayH; -// String deviceName; // not really useful b/c it's more about bounds anyway - Rectangle deviceBounds; boolean isMaximized; + GraphicsConfiguration deviceConfig; - // how far to offset a new window from the previous window + /** How far to offset a new window from the previous window */ static final int WINDOW_OFFSET = 28; + /** + * Keep a reference to the last device config so we know which display to + * use when creating a new window after all windows have been closed. + */ + static GraphicsConfiguration lastConfig; + /** * Create a fresh editor state object from the default screen device and * set its placement relative to the last opened window. * @param editors List of active editor objects */ - public EditorState(List editors) { - defaultConfig(); - defaultLocation(editors); - } + static public EditorState nextEditor(List editors) { + Editor lastEditor = null; + int editorCount = editors.size(); + if (editorCount > 0) { + lastEditor = editors.get(editorCount-1); + } + // update lastConfig so it can be set for this Editor and + // for the next Editor created if the last window is closed. + if (lastEditor != null) { + lastConfig = lastEditor.getGraphicsConfiguration(); + } + if (lastConfig == null) { + lastConfig = getDefaultConfig(); + } -// EditorState(BufferedReader reader) throws IOException { -// String line = reader.readLine(); -// EditorState(String[] pieces) throws IOException { - EditorState(String info) throws IOException { -// String line = reader.readLine(); -// String[] pieces = PApplet.split(line, '\t'); - String[] pieces = PApplet.split(info, ','); -// path = pieces[0]; - - editorBounds = new Rectangle(Integer.parseInt(pieces[0]), - Integer.parseInt(pieces[1]), - Integer.parseInt(pieces[2]), - Integer.parseInt(pieces[3])); - - dividerLocation = Integer.parseInt(pieces[4]); - - deviceBounds = new Rectangle(Integer.parseInt(pieces[5]), - Integer.parseInt(pieces[6]), - Integer.parseInt(pieces[7]), - Integer.parseInt(pieces[8])); - -// windowX = Integer.parseInt(pieces[1]); -// windowY = Integer.parseInt(pieces[2]); -// windowW = Integer.parseInt(pieces[3]); -// windowH = Integer.parseInt(pieces[4]); - -// displayW = Integer.parseInt(pieces[5]); -// displayH = Integer.parseInt(pieces[6]); + EditorState outgoing = new EditorState(); + outgoing.initLocation(lastConfig, lastEditor); + return outgoing; } @@ -110,55 +103,31 @@ public class EditorState { editorBounds.width + "," + editorBounds.height + "," + dividerLocation + "," + - deviceBounds.x + "," + - deviceBounds.y + "," + - deviceBounds.width + "," + - deviceBounds.height); + deviceConfig); } - /** - * Returns a GraphicsConfiguration so that a new Editor Frame can be - * constructed. First tries to match the bounds for this state information - * to an existing config (nominally, a display) and if that doesn't work, - * then returns the default configuration/default display. - */ - GraphicsConfiguration checkConfig() { - if (deviceBounds != null) { - GraphicsEnvironment graphicsEnvironment = - GraphicsEnvironment.getLocalGraphicsEnvironment(); - GraphicsDevice[] screenDevices = graphicsEnvironment.getScreenDevices(); - for (GraphicsDevice device : screenDevices) { - GraphicsConfiguration[] configurations = device.getConfigurations(); - for (GraphicsConfiguration config : configurations) { -// if (config.getDevice().getIDstring().equals(deviceName)) { - if (config.getBounds().equals(deviceBounds)) { - return config; - } - } - } - } - // otherwise go to the default config - return defaultConfig(); + public GraphicsConfiguration getConfig() { + return deviceConfig; } - GraphicsConfiguration defaultConfig() { + static GraphicsConfiguration getDefaultConfig() { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice device = ge.getDefaultScreenDevice(); - GraphicsConfiguration config = device.getDefaultConfiguration(); -// deviceName = device.getIDstring(); - deviceBounds = config.getBounds(); - return config; + return device.getDefaultConfiguration(); } /** * Figure out the next location by sizing up the last editor in the list. * If no editors are opened, it'll just open on the main screen. - * @param editors List of editors currently opened + * @param lastOpened The last Editor opened, used to determine display and location */ - void defaultLocation(List editors) { + void initLocation(GraphicsConfiguration lastConfig, Editor lastOpened) { + deviceConfig = lastConfig; + Rectangle deviceBounds = deviceConfig.getBounds(); + int defaultWidth = Toolkit.zoom(Preferences.getInteger("editor.window.width.default")); int defaultHeight = @@ -166,9 +135,8 @@ public class EditorState { defaultWidth = Math.min(defaultWidth, deviceBounds.width); defaultHeight = Math.min(defaultHeight, deviceBounds.height); - //System.out.println("default w/h = " + defaultWidth + "/" + defaultHeight); - if (editors.size() == 0) { + if (lastOpened == null) { // If no current active editor, use default placement. // Center the window on ths screen, taking into account that the // upper-left corner of the device may have a non (0, 0) origin. @@ -183,41 +151,30 @@ public class EditorState { } else { // With a currently active editor, open the new window using the same // dimensions and divider location, but offset slightly. - synchronized (editors) { - Editor lastOpened = editors.get(editors.size() - 1); - isMaximized = (lastOpened.getExtendedState() == Frame.MAXIMIZED_BOTH); - editorBounds = lastOpened.getBounds(); - editorBounds.x += WINDOW_OFFSET; - editorBounds.y += WINDOW_OFFSET; - dividerLocation = lastOpened.getDividerLocation(); + //GraphicsDevice device = lastOpened.getGraphicsConfiguration().getDevice(); + //System.out.println("last opened device is " + device); - if (!deviceBounds.contains(editorBounds)) { - // Warp the next window to a randomish location on screen. - editorBounds.x = deviceBounds.x + - (int) (Math.random() * (deviceBounds.width - defaultWidth)); - editorBounds.y = deviceBounds.y + - (int) (Math.random() * (deviceBounds.height - defaultHeight)); - } - if (isMaximized) { - editorBounds.width = defaultWidth; - editorBounds.height = defaultHeight; - } + isMaximized = (lastOpened.getExtendedState() == Frame.MAXIMIZED_BOTH); + editorBounds = lastOpened.getBounds(); + editorBounds.x += WINDOW_OFFSET; + editorBounds.y += WINDOW_OFFSET; + dividerLocation = lastOpened.getDividerLocation(); + + if (!deviceBounds.contains(editorBounds)) { + // Warp the next window to a random-ish location on screen. + editorBounds.x = deviceBounds.x + + (int) (Math.random() * (deviceBounds.width - defaultWidth)); + editorBounds.y = deviceBounds.y + + (int) (Math.random() * (deviceBounds.height - defaultHeight)); + } + if (isMaximized) { + editorBounds.width = defaultWidth; + editorBounds.height = defaultHeight; } } } - void update(Editor editor) { -// path = editor.getSketch().getMainFilePath(); - editorBounds = editor.getBounds(); - dividerLocation = editor.getDividerLocation(); - GraphicsConfiguration config = editor.getGraphicsConfiguration(); -// GraphicsDevice device = config.getDevice(); - deviceBounds = config.getBounds(); -// deviceName = device.getIDstring(); - } - - void apply(Editor editor) { editor.setBounds(editorBounds); @@ -229,27 +186,9 @@ public class EditorState { if (isMaximized) { editor.setExtendedState(Frame.MAXIMIZED_BOTH); } + + // note: doesn't do anything with the device, though that could be + // added if it's something that would be necessary (i.e. to store windows and + // re-open them on when re-opening Processing) } - - -// void write(PrintWriter writer) { -//// writer.print(path); -// writer.print('\t'); -// writeRect(writer, editorBounds); -//// writer.print('\t'); -//// writer.print(deviceName); -// writer.print('\t'); -// writeRect(writer, deviceBounds); -// } -// -// -// void writeRect(PrintWriter writer, Rectangle rect) { -// writer.print(rect.x); -// writer.print('\t'); -// writer.print(rect.y); -// writer.print('\t'); -// writer.print(rect.width); -// writer.print('\t'); -// writer.print(rect.height); -// } } diff --git a/app/src/processing/app/ui/PreferencesFrame.java b/app/src/processing/app/ui/PreferencesFrame.java index c03879897..af9004266 100644 --- a/app/src/processing/app/ui/PreferencesFrame.java +++ b/app/src/processing/app/ui/PreferencesFrame.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-19 The Processing Foundation + Copyright (c) 2012-21 The Processing Foundation Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology @@ -77,6 +77,7 @@ public class PreferencesFrame { JComboBox languageSelectionBox; int displayCount; + int defaultDisplayNum; String[] monoFontFamilies; JComboBox fontSelectionBox; @@ -112,14 +113,12 @@ public class PreferencesFrame { sketchbookLocationField = new JTextField(40); browseButton = new JButton(Language.getPrompt("browse")); - browseButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - File dflt = new File(sketchbookLocationField.getText()); - ShimAWT.selectFolder(Language.text("preferences.sketchbook_location.popup"), - "sketchbookCallback", dflt, - PreferencesFrame.this); - } - }); + browseButton.addActionListener(e -> { + File defaultLocation = new File(sketchbookLocationField.getText()); + ShimAWT.selectFolder(Language.text("preferences.sketchbook_location.popup"), + "sketchbookCallback", defaultLocation, + PreferencesFrame.this); + }); // Language: [ English ] (requires restart of Processing) @@ -155,10 +154,11 @@ public class PreferencesFrame { JLabel fontSizelabel = new JLabel(Language.text("preferences.editor_font_size")+": "); fontSizeField = new JComboBox<>(FONT_SIZES); + fontSizeField.setSelectedItem(Preferences.getInteger("editor.font.size")); JLabel consoleFontSizeLabel = new JLabel(Language.text("preferences.console_font_size")+": "); consoleFontSizeField = new JComboBox<>(FONT_SIZES); - fontSizeField.setSelectedItem(Preferences.getFont("editor.font.size")); + consoleFontSizeField.setSelectedItem(Preferences.getInteger("console.font.size")); // Interface scale: [ 100% ] (requires restart of Processing) @@ -166,12 +166,7 @@ public class PreferencesFrame { JLabel zoomLabel = new JLabel(Language.text("preferences.zoom") + ": "); zoomAutoBox = new JCheckBox(Language.text("preferences.zoom.auto")); - zoomAutoBox.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - zoomSelectionBox.setEnabled(!zoomAutoBox.isSelected()); - } - }); + zoomAutoBox.addChangeListener(e -> zoomSelectionBox.setEnabled(!zoomAutoBox.isSelected())); zoomSelectionBox = new JComboBox<>(); zoomSelectionBox.setModel(new DefaultComboBoxModel<>(Toolkit.zoomOptions.array())); @@ -201,20 +196,12 @@ public class PreferencesFrame { public void removeUpdate(DocumentEvent e) { final String colorValue = presentColorHex.getText().toUpperCase(); if (colorValue.length() == 7 && (colorValue.startsWith("#"))) - EventQueue.invokeLater(new Runnable() { - public void run() { - presentColorHex.setText(colorValue.substring(1)); - } - }); + EventQueue.invokeLater(() -> presentColorHex.setText(colorValue.substring(1))); if (colorValue.length() == 6 && colorValue.matches("[0123456789ABCDEF]*")) { presentColor.setBackground(new Color(PApplet.unhex(colorValue))); if (!colorValue.equals(presentColorHex.getText())) - EventQueue.invokeLater(new Runnable() { - public void run() { - presentColorHex.setText(colorValue); - } - }); + EventQueue.invokeLater(() -> presentColorHex.setText(colorValue)); } } @@ -222,20 +209,12 @@ public class PreferencesFrame { public void insertUpdate(DocumentEvent e) { final String colorValue = presentColorHex.getText().toUpperCase(); if (colorValue.length() == 7 && (colorValue.startsWith("#"))) - EventQueue.invokeLater(new Runnable() { - public void run() { - presentColorHex.setText(colorValue.substring(1)); - } - }); + EventQueue.invokeLater(() -> presentColorHex.setText(colorValue.substring(1))); if (colorValue.length() == 6 && colorValue.matches("[0123456789ABCDEF]*")) { presentColor.setBackground(new Color(PApplet.unhex(colorValue))); if (!colorValue.equals(presentColorHex.getText())) - EventQueue.invokeLater(new Runnable() { - public void run() { - presentColorHex.setText(colorValue); - } - }); + EventQueue.invokeLater(() -> presentColorHex.setText(colorValue)); } } @@ -244,16 +223,12 @@ public class PreferencesFrame { selector = new ColorChooser(frame, false, Preferences.getColor("run.present.bgcolor"), - Language.text("prompt.ok"), - new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - String colorValue = selector.getHexColor(); - colorValue = colorValue.substring(1); // remove the # - presentColorHex.setText(colorValue); - presentColor.setBackground(new Color(PApplet.unhex(colorValue))); - selector.hide(); - } + Language.text("prompt.ok"), e -> { + String colorValue = selector.getHexColor(); + colorValue = colorValue.substring(1); // remove the # + presentColorHex.setText(colorValue); + presentColor.setBackground(new Color(PApplet.unhex(colorValue))); + selector.hide(); }); presentColor.addMouseListener(new MouseAdapter() { @@ -293,9 +268,7 @@ public class PreferencesFrame { errorCheckerBox = new JCheckBox(Language.text("preferences.continuously_check")); - errorCheckerBox.addItemListener(e -> { - warningsCheckerBox.setEnabled(errorCheckerBox.isSelected()); - }); + errorCheckerBox.addItemListener(e -> warningsCheckerBox.setEnabled(errorCheckerBox.isSelected())); // [ ] Show Warnings - PDE X @@ -321,12 +294,7 @@ public class PreferencesFrame { memoryOverrideBox = new JCheckBox(Language.text("preferences.increase_max_memory")+": "); memoryField = new JTextField(4); - memoryOverrideBox.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - memoryField.setEnabled(memoryOverrideBox.isSelected()); - } - }); + memoryOverrideBox.addChangeListener(e -> memoryField.setEnabled(memoryOverrideBox.isSelected())); JLabel mbLabel = new JLabel("MB"); @@ -388,19 +356,13 @@ public class PreferencesFrame { // [ OK ] [ Cancel ] okButton = new JButton(Language.getPrompt("ok")); - okButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - applyFrame(); - disposeFrame(); - } - }); + okButton.addActionListener(e -> { + applyFrame(); + disposeFrame(); + }); JButton cancelButton = new JButton(Language.getPrompt("cancel")); - cancelButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - disposeFrame(); - } - }); + cancelButton.addActionListener(e -> disposeFrame()); final int buttonWidth = Toolkit.getButtonWidth(); layout.setHorizontalGroup(layout.createSequentialGroup() // sequential group for border + mainContent + border @@ -536,11 +498,7 @@ public class PreferencesFrame { } }); - ActionListener disposer = new ActionListener() { - public void actionPerformed(ActionEvent actionEvent) { - disposeFrame(); - } - }; + ActionListener disposer = actionEvent -> disposeFrame(); // finish up Toolkit.registerWindowCloseKeys(frame.getRootPane(), disposer); @@ -620,13 +578,19 @@ public class PreferencesFrame { // The preference will have already been reset when the window was created if (displaySelectionBox.isEnabled()) { int oldDisplayNum = Preferences.getInteger("run.display"); - int displayNum = -1; + int displayNum = -1; // use the default display for (int d = 0; d < displaySelectionBox.getItemCount(); d++) { if (displaySelectionBox.getSelectedIndex() == d) { - displayNum = d + 1; + if (d == defaultDisplayNum-1) { + // if it's the default display, store -1 instead of its index, + // because displays can get renumbered when others are attached + displayNum = -1; + } else { + displayNum = d + 1; + } } } - if ((displayNum != -1) && (displayNum != oldDisplayNum)) { + if (displayNum != oldDisplayNum) { Preferences.setInteger("run.display", displayNum); //$NON-NLS-1$ // Reset the location of the sketch, the window has changed for (Editor editor : base.getEditors()) { @@ -641,7 +605,9 @@ public class PreferencesFrame { try { memoryMax = Integer.parseInt(memoryField.getText().trim()); // make sure memory setting isn't too small - if (memoryMax < memoryMin) memoryMax = memoryMin; + if (memoryMax < memoryMin) { + memoryMax = memoryMin; + } Preferences.setInteger("run.options.memory.maximum", memoryMax); //$NON-NLS-1$ } catch (NumberFormatException e) { System.err.println("Ignoring bad memory setting"); @@ -650,6 +616,9 @@ public class PreferencesFrame { // Don't change anything if the user closes the window before fonts load if (fontSelectionBox.isEnabled()) { String fontFamily = (String) fontSelectionBox.getSelectedItem(); + if (Toolkit.getMonoFontName().equals(fontFamily)) { + fontFamily = "processing.mono"; + } Preferences.set("editor.font.family", fontFamily); } @@ -666,10 +635,6 @@ public class PreferencesFrame { fontSizeField.setSelectedItem(Preferences.getInteger("editor.font.size")); } - Preferences.setBoolean("editor.zoom.auto", zoomAutoBox.isSelected()); - Preferences.set("editor.zoom", - String.valueOf(zoomSelectionBox.getSelectedItem())); - try { Object selection = consoleFontSizeField.getSelectedItem(); if (selection instanceof String) { @@ -683,6 +648,10 @@ public class PreferencesFrame { consoleFontSizeField.setSelectedItem(Preferences.getInteger("console.font.size")); } + Preferences.setBoolean("editor.zoom.auto", zoomAutoBox.isSelected()); + Preferences.set("editor.zoom", + String.valueOf(zoomSelectionBox.getSelectedItem())); + Preferences.setColor("run.present.bgcolor", presentColor.getBackground()); Preferences.setBoolean("editor.input_method_support", inputMethodBox.isSelected()); //$NON-NLS-1$ @@ -711,38 +680,32 @@ public class PreferencesFrame { warningsCheckerBox.setSelected(Preferences.getBoolean("pdex.warningsEnabled")); warningsCheckerBox.setEnabled(errorCheckerBox.isSelected()); codeCompletionBox.setSelected(Preferences.getBoolean("pdex.completion")); - //codeCompletionTriggerBox.setSelected(Preferences.getBoolean("pdex.completion.trigger")); - //codeCompletionTriggerBox.setEnabled(codeCompletionBox.isSelected()); importSuggestionsBox.setSelected(Preferences.getBoolean("pdex.suggest.imports")); deletePreviousBox.setSelected(Preferences.getBoolean("export.delete_target_folder")); //$NON-NLS-1$ sketchbookLocationField.setText(Preferences.getSketchbookPath()); checkUpdatesBox.setSelected(Preferences.getBoolean("update.check")); //$NON-NLS-1$ - int defaultDisplayNum = updateDisplayList(); + defaultDisplayNum = updateDisplayList(); int displayNum = Preferences.getInteger("run.display"); //$NON-NLS-1$ - //if (displayNum > 0 && displayNum <= displayCount) { if (displayNum < 1 || displayNum > displayCount) { + // set the display on close instead; too much weird logic here + //Preferences.setInteger("run.display", displayNum); displayNum = defaultDisplayNum; - Preferences.setInteger("run.display", displayNum); } displaySelectionBox.setSelectedIndex(displayNum-1); // This takes a while to load, so run it from a separate thread //EventQueue.invokeLater(new Runnable() { - new Thread(new Runnable() { - public void run() { - initFontList(); - } - }).start(); + new Thread(this::initFontList).start(); fontSizeField.setSelectedItem(Preferences.getInteger("editor.font.size")); consoleFontSizeField.setSelectedItem(Preferences.getInteger("console.font.size")); boolean zoomAuto = Preferences.getBoolean("editor.zoom.auto"); if (zoomAuto) { - zoomAutoBox.setSelected(zoomAuto); - zoomSelectionBox.setEnabled(!zoomAuto); + zoomAutoBox.setSelected(true); + zoomSelectionBox.setEnabled(false); } String zoomSel = Preferences.get("editor.zoom"); int zoomIndex = Toolkit.zoomOptions.index(zoomSel); @@ -777,38 +740,39 @@ public class PreferencesFrame { } - /** - * I have some ideas on how we could make Swing even more obtuse for the - * most basic usage scenarios. Is there someone on the team I can contact? - * Are you an Oracle staffer reading this? This could be your meal ticket. - */ - static class FontNamer extends JLabel implements ListCellRenderer { - public Component getListCellRendererComponent(JList list, - Font value, int index, - boolean isSelected, - boolean cellHasFocus) { - setText(value.getFamily() + " / " + value.getName() + " (" + value.getPSName() + ")"); - return this; - } - } +// /** +// * I have some ideas on how we could make Swing even more obtuse for the +// * most basic usage scenarios. Is there someone on the team I can contact? +// * Are you an Oracle staffer reading this? This could be your meal ticket. +// */ +// static class FontNamer extends JLabel implements ListCellRenderer { +// public Component getListCellRendererComponent(JList list, +// Font value, int index, +// boolean isSelected, +// boolean cellHasFocus) { +// setText(value.getFamily() + " / " + value.getName() + " (" + value.getPSName() + ")"); +// return this; +// } +// } void initFontList() { if (monoFontFamilies == null) { monoFontFamilies = Toolkit.getMonoFontFamilies(); - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - fontSelectionBox.setModel(new DefaultComboBoxModel<>(monoFontFamilies)); - String family = Preferences.get("editor.font.family"); - - // Set a reasonable default, in case selecting the family fails - fontSelectionBox.setSelectedItem("Monospaced"); - // Now try to select the family (will fail silently, see prev line) - fontSelectionBox.setSelectedItem(family); - fontSelectionBox.setEnabled(true); + EventQueue.invokeLater(() -> { + fontSelectionBox.setModel(new DefaultComboBoxModel<>(monoFontFamilies)); + String family = Preferences.get("editor.font.family"); + String defaultName = Toolkit.getMonoFontName(); + if ("processing.mono".equals(family)) { + family = defaultName; } + + // Set a reasonable default, in case selecting the family fails + fontSelectionBox.setSelectedItem(defaultName); + // Now try to select the family (will fail silently, see prev line) + fontSelectionBox.setSelectedItem(family); + fontSelectionBox.setEnabled(true); }); } } diff --git a/app/src/processing/app/ui/Toolkit.java b/app/src/processing/app/ui/Toolkit.java index 096b96a9b..6b0dc7e7e 100644 --- a/app/src/processing/app/ui/Toolkit.java +++ b/app/src/processing/app/ui/Toolkit.java @@ -157,8 +157,6 @@ public class Toolkit { * Create a menu item and set its KeyStroke by name (so it can be stored * in the language settings or the preferences. Syntax is here: * https://docs.oracle.com/javase/8/docs/api/javax/swing/KeyStroke.html#getKeyStroke-java.lang.String- - * @param sequence the name, as outlined by the KeyStroke API - * @param fallback what to use if getKeyStroke() comes back null */ static public JMenuItem newJMenuItemExt(String base) { JMenuItem menuItem = new JMenuItem(Language.text(base)); @@ -1002,6 +1000,7 @@ public class Toolkit { static Font sansBoldFont; + /** Get the name of the default (built-in) monospaced font. */ static public String getMonoFontName() { if (monoFont == null) { // create a dummy version if the font has never been loaded (rare) @@ -1011,6 +1010,13 @@ public class Toolkit { } + /** + * Get the Font object of the default (built-in) monospaced font. + * As of 4.x, this is Source Code Pro and ships in lib/fonts because + * it looks like JDK 11 no longer has (supports?) a "fonts" subfolder + * (or at least, its cross-platform implementation is inconsistent). + * https://www.oracle.com/java/technologies/javase/11-relnote-issues.html#JDK-8191522 + */ static public Font getMonoFont(int size, int style) { if (monoFont == null) { try { diff --git a/build/.gitignore b/build/.gitignore index 7c4bf3a86..162fa4ac6 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -4,9 +4,12 @@ javadoc macosx/javafx-sdk-* macosx/jdk-11.* macosx/jfx-11.* +macosx/jfx-16.* windows/javafx-sdk-* windows/jdk-11.* windows/jfx-11.* +windows/jfx-16.* linux/javafx-sdk-* linux/jdk-11.* linux/jfx-11.* +linux/jfx-16.* diff --git a/build/build.xml b/build/build.xml index fc572a348..6a1efc699 100644 --- a/build/build.xml +++ b/build/build.xml @@ -72,13 +72,6 @@ - - - - - - - @@ -90,132 +83,21 @@ - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -233,11 +115,11 @@ + @@ -251,74 +133,17 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -335,7 +160,7 @@ - + @@ -352,19 +177,6 @@ path="${jdk.tgz.path}" /> - - - - - @@ -470,6 +282,7 @@ + @@ -492,6 +305,7 @@ + @@ -521,6 +335,12 @@ + + + + + + @@ -646,7 +466,7 @@ - + @@ -673,14 +493,14 @@ name="Processing" displayName="Processing" executableName="Processing" - identifier="org.processing.app" - signature="Pde3" + identifier="org.processing.four" + signature="Pde4" icon="macosx/processing.icns" copyright="© The Processing Foundation" shortVersion="${version}" version="${revision}" mainClassName="processing.app.BaseSplash" - minimumSystemVersion="10.13.6"> + minimumSystemVersion="10.14.6"> + + @@ -743,6 +569,16 @@ - + + @@ -867,10 +705,12 @@ + @@ -969,8 +809,7 @@ - - + @@ -987,7 +826,7 @@ - + @@ -1105,7 +944,7 @@ - + @@ -1145,9 +984,10 @@ - + + @@ -1224,10 +1065,6 @@ - @@ -1236,11 +1073,12 @@ + - + @@ -1435,34 +1273,7 @@ - - - - - - - - - - - - - - - - - - - - - - + @@ -1525,16 +1336,6 @@ - - - - - @@ -1563,15 +1360,6 @@ - @@ -1580,15 +1368,6 @@ prefix="processing-${version}" /> - - - ======================================================= Processing for Windows was built. Grab the archive from @@ -1709,7 +1488,7 @@ remove the spaces for depth since it should be double dash, but screws up commen - + @@ -1744,13 +1523,13 @@ remove the spaces for depth since it should be double dash, but screws up commen - + - + @@ -1785,13 +1564,13 @@ remove the spaces for depth since it should be double dash, but screws up commen - + - + @@ -1808,6 +1587,9 @@ remove the spaces for depth since it should be double dash, but screws up commen + + + diff --git a/build/jre/.classpath b/build/jre/.classpath index a95e6b974..6b40476ff 100644 --- a/build/jre/.classpath +++ b/build/jre/.classpath @@ -1,7 +1,7 @@ - + diff --git a/build/linux/processing b/build/linux/processing index 711147d91..c5da2df73 100755 --- a/build/linux/processing +++ b/build/linux/processing @@ -5,10 +5,8 @@ # If your system needs a different version of Java than what's included # in the download, replace the 'java' folder with the contents of a new -# Oracle JRE (Java 8 only), or create a symlink named "java" in the +# OpenJDK (Java 11 only), or create a symlink named "java" in the # Processing installation directory that points to the JRE home directory. -# This must be a Sun/Oracle JDK. For more details, see here: -# https://github.com/processing/processing/wiki/Supported-Platforms # Thanks to Ferdinand Kasper for this build script. [fry] @@ -115,5 +113,5 @@ else fi cd "$APPDIR" - java -splash:lib/about-1x.png -Djna.nosys=true -Xmx512m processing.app.Base "$SKETCH" & + java -splash:lib/about-1x.png -Djna.nosys=true -Djava.library.path=lib:modes/java/libraries/javafx/library/linux64 --module-path=modes/java/libraries/javafx/library/linux64/modules --add-modules=javafx.base,javafx.controls,javafx.fxml,javafx.graphics,javafx.media,javafx.swing -Xmx512m processing.app.Base "$SKETCH" & fi diff --git a/build/macosx/appbundler.jar b/build/macosx/appbundler.jar index 62a658d51..29ed99e20 100644 Binary files a/build/macosx/appbundler.jar and b/build/macosx/appbundler.jar differ diff --git a/build/macosx/appbundler/.classpath b/build/macosx/appbundler/.classpath index 3124990b4..ec275ba27 100644 --- a/build/macosx/appbundler/.classpath +++ b/build/macosx/appbundler/.classpath @@ -1,7 +1,7 @@ - + diff --git a/build/macosx/appbundler/build.xml b/build/macosx/appbundler/build.xml index 068b41b39..8f7dea90d 100644 --- a/build/macosx/appbundler/build.xml +++ b/build/macosx/appbundler/build.xml @@ -89,8 +89,8 @@ questions. --> - - + + diff --git a/build/macosx/appbundler/native/main.m b/build/macosx/appbundler/native/main.m index 510c371d7..6675192a5 100644 --- a/build/macosx/appbundler/native/main.m +++ b/build/macosx/appbundler/native/main.m @@ -105,7 +105,7 @@ int main(int argc, char *argv[]) { result = 0; } @catch (NSException *exception) { NSAlert *alert = [[NSAlert alloc] init]; - [alert setAlertStyle:NSCriticalAlertStyle]; + [alert setAlertStyle:NSAlertStyleCritical]; [alert setMessageText:[exception reason]]; [alert runModal]; @@ -241,7 +241,8 @@ int launch(char *commandName, int progargc, char *progargv[]) { libjliPath = [javaDylib fileSystemRepresentation]; } - DLog(@"Launchpath: %s", libjliPath); + // Disable chatty log message that looks like an error in the Console + //DLog(@"Launchpath: %s", libjliPath); void *libJLI = dlopen(libjliPath, RTLD_LAZY); @@ -413,19 +414,26 @@ int launch(char *commandName, int progargc, char *progargv[]) { DLog(@"Main Class Name: '%@'", mainClassName); } - // If a jar file is defined as launcher, disacard the javaPath + // If a jar file is defined as launcher, discard the javaPath if ( jarlauncher != nil ) { [classPath appendFormat:@":%@/%@", javaPath, jarlauncher]; } else { NSArray *cp = [infoDictionary objectForKey:@JVM_CLASSPATH_KEY]; if (cp == nil) { - // Implicit classpath, so use the contents of the "Java" folder to build an explicit classpath [classPath appendFormat:@"%@/Classes", javaPath]; NSFileManager *defaultFileManager = [NSFileManager defaultManager]; + // original, non-recursive version: + // https://developer.apple.com/documentation/foundation/nsfilemanager/1414584-contentsofdirectoryatpath NSArray *javaDirectoryContents = [defaultFileManager contentsOfDirectoryAtPath:javaPath error:nil]; + + // changed to recursive version to walk the 'core' folder + // https://developer.apple.com/documentation/foundation/nsfilemanager/1417353-subpathsofdirectoryatpath + // NSArray *javaDirectoryContents = [defaultFileManager subpathsOfDirectoryAtPath:javaPath error:nil]; + // moving back away from this because we need the 'modules' directory off the classpath + if (javaDirectoryContents == nil) { [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR reason:NSLocalizedString(@"JavaDirectoryNotFound", @UNSPECIFIED_ERROR) diff --git a/build/macosx/appbundler/src/com/oracle/appbundler/AppBundlerTask.java b/build/macosx/appbundler/src/com/oracle/appbundler/AppBundlerTask.java index 1d922789f..fcdc17859 100644 --- a/build/macosx/appbundler/src/com/oracle/appbundler/AppBundlerTask.java +++ b/build/macosx/appbundler/src/com/oracle/appbundler/AppBundlerTask.java @@ -55,6 +55,7 @@ import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.types.Resource; import org.apache.tools.ant.types.resources.FileResource; /** @@ -505,13 +506,11 @@ public class AppBundlerTask extends Task { private void copyClassPathRefEntries(File javaDirectory) throws IOException { if(classPathRef != null) { org.apache.tools.ant.types.Path classpath = + (org.apache.tools.ant.types.Path) classPathRef.getReferencedObject(getProject()); - (org.apache.tools.ant.types.Path) classPathRef.getReferencedObject(getProject()); - - @SuppressWarnings("unchecked") - Iterator iter = classpath.iterator(); - while(iter.hasNext()) { - FileResource resource = iter.next(); + Iterator iter = classpath.iterator(); + while (iter.hasNext()) { + FileResource resource = (FileResource) iter.next(); File source = resource.getFile(); File destination = new File(javaDirectory, source.getName()); copy(source, destination); diff --git a/build/macosx/ffs.entitlements b/build/macosx/ffs.entitlements index 17bf6d8f6..e18f052e0 100644 --- a/build/macosx/ffs.entitlements +++ b/build/macosx/ffs.entitlements @@ -12,5 +12,11 @@ com.apple.security.cs.allow-dyld-environment-variables + com.apple.security.device.camera + + com.apple.security.device.microphone + + com.apple.security.device.audio-input + \ No newline at end of file diff --git a/build/shared/changes.md b/build/shared/changes.md index cc551af72..867f1524c 100644 --- a/build/shared/changes.md +++ b/build/shared/changes.md @@ -1,3 +1,139 @@ +# Processing 4.0 alpha 6 + +*Revision 1275 – XX July 2021* + +Code completion is back! + +### Bug Fixes + +* A more useful message for the `NoClassDefError: processing/core/PApplet` startup error on Windows was in the alpha 5 source, but may not have made it into the actual release. [#154](https://github.com/processing/processing4/issues/154) + + +# Processing 4.0 alpha 5 + +*Revision 1274 – 24 June 2021* + +Sneaking a release out the door the morning before our company meeting. Don't tell my boss, he's kind of a jerk. + + +### Known Bugs + +* Code completion is currently broken. Any updates will be posted [here](https://github.com/processing/processing4/issues/177). + +* Plenty of other issues being tracked in the [Processing 4](https://github.com/processing/processing4/issues) and [Processing 3](https://github.com/processing/processing/issues) repositories. Please help! + + +### Updates and Additions + +* Added a [few more](https://github.com/processing/processing4/commit/7b75acf2799f61c9c22233f38ee73c07635cea14) “entitlements” to the macOS release that may help with using the Video and Sound libraries, especially on Big Sur. + +* Moved from the 11.0.2 LTS version of JavaFX to the in-progress version 16. This fixes a [garbled text](https://bugs.openjdk.java.net/browse/JDK-8234916) issue that was breaking Tools that used JavaFX. + +* JavaFX has been moved out of core and into a separate library. After Java 8, it's no longer part of the JDK, so it requires additional files that were formerly included by default. The Processing version of that library comes in at 180 MB, which seems excessive to include with every project, regardless of whether it's used. (And that's not including the full Webkit implementation, which adds \~80 MB per platform.) + +* JavaFX is back and working again! This also fixes some Tools that relied on it, and Export to Application should be working as well. [#110](https://github.com/processing/processing4/issues/110), [#112](https://github.com/processing/processing4/pull/112), [#3288](https://github.com/processing/processing/issues/3288), [#209](https://github.com/processing/processing4/issues/209), [#210](https://github.com/processing/processing4/issues/210) + + +### Other Fixes and Changes + +* Major font cleanup inside Preferences. It appears that the “Monospace” font may be [missing or broken](https://www.oracle.com/java/technologies/javase/11-relnote-issues.html#JDK-8191522) with OpenJDK 11. Does it show a random sans serif for you? Let us know if it does. But aside from that, the Editor and Console font should be behaving a little more reliably. + +* The Windows splash screen should no longer be tiny. Still sorting out some hidpi issues on Windows, but this one might make things a bit less quirky. [#4896](https://github.com/processing/processing/issues/4896), [#145](https://github.com/processing/processing4/issues/145) + +* Better handling of `NoClassDefError: processing/core/PApplet` on startup with Windows 10. [#154](https://github.com/processing/processing4/issues/154) + +* No longer using `JFileChooser` on macOS; it's too awful. This means a Copy/Paste issue comes back, but the cure was worse than the disease. [#1035](https://github.com/processing/processing/issues/1035), [#77](https://github.com/processing/processing4/issues/77) + + +### Internal Changes + +* The minimum system version for macOS (for the PDE and exported applications) is now set to 10.14.6 (the last update of Mojave). 10.13 (High Sierra) is no longer supported by Apple as of September or December 2020 (depending on what you read), and for our sanity, we're dropping it as well. + +* Updated JNA from 5.7.0 to 5.8.0. + +* Remove the ant binary from the repo, updated the version we're using from 1.8.2 to 1.10.10. + +* Rebuilt the `appbundler` tool for macOS to handle some recent changes, and disabled some logging chatter as well. + +* Update to launch4j 3.14, fixing Export to Application on Windows. + + +# Processing 4.0 alpha 4 + +*Revision 1273 - 15 June 2021* + +Happy birthday to my goddaughter Kelsey! Let's celebrate with another alpha release. + +This should be a bit more stable than the last round. I've rolled back some of the more aggressive anti-AWT changes (bad for longevity, good for compatibility) so images in particular are now behaving better. + +But enough of that, let's go to the phone lines: + + +### What bugs have been fixed; why should I care? + +* Sketch window location is saved once again: re-running a sketch will open the window in the same location. This was broken for a while! [#158](https://github.com/processing/processing4/issues/158), [#5843](https://github.com/processing/processing/issues/5843), [#5781](https://github.com/processing/processing/issues/5781) + +* When using multiple monitors, new Editor windows will open on the same display as the most recently opened Editor window. [#205](https://github.com/processing/processing4/issues/205), formerly [#1566](https://github.com/processing/processing/issues/1566) + +* A major Undo fix, this may even be [the big one](https://github.com/processing/processing/issues/4775), but it's not confirmed. (Please help confirm!) [#175](https://github.com/processing/processing4/pull/175) + + +### Were you too hasty with exorcising AWT? + +* `cursor(PImage)` broken everywhere because `PImage.getNative()` returns `null` [#180](https://github.com/processing/processing4/issues/180) + +* `PImage.resize()` not working properly. [#200](https://github.com/processing/processing4/issues/200) + +* `copy()` not working correctly. [#169](https://github.com/processing/processing4/issues/169) + + +### Did you find any particularly niggling, but small issues? + +* Catch `NoClassDefError` in `Platform.deleteFile()` (still unclear of its cause) on Big Sur. [#159](https://github.com/processing/processing4/issues/159), [#6185](https://github.com/processing/processing/issues/6185) + +* Fixed `Exception in thread "Contribution Uninstaller" NullPointerException` when removing an installed contribution. [#174](https://github.com/processing/processing4/issues/174) + +* If the default display is selected in the Preferences window, store that, rather than its number. It was discovered that plugging in a second display could bump the “default” display to number 2, even while it was still selected. Yay! + +* Sort out calling `unregisterMethod()` for `dispose` from `dispose()` makes for bad state situation. [#199](https://github.com/processing/processing4/pull/199) + + +### How about contributions from the community? + ++ Don't sort user's charset array when calling `createFont()`. [#197](https://github.com/processing/processing4/issues/197), [#198](https://github.com/processing/processing4/pull/198) + +* Some exciting things are on the way for the documentation and web site. [#191](https://github.com/processing/processing4/pull/191) + +* Update Batik to 1.14. [#179](https://github.com/processing/processing4/issues/179), [#192](https://github.com/processing/processing4/issues/192), [#183](https://github.com/processing/processing4/pull/183) + +* Tweak the circle for number of updates based on Akarshit's initial attempt. [#201](https://github.com/processing/processing4/issues/201), [#4097](https://github.com/processing/processing/pull/4097) + +* Make `parseJSONObject()` and `parseJSONArray()` return `null` when parsing fails. [#165](https://github.com/processing/processing4/issues/165), [#166](https://github.com/processing/processing4/pull/166) + + +### Is there anything new? + ++ Added `PVector.setHeading()` for parity with p5.js. [#193](https://github.com/processing/processing4/issues/193) + +* The default font (what you get if `textFont()` isnot used) has been changed to Source Sans instead of Lucida Sans. I just couldn't take Lucida any longer. + + +### Were there any internal changes I probably won't notice? + +* Updated to JDK 11.0.11+9 + +* Update from JNA 5.2.0 to 5.7.0 + +* Modernize the RegisteredMethods code to use collections classes w/ concurrency. [#199](https://github.com/processing/processing4/pull/199) + +* Set closed issues to [automatically lock](https://github.com/dessant/lock-threads) after they've been closed for 30 days. (This has no effect on open issues, only closed ones.) Actually this one you may have noticed if you had a lot of notifications turned on. + +* Slowly transitioning some of the older code to newer syntax (lambda functions, etc). This is not a priority for anyone else: it's being done slowly, and as a chance to do code review on some very old work. + + +* Fix `textMode(SHAPE) is not supported by this renderer` message with SVG Export. [#202](https://github.com/processing/processing4/issues/202), [#6169](https://github.com/processing/processing/issues/6169) + + # Processing 4.0 alpha 3 *Revision 1272 - 17 January 2021* diff --git a/build/shared/lib/defaults.txt b/build/shared/lib/defaults.txt index d40b5c2f8..6805114d4 100644 --- a/build/shared/lib/defaults.txt +++ b/build/shared/lib/defaults.txt @@ -74,7 +74,12 @@ recent.count = 10 # Default to the native (AWT) file selector where possible chooser.files.native = true -chooser.files.native.macosx = false # except on mac where it's broken +# We were shutting this off on macOS because it broke Copy/Paste: +# https://github.com/processing/processing/issues/1035 +# https://github.com/processing/processing4/issues/77 +# But changing this for 4.0 alpha 5, because the JFileChooser is awful, +# and even worse on Big Sur, and worse than the Copy/Paste issue. +#chooser.files.native.macosx = false # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -90,6 +95,9 @@ platform.auto_file_type_associations = true # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# default font used for UI elements +ui.font.family = Dialog +ui.font.size = 12 # default size for the main window editor.window.width.default = 700 @@ -109,13 +117,9 @@ editor.zoom = 100% # automatically set based on system dpi (only helps on Windows) editor.zoom.auto = true -# font size for editor -#editor.font = Monospaced,plain,12 -# Monaco is nicer on Mac OS X, so use that explicitly -#editor.font.macosx = Monaco,plain,10 -# trying for a built-in, consistent monospace for 2.0 -#editor.font = processing.mono,plain,12 -editor.font.family = Source Code Pro +# 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 @@ -172,9 +176,6 @@ editor.watcher.window = 1500 search.format = https://google.com/search?q=%s # font choice and size for the console -#console.font = Monospaced,plain,11 -#console.font.macosx = Monaco,plain,10 -#console.font = processing.mono,plain,12 console.font.size = 12 # number of lines to show by default diff --git a/build/shared/lib/languages/PDE.properties b/build/shared/lib/languages/PDE.properties index b81fcd11e..1083b7908 100644 --- a/build/shared/lib/languages/PDE.properties +++ b/build/shared/lib/languages/PDE.properties @@ -607,8 +607,8 @@ color_chooser.select = Select # Movie Maker movie_maker = Movie Maker -movie_maker.title = QuickTime Movie Maker -movie_maker.blurb = This tool creates a QuickTime movie from a sequence of images.

To avoid artifacts caused by re-compressing images as video,
use TIFF, TGA (from Processing), or PNG images as the source.

TIFF and TGA images will write more quickly, but require more disk:
saveFrame("frames/####.tif");
saveFrame("frames/####.tga");

PNG images are smaller, but your sketch will run more slowly:
saveFrame("frames/####.png");

This code is based on QuickTime Movie Maker 1.5.1 2011-01-17.
Copyright © 2010-2011 Werner Randelshofer. All rights reserved.
+movie_maker.two.title = Movie Maker II: The Revenge +movie_maker.two.blurb = Create an MPEG movie or Animated GIF from a sequence of images

To avoid artifacts caused by re-compressing images as video,
use TIFF, TGA, or PNG images as the source.

TIFF and TGA will save more quickly in a sketch, but require more disk:
saveFrame("frames/####.tif");
saveFrame("frames/####.tga");

PNG images are smaller, but your sketch will run more slowly:
saveFrame("frames/####.png");

Lossless 4:2:0 is still imperfect because of how MPEG-4 works.
4:4:4 should be ideal, but less compatible with other software.

Formerly QuickTime Movie Maker by Werner Randelshofer,
this was mostly rewritten to use FFmpeg for Processing 4.
movie_maker.image_folder_help_label = Drag a folder with image files into the field below: movie_maker.choose_button = Choose... movie_maker.select_image_folder = Select image folder... @@ -620,9 +620,6 @@ movie_maker.save_dialog_prompt = Save movie as... movie_maker.width = Width: movie_maker.height = Height: movie_maker.compression = Compression: -movie_maker.compression.animation = Animation -movie_maker.compression.jpeg = JPEG -movie_maker.compression.png = PNG movie_maker.framerate = Framerate: movie_maker.orig_size_button = Same size as originals movie_maker.orig_size_tooltip = Check this box if the folder contains already encoded video frames in the desired size. @@ -637,7 +634,9 @@ movie_maker.error.no_images_found = No image files found. movie_maker.error.sorry = Sorry movie_maker.error.unknown_tga_format = Unknown .tga file format for %s. -movie_maker.progress.creating_file_name = Creating %s. +movie_maker.progress.creating_file_name = Combining images into movie file %s. movie_maker.progress.creating_output_file = Creating output file movie_maker.progress.initializing = Initializing... movie_maker.progress.processing = Processing %s. + +movie_maker.progress.handling_frame = Converting frame %s of %s... \ No newline at end of file diff --git a/build/shared/tools/MovieMaker/.gitignore b/build/shared/tools/MovieMaker/.gitignore index 7b057f39e..1a046ba46 100644 --- a/build/shared/tools/MovieMaker/.gitignore +++ b/build/shared/tools/MovieMaker/.gitignore @@ -1,4 +1,6 @@ bin +ffmpeg-*.gz + diff --git a/build/shared/tools/MovieMaker/build.xml b/build/shared/tools/MovieMaker/build.xml index f56fbc954..ee074a477 100644 --- a/build/shared/tools/MovieMaker/build.xml +++ b/build/shared/tools/MovieMaker/build.xml @@ -1,28 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + - + + + + + + + + + + + + + + diff --git a/build/shared/tools/MovieMaker/src/processing/app/tools/FFmpegEngine.java b/build/shared/tools/MovieMaker/src/processing/app/tools/FFmpegEngine.java new file mode 100644 index 000000000..3dc7c6c74 --- /dev/null +++ b/build/shared/tools/MovieMaker/src/processing/app/tools/FFmpegEngine.java @@ -0,0 +1,258 @@ +package processing.app.tools; + +import processing.app.Language; +import processing.app.Platform; + +import javax.swing.*; +import java.awt.Component; +import java.io.*; +import java.text.NumberFormat; +import java.util.List; +import java.util.ArrayList; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +class FFmpegEngine { + static Pattern framePattern = Pattern.compile("^frame=(\\d+)$"); + + Component parent; + String ffmpegPath; + + + FFmpegEngine(Component parent) { + this.parent = parent; + + // Use the location of this jar to find the "tool" folder + String jarPath = + getClass().getProtectionDomain().getCodeSource().getLocation().getFile(); + File toolFolder = new File(jarPath).getParentFile(); + // Use that path to get the full path to our copy of the ffmpeg binary + String ffmpegName = Platform.isWindows() ? "ffmpeg.exe" : "ffmpeg"; + ffmpegPath = new File(toolFolder, ffmpegName).getAbsolutePath(); + } + + + String[] getFormats() { + return new String[] { + "MPEG-4", "MPEG-4 (Lossless 4:2:0)", "MPEG-4 (Lossless 4:4:4)", + "Animated GIF", "Animated GIF (Loop)" + }; + } + + + void write(File movieFile, File[] imgFiles, File soundFile, + int width, int height, double fps, String formatName) throws IOException { + // Write a temporary file with the names of all the images. + // This removes the requirement for images using %04d format (and having + // to detect the exact variant). Also, the number of images is likely to + // be too long for command line arguments on some platforms. + File listingFile = File.createTempFile("listing", ".txt"); + PrintWriter writer = new PrintWriter(new FileWriter(listingFile)); + for (File file : imgFiles) { + writer.println("file '" + file.getAbsolutePath() + "'"); + } + writer.flush(); + writer.close(); + + // path specified by the user (may be changed later to append file type) + String outputPath = movieFile.getAbsolutePath(); + + List cmd = new ArrayList<>(); + cmd.add(ffmpegPath); + + // delete the file if it already exists + cmd.add("-y"); + + // set frame rate + cmd.add("-r"); + cmd.add(String.valueOf(fps)); + + // input format is image files + cmd.add("-f"); + cmd.add("image2"); + + // allow absolute paths in the file list + cmd.add("-safe"); + cmd.add("0"); + + // use the concatenation filter to read our entries from a file + // https://trac.ffmpeg.org/wiki/Concatenate + // (not enough to simply specify -i with a .txt file) + cmd.add("-f"); + cmd.add("concat"); + + // temporary file with the list of all images + cmd.add("-i"); + cmd.add(listingFile.getAbsolutePath()); + + // pipe:1 sends progress to stdout, pipe:2 to stderr + cmd.add("-progress"); + cmd.add("pipe:2"); + + String formatArgs = "fps=" + fps; + if (width != 0 && height != 0) { + formatArgs += ",scale=" + width + ":" + height + ":flags=lanczos"; + } + + if (formatName.startsWith("MPEG-4")) { + // slideshow: http://trac.ffmpeg.org/wiki/Slideshow + // options for compatibility: https://superuser.com/a/424024 + + if (soundFile != null) { + cmd.add("-i"); + cmd.add(soundFile.getAbsolutePath()); + } + + // use the h.264 video codec + cmd.add("-vcodec"); + cmd.add("libx264"); + + if (formatName.contains("4:2:0")) { + // Best compatibility with QuickTime, macOS, and others + cmd.add("-pix_fmt"); + cmd.add("yuv420p"); + + // https://trac.ffmpeg.org/wiki/Encode/H.264 + cmd.add("-preset"); + // can also use "veryslow" for better compression + cmd.add("ultrafast"); + + cmd.add("-crf"); + cmd.add("0"); + + } else if (formatName.contains("4:4:4")) { + // Based on https://stackoverflow.com/a/18506577 + // Not compatible with QuickTime and macOS, but works in VLC + cmd.add("-pix_fmt"); + cmd.add("yuv444p"); + + cmd.add("-profile:v"); + cmd.add("high444"); + + cmd.add("-preset:v"); + cmd.add("slow"); + + cmd.add("-crf"); + cmd.add("0"); + + } else { + cmd.add("-pix_fmt"); + cmd.add("yuv420p"); + + // decent quality images + cmd.add("-crf"); + cmd.add("21"); // 18 to 25, with 18 the lowest + } + + // if there's a resize, specify it and the type of scaling + if (width != 0 && height != 0) { + cmd.add("-vf"); + cmd.add(formatArgs); + } + + if (soundFile != null) { + cmd.add("-acodec"); + cmd.add("aac"); + } + + // move container metadata to the beginning of the file + cmd.add("-movflags"); + cmd.add("faststart"); + + if (!outputPath.toLowerCase().endsWith(".mp4")) { + outputPath += ".mp4"; + } + + } else if (formatName.startsWith("Animated GIF")) { + // ffmpeg -r 30 -f image2 -safe 0 -f concat -i listing.txt -vf "fps=30,scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 ~/Desktop/output.gif + // sets the gif palette nicely, based on https://superuser.com/a/556031 + formatArgs += ",split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse"; + cmd.add("-vf"); + cmd.add(formatArgs); + + // https://www.ffmpeg.org/ffmpeg-formats.html#gif-2 + cmd.add("-loop"); + // -1 means no loop, 0 means infinite loop (1+ means number of loops?) + cmd.add(formatName.contains("Loop") ? "0" : "-1"); + + if (!outputPath.toLowerCase().endsWith(".gif")) { + outputPath += ".gif"; + } + } + + cmd.add(outputPath); + final String outputName = new File(outputPath).getName(); + + // pass cmd to Runtime exec + // read the output and set the progress bar + // show a message (success/failure) when complete + + final String maxCount = nfc(imgFiles.length); + final ProgressMonitor progress = new ProgressMonitor(parent, + Language.interpolate("movie_maker.progress.creating_file_name", outputName), + //Language.text("movie_maker.progress.creating_output_file"), + //Language.interpolate("movie_maker.progress.handling_frame", 0, imgFiles.length), + Language.interpolate("movie_maker.progress.handling_frame", maxCount, maxCount), + 0, imgFiles.length); + + // https://stackoverflow.com/a/33386692 + Process p = new ProcessBuilder(cmd).start(); + StreamGobbler outputGobbler = new StreamGobbler(p.getInputStream(), System.out::println); + StreamGobbler errorGobbler = new StreamGobbler(p.getErrorStream(), line -> { + // TODO handle process.isCanceled() + //System.out.println("msg: " + msg); + Matcher m = framePattern.matcher(line); + if (m.find()) { + int frameCount = Integer.parseInt(m.group(1)); + // this was used to show which (input) image file was being handled + //progress.setNote(Language.interpolate("movie_maker.progress.processing", inputName)); + progress.setNote(Language.interpolate("movie_maker.progress.handling_frame", + nfc(frameCount + 1), nfc(imgFiles.length))); +// System.out.println(frameCount + " of " + imgFiles.length); + progress.setProgress(frameCount); +// } else { +// System.out.println(">>>" + line + "<<<"); + } + }); + + new Thread(outputGobbler).start(); + new Thread(errorGobbler).start(); + try { + int result = p.waitFor(); + //System.out.println("encoding result was " + result); + if (result != 0) { + throw new IOException("Unknown error while creating movie. " + + "(FFmpeg result was " + result + ")"); + } + progress.close(); + + } catch (InterruptedException ignored) { } + } + + + static class StreamGobbler implements Runnable { + final private InputStream inputStream; + final private Consumer consumeInputLine; + + public StreamGobbler(InputStream inputStream, Consumer consumeInputLine) { + this.inputStream = inputStream; + this.consumeInputLine = consumeInputLine; + } + + public void run() { + new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(consumeInputLine); + } + } + + + static NumberFormat formatter = NumberFormat.getInstance(); + + static String nfc(int num) { +// if (formatter == null) { +// formatter = NumberFormat.getInstance(); +// } + return formatter.format(num); + } +} \ No newline at end of file diff --git a/build/shared/tools/MovieMaker/src/processing/app/tools/MovieMaker.java b/build/shared/tools/MovieMaker/src/processing/app/tools/MovieMaker.java index bbfd28abf..5ef6c8300 100644 --- a/build/shared/tools/MovieMaker/src/processing/app/tools/MovieMaker.java +++ b/build/shared/tools/MovieMaker/src/processing/app/tools/MovieMaker.java @@ -13,13 +13,10 @@ package processing.app.tools; import java.awt.*; import java.awt.event.*; -import java.awt.image.*; import java.io.*; -import java.util.*; +import java.util.Arrays; import java.util.prefs.Preferences; -import javax.imageio.ImageIO; -import javax.sound.sampled.*; import javax.swing.*; import javax.swing.border.EmptyBorder; import javax.swing.filechooser.FileSystemView; @@ -28,41 +25,18 @@ import processing.app.Base; import processing.app.Language; import ch.randelshofer.gui.datatransfer.FileTextFieldTransferHandler; -import ch.randelshofer.media.mp3.MP3AudioInputStream; -import ch.randelshofer.media.quicktime.QuickTimeWriter; +import processing.app.Platform; /** - * Hacked from Werner Randelshofer's QuickTimeWriter demo. The original version - * can be found here. - *

- * A more up-to-date version of the project is - * here. - * Problem is, it's too big, so we don't want to merge it into our code. - *

- * Broken out as a separate project because the license (CC) probably isn't - * compatible with the rest of Processing and we don't want any confusion. - *

- * Added JAI ImageIO to support lots of other image file formats [131008]. - * Also copied the Processing TGA implementation. - *

- * Added support for the gamma ('gama') atom [131008]. - *

- * A few more notes on the implementation: - *

    - *
  • The dialog box is super ugly. It's a hacked up version of the previous - * interface, but I'm too scared to pull that GUI layout code apart. - *
  • The 'None' compressor seems to have bugs, so just disabled it instead. - *
  • The 'pass through' option seems to be broken, so it's been removed. - * In its place is an option to use the same width/height as the originals. - *
  • When this new 'pass through' is set, there's some nastiness with how - * the 'final' width/height variables are passed to the movie maker. - * This is an easy fix but needs a couple minutes. - *
- * Ben Fry 2011-09-06, updated 2013-10-09 + * Tool for creating movie files from sequences of images. + * Originally hacked from Werner Randelshofer's QuickTimeWriter demo, + * reorganized for Processing 4 to instead use FFmpeg. */ public class MovieMaker extends JFrame implements Tool { private Preferences prefs; + //private QuickTimeEngine engine; + private FFmpegEngine engine; public String getMenuTitle() { @@ -76,7 +50,10 @@ public class MovieMaker extends JFrame implements Tool { public void init(Base base) { - initComponents(base.getActiveEditor() == null); + //engine = new QuickTimeEngine(this); + engine = new FFmpegEngine(this); + //initComponents(base.getActiveEditor() == null); + initComponents(base == null); ((JComponent) getContentPane()).setBorder(new EmptyBorder(12, 18, 18, 18)); imageFolderField.setTransferHandler(new FileTextFieldTransferHandler(JFileChooser.DIRECTORIES_ONLY)); @@ -114,23 +91,12 @@ public class MovieMaker extends JFrame implements Tool { fpsField.setText(fps); compressionBox.setSelectedIndex(Math.max(0, Math.min(compressionBox.getItemCount() - 1, prefs.getInt("movie.compression", 0)))); - originalSizeCheckBox.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - boolean enabled = !originalSizeCheckBox.isSelected(); - widthField.setEnabled(enabled); - heightField.setEnabled(enabled); - } + originalSizeCheckBox.addActionListener(e -> { + boolean enabled = !originalSizeCheckBox.isSelected(); + widthField.setEnabled(enabled); + heightField.setEnabled(enabled); }); -// String streaming = prefs.get("movie.streaming", "fastStartCompressed"); -// for (Enumeration i = streamingGroup.getElements(); i.hasMoreElements();) { -// AbstractButton btn = i.nextElement(); -// if (btn.getActionCommand().equals(streaming)) { -// btn.setSelected(true); -// break; -// } -// } - // scoot everybody around pack(); // center the frame on screen @@ -148,7 +114,7 @@ public class MovieMaker extends JFrame implements Tool { root.registerKeyboardAction(disposer, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW); - int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); stroke = KeyStroke.getKeyStroke('W', modifiers); root.registerKeyboardAction(disposer, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW); @@ -156,19 +122,19 @@ public class MovieMaker extends JFrame implements Tool { private void initComponents(final boolean standalone) { - imageFolderHelpLabel = new JLabel(); + JLabel imageFolderHelpLabel = new JLabel(); imageFolderField = new JTextField(); - chooseImageFolderButton = new JButton(); - soundFileHelpLabel = new JLabel(); + JButton chooseImageFolderButton = new JButton(); + JLabel soundFileHelpLabel = new JLabel(); soundFileField = new JTextField(); - chooseSoundFileButton = new JButton(); + JButton chooseSoundFileButton = new JButton(); createMovieButton = new JButton(); widthLabel = new JLabel(); widthField = new JTextField(); heightLabel = new JLabel(); heightField = new JTextField(); compressionLabel = new JLabel(); - compressionBox = new JComboBox(); + compressionBox = new JComboBox<>(); fpsLabel = new JLabel(); fpsField = new JTextField(); originalSizeCheckBox = new JCheckBox(); @@ -179,95 +145,59 @@ public class MovieMaker extends JFrame implements Tool { setVisible(false); } }); - registerWindowCloseKeys(getRootPane(), new ActionListener() { - public void actionPerformed(ActionEvent actionEvent) { - if (standalone) { - System.exit(0); - } else { - setVisible(false); - } + registerWindowCloseKeys(getRootPane(), actionEvent -> { + if (standalone) { + System.exit(0); + } else { + setVisible(false); } }); - setTitle(Language.text("movie_maker.title")); + setTitle(Language.text("movie_maker.two.title")); - aboutLabel = new JLabel(Language.text("movie_maker.blurb")); + JLabel aboutLabel = new JLabel(Language.text("movie_maker.two.blurb")); imageFolderHelpLabel.setText(Language.text("movie_maker.image_folder_help_label")); chooseImageFolderButton.setText(Language.text("movie_maker.choose_button")); //chooseImageFolderButton.addActionListener(formListener); - chooseImageFolderButton.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - Chooser.selectFolder(MovieMaker.this, - Language.text("movie_maker.select_image_folder"), - new File(imageFolderField.getText()), - new Chooser.Callback() { - void select(File file) { - if (file != null) { - imageFolderField.setText(file.getAbsolutePath()); - } - } - }); + chooseImageFolderButton.addActionListener(e -> Chooser.selectFolder(MovieMaker.this, + Language.text("movie_maker.select_image_folder"), + new File(imageFolderField.getText()), + new Chooser.Callback() { + void select(File file) { + if (file != null) { + imageFolderField.setText(file.getAbsolutePath()); + } } - }); + })); soundFileHelpLabel.setText(Language.text("movie_maker.sound_file_help_label")); chooseSoundFileButton.setText(Language.text("movie_maker.choose_button")); //chooseSoundFileButton.addActionListener(formListener); - chooseSoundFileButton.addActionListener(new ActionListener() { + chooseSoundFileButton.addActionListener(e -> Chooser.selectInput(MovieMaker.this, + Language.text("movie_maker.select_sound_file"), + new File(soundFileField.getText()), + new Chooser.Callback() { - @Override - public void actionPerformed(ActionEvent e) { - Chooser.selectInput(MovieMaker.this, - Language.text("movie_maker.select_sound_file"), - new File(soundFileField.getText()), - new Chooser.Callback() { - - void select(File file) { - if (file != null) { - soundFileField.setText(file.getAbsolutePath()); - } - } - }); + void select(File file) { + if (file != null) { + soundFileField.setText(file.getAbsolutePath()); + } } - }); + })); createMovieButton.setText(Language.text("movie_maker.create_movie_button")); -// createMovieButton.addActionListener(formListener); - createMovieButton.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - String lastPath = prefs.get("movie.outputFile", null); - File lastFile = lastPath == null ? null : new File(lastPath); - Chooser.selectOutput(MovieMaker.this, - Language.text("movie_maker.save_dialog_prompt"), - lastFile, - new Chooser.Callback() { - @Override - void select(File file) { - if (file != null) { - String path = file.getAbsolutePath(); - if (!path.toLowerCase().endsWith(".mov")) { - path += ".mov"; - } - prefs.put("movie.outputFile", path); - createMovie(new File(path)); -// final File target = new File(path); -// //new Thread(new Runnable() { -// EventQueue.invokeLater(new Runnable() { -// -// @Override -// public void run() { -// createMovie(target); -// } -// -// }); - } - } - }); - } + createMovieButton.addActionListener(e -> { + String lastPath = prefs.get("movie.outputFile", null); + File lastFile = lastPath == null ? null : new File(lastPath); + Chooser.selectOutput(MovieMaker.this, + Language.text("movie_maker.save_dialog_prompt"), + lastFile, + new Chooser.Callback() { + @Override + void select(File file) { + createMovie(file); + } + }); }); Font font = new Font("Dialog", Font.PLAIN, 11); @@ -288,13 +218,7 @@ public class MovieMaker extends JFrame implements Tool { compressionLabel.setText(Language.text("movie_maker.compression")); compressionBox.setFont(font); //compressionBox.setModel(new DefaultComboBoxModel(new String[] { "None", "Animation", "JPEG", "PNG" })); - compressionBox.setModel(new DefaultComboBoxModel( - new String[] { - Language.text("movie_maker.compression.animation"), - Language.text("movie_maker.compression.jpeg"), - Language.text("movie_maker.compression.png") - } - )); + compressionBox.setModel(new DefaultComboBoxModel<>(engine.getFormats())); fpsLabel.setFont(font); fpsLabel.setText(Language.text("movie_maker.framerate")); @@ -306,27 +230,6 @@ public class MovieMaker extends JFrame implements Tool { originalSizeCheckBox.setText(Language.text("movie_maker.orig_size_button")); originalSizeCheckBox.setToolTipText(Language.text("movie_maker.orig_size_tooltip")); -// streamingLabel.setText("Prepare for Internet Streaming"); -// -// streamingGroup.add(noPreparationRadio); -// noPreparationRadio.setFont(font); -// noPreparationRadio.setSelected(true); -// noPreparationRadio.setText("No preparation"); -// noPreparationRadio.setActionCommand("none"); -// noPreparationRadio.addActionListener(formListener); -// -// streamingGroup.add(fastStartRadio); -// fastStartRadio.setFont(font); -// fastStartRadio.setText("Fast Start"); -// fastStartRadio.setActionCommand("fastStart"); -// fastStartRadio.addActionListener(formListener); -// -// streamingGroup.add(fastStartCompressedRadio); -// fastStartCompressedRadio.setFont(font); -// fastStartCompressedRadio.setText("Fast Start - Compressed Header"); -// fastStartCompressedRadio.setActionCommand("fastStartCompressed"); -// fastStartCompressedRadio.addActionListener(formListener); - GroupLayout layout = new GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) @@ -373,83 +276,43 @@ public class MovieMaker extends JFrame implements Tool { .addContainerGap()))) ); layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(aboutLabel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(aboutLabel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(imageFolderHelpLabel) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(imageFolderField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(chooseImageFolderButton)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(widthLabel) + .addComponent(widthField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(heightLabel) + .addComponent(heightField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(compressionBox, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(fpsLabel) + .addComponent(fpsField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(compressionLabel) + .addComponent(originalSizeCheckBox)) .addGap(18, 18, 18) - .addComponent(imageFolderHelpLabel) + .addComponent(soundFileHelpLabel) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(imageFolderField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addComponent(chooseImageFolderButton)) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(widthLabel) - .addComponent(widthField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addComponent(heightLabel) - .addComponent(heightField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(compressionBox, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addComponent(fpsLabel) - .addComponent(fpsField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addComponent(compressionLabel) - .addComponent(originalSizeCheckBox)) - .addGap(18, 18, 18) - .addComponent(soundFileHelpLabel) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) - .addComponent(soundFileField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) - .addComponent(chooseSoundFileButton)) - .addGap(18, 18, 18) - .addComponent(createMovieButton) - .addContainerGap()) + .addComponent(soundFileField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(chooseSoundFileButton)) + .addGap(18, 18, 18) + .addComponent(createMovieButton) + .addContainerGap()) ); pack(); } - // Code for dispatching events from components to event handlers. -// private class FormListener implements java.awt.event.ActionListener { -// FormListener() {} -// public void actionPerformed(java.awt.event.ActionEvent evt) { -// if (evt.getSource() == chooseImageFolderButton) { -// MovieMaker.this.chooseImageFolder(evt); -// } -// else if (evt.getSource() == chooseSoundFileButton) { -// MovieMaker.this.chooseSoundFile(evt); -// } -// else if (evt.getSource() == createMovieButton) { -// MovieMaker.this.createMovie(evt); -// } -//// else if (evt.getSource() == fastStartCompressedRadio) { -//// MovieMaker.this.streamingRadioPerformed(evt); -//// } -//// else if (evt.getSource() == fastStartRadio) { -//// MovieMaker.this.streamingRadioPerformed(evt); -//// } -//// else if (evt.getSource() == noPreparationRadio) { -//// MovieMaker.this.streamingRadioPerformed(evt); -//// } -// } -// } - -// private void chooseImageFolder(ActionEvent evt) { -// if (imageFolderChooser == null) { -// imageFolderChooser = new JFileChooser(); -// imageFolderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); -// if (imageFolderField.getText().length() > 0) { -// imageFolderChooser.setSelectedFile(new File(imageFolderField.getText())); -// } else if (soundFileField.getText().length() > 0) { -// imageFolderChooser.setCurrentDirectory(new File(soundFileField.getText()).getParentFile()); -// } -// } -// if (JFileChooser.APPROVE_OPTION == imageFolderChooser.showOpenDialog(this)) { -// imageFolderField.setText(imageFolderChooser.getSelectedFile().getPath()); -// } -// } -// // private void chooseSoundFile(ActionEvent evt) { // if (soundFileChooser == null) { // soundFileChooser = new JFileChooser(); @@ -465,13 +328,25 @@ public class MovieMaker extends JFrame implements Tool { // } - // this is super naughty, and shouldn't be out here. it's a hack to get the // ImageIcon width/height setting to work. there are better ways to do this // given a bit of time. you know, time? the infinite but non-renewable resource? int width, height; private void createMovie(final File movieFile) { + if (movieFile == null) return; + + /* + if (movieFile != null) { + String path = movieFile.getAbsolutePath(); + if (!path.toLowerCase().endsWith(".mov")) { + path += ".mov"; + } + prefs.put("movie.outputFile", path); + createMovie(new File(path)); + } + */ + createMovieButton.setEnabled(false); // --------------------------------- @@ -479,8 +354,6 @@ public class MovieMaker extends JFrame implements Tool { // --------------------------------- final File soundFile = soundFileField.getText().trim().length() == 0 ? null : new File(soundFileField.getText().trim()); final File imageFolder = imageFolderField.getText().trim().length() == 0 ? null : new File(imageFolderField.getText().trim()); - //final String streaming = prefs.get("movie.streaming", "fastStartCompressed"); - final String streaming = "fastStartCompressed"; if (soundFile == null && imageFolder == null) { JOptionPane.showMessageDialog(this, Language.text("movie_maker.error.need_input")); return; @@ -500,22 +373,24 @@ public class MovieMaker extends JFrame implements Tool { return; } + /* final QuickTimeWriter.VideoFormat videoFormat; switch (compressionBox.getSelectedIndex()) { // case 0: // videoFormat = QuickTimeWriter.VideoFormat.RAW; // break; - case 0://1: - videoFormat = QuickTimeWriter.VideoFormat.RLE; - break; - case 1://2: - videoFormat = QuickTimeWriter.VideoFormat.JPG; - break; - case 2://3: - default: - videoFormat = QuickTimeWriter.VideoFormat.PNG; - break; + case 0://1: + videoFormat = QuickTimeWriter.VideoFormat.RLE; + break; + case 1://2: + videoFormat = QuickTimeWriter.VideoFormat.JPG; + break; + case 2://3: + default: + videoFormat = QuickTimeWriter.VideoFormat.PNG; + break; } + */ // --------------------------------- // Update Preferences @@ -539,10 +414,10 @@ public class MovieMaker extends JFrame implements Tool { protected Throwable doInBackground() { try { // Read image files - File[] imgFiles = null; + File[] imgFiles; if (imageFolder != null) { imgFiles = imageFolder.listFiles(new FileFilter() { - FileSystemView fsv = FileSystemView.getFileSystemView(); + final FileSystemView fsv = FileSystemView.getFileSystemView(); public boolean accept(File f) { return f.isFile() && !fsv.isHiddenFile(f) && @@ -553,30 +428,22 @@ public class MovieMaker extends JFrame implements Tool { return new RuntimeException(Language.text("movie_maker.error.no_images_found")); } Arrays.sort(imgFiles); - } - // Get the width and height if we're preserving size. - if (originalSize) { - Dimension d = findSize(imgFiles); - if (d == null) { - // No images at all? No video then. - throw new RuntimeException(Language.text("movie_maker.error.no_images_found")); + // Get the width and height if we're preserving size. + if (originalSize) { + width = 0; + height = 0; } - width = d.width; - height = d.height; - } - // Delete movie file if it already exists. - if (movieFile.exists()) { - movieFile.delete(); - } + // Delete movie file if it already exists. + if (movieFile.exists()) { + if (!movieFile.delete()) { + return new RuntimeException("Could not replace " + movieFile.getAbsolutePath()); + } + } - if (imageFolder != null && soundFile != null) { - writeVideoAndAudio(movieFile, imgFiles, soundFile, width, height, fps, videoFormat, /*passThrough,*/ streaming); - } else if (imageFolder != null) { - writeVideoOnlyVFR(movieFile, imgFiles, width, height, fps, videoFormat, /*passThrough,*/ streaming); - } else { - writeAudioOnly(movieFile, soundFile, streaming); + String formatName = (String) compressionBox.getSelectedItem(); + engine.write(movieFile, imgFiles, soundFile, width, height, fps, formatName); } return null; @@ -585,19 +452,6 @@ public class MovieMaker extends JFrame implements Tool { } } - Dimension findSize(File[] imgFiles) { - for (int i = 0; i < imgFiles.length; i++) { - BufferedImage temp = readImage(imgFiles[i]); - if (temp != null) { - return new Dimension(temp.getWidth(), temp.getHeight()); - } else { - // Nullify bad Files so we don't get errors twice. - imgFiles[i] = null; - } - } - return null; - } - @Override protected void done() { Throwable t; @@ -609,10 +463,10 @@ public class MovieMaker extends JFrame implements Tool { if (t != null) { t.printStackTrace(); JOptionPane.showMessageDialog(MovieMaker.this, - Language.text("movie_maker.error.movie_failed") + "\n" + - (t.getMessage() == null ? t.toString() : t.getMessage()), - Language.text("movie_maker.error.sorry"), - JOptionPane.ERROR_MESSAGE); + Language.text("movie_maker.error.movie_failed") + "\n" + + (t.getMessage() == null ? t.toString() : t.getMessage()), + Language.text("movie_maker.error.sorry"), + JOptionPane.ERROR_MESSAGE); } createMovieButton.setEnabled(true); } @@ -621,614 +475,28 @@ public class MovieMaker extends JFrame implements Tool { }//GEN-LAST:event_createMovie - /** - * Read an image from a file. ImageIcon doesn't don't do well with some - * file types, so we use ImageIO. ImageIO doesn't handle TGA files - * created by Processing, so this calls our own loadImageTGA(). - *
Prints errors itself. - * @return null on error; image only if okay. - */ - private BufferedImage readImage(File file) { - try { - Thread current = Thread.currentThread(); - ClassLoader origLoader = Thread.currentThread().getContextClassLoader(); - current.setContextClassLoader(getClass().getClassLoader()); + static public void main(String[] args) { + EventQueue.invokeLater(() -> { + Base.setCommandLine(); + Platform.init(); - BufferedImage image; - try { - image = ImageIO.read(file); - } catch (IOException e) { - System.err.println(Language.interpolate("movie_maker.error.cannot_read", - file.getAbsolutePath())); - return null; - } - - current.setContextClassLoader(origLoader); - - /* - String[] loadImageFormats = ImageIO.getReaderFormatNames(); - if (loadImageFormats != null) { - for (String format : loadImageFormats) { - System.out.println(format); - } - } - */ - - if (image == null) { - String path = file.getAbsolutePath(); - String pathLower = path.toLowerCase(); - // Might be an incompatible TGA or TIFF created by Processing - if (pathLower.endsWith(".tga")) { - try { - return loadImageTGA(file); - } catch (IOException e) { - cannotRead(file); - return null; - } - - } else if (pathLower.endsWith(".tif") || pathLower.endsWith(".tiff")) { - cannotRead(file); - System.err.println(Language.text("movie_maker.error.avoid_tiff")); - return null; - - } else { - cannotRead(file); - return null; - } - - } else { - if (image.getWidth() <= 0 || image.getHeight() <= 0) { - System.err.println(Language.interpolate("movie_maker.error.cannot_read_maybe_bad", file.getAbsolutePath())); - return null; - } - } - return image; - - // Catch-all is sometimes needed. - } catch (RuntimeException e) { - cannotRead(file); - return null; - } - } - - - static private void cannotRead(File file) { - String path = file.getAbsolutePath(); - String msg = Language.interpolate("movie_maker.error.cannot_read", path); - System.err.println(msg); - } - - - /** variable frame rate. */ - private void writeVideoOnlyVFR(File movieFile, File[] imgFiles, int width, int height, double fps, QuickTimeWriter.VideoFormat videoFormat, /*boolean passThrough,*/ String streaming) throws IOException { - File tmpFile = streaming.equals("none") ? movieFile : new File(movieFile.getPath() + ".tmp"); - ProgressMonitor p = new ProgressMonitor(MovieMaker.this, - Language.interpolate("movie_maker.progress.creating_file_name", movieFile.getName()), - Language.text("movie_maker.progress.creating_output_file"), - 0, imgFiles.length); - Graphics2D g = null; - BufferedImage img = null; - BufferedImage prevImg = null; - int[] data = null; - int[] prevData = null; - QuickTimeWriter qtOut = null; - try { - int timeScale = (int) (fps * 100.0); - int duration = 100; - - qtOut = new QuickTimeWriter(videoFormat == QuickTimeWriter.VideoFormat.RAW ? movieFile : tmpFile); - qtOut.addVideoTrack(videoFormat, timeScale, width, height); - qtOut.setSyncInterval(0, 30); - - //if (!passThrough) { - if (true) { - img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - data = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); - prevImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - prevData = ((DataBufferInt) prevImg.getRaster().getDataBuffer()).getData(); - g = img.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); - } - int prevImgDuration = 0; - for (int i = 0; i < imgFiles.length && !p.isCanceled(); i++) { - File f = imgFiles[i]; - if (f == null) continue; - - p.setNote(Language.interpolate("movie_maker.progress.processing", f.getName())); - p.setProgress(i); - - //if (passThrough) { - if (false) { - qtOut.writeSample(0, f, duration); - } else { - //BufferedImage fImg = ImageIO.read(f); - BufferedImage fImg = readImage(f); - if (fImg == null) continue; - - g.drawImage(fImg, 0, 0, width, height, null); - if (i != 0 && Arrays.equals(data, prevData)) { - prevImgDuration += duration; - } else { - if (prevImgDuration != 0) { - qtOut.writeFrame(0, prevImg, prevImgDuration); - } - prevImgDuration = duration; - System.arraycopy(data, 0, prevData, 0, data.length); - } - } - } - if (prevImgDuration != 0) { - qtOut.writeFrame(0, prevImg, prevImgDuration); - } - if (streaming.equals("fastStart")) { - qtOut.toWebOptimizedMovie(movieFile, false); - tmpFile.delete(); - } else if (streaming.equals("fastStartCompressed")) { - qtOut.toWebOptimizedMovie(movieFile, true); - tmpFile.delete(); - } - qtOut.close(); - qtOut = null; - } finally { - p.close(); - if (g != null) { - g.dispose(); - } - if (img != null) { - img.flush(); - } - if (qtOut != null) { - qtOut.close(); - } - } - } - - - private void writeAudioOnly(File movieFile, File audioFile, String streaming) throws IOException { - File tmpFile = streaming.equals("none") ? movieFile : new File(movieFile.getPath() + ".tmp"); - - int length = (int) Math.min(Integer.MAX_VALUE, audioFile.length()); // file length is used for a rough progress estimate. This will only work for uncompressed audio. - ProgressMonitor p = new ProgressMonitor(MovieMaker.this, - Language.interpolate("movie_maker.progress.creating_file_name", movieFile.getName()), - Language.text("movie_maker.progress.initializing"), 0, length); - AudioInputStream audioIn = null; - QuickTimeWriter qtOut = null; - - try { - qtOut = new QuickTimeWriter(tmpFile); - if (audioFile.getName().toLowerCase().endsWith(".mp3")) { - audioIn = new MP3AudioInputStream(audioFile); - } else { - audioIn = AudioSystem.getAudioInputStream(audioFile); - } - AudioFormat audioFormat = audioIn.getFormat(); - //System.out.println("QuickTimeMovieMakerMain " + audioFormat); - qtOut.addAudioTrack(audioFormat); - boolean isVBR = audioFormat.getProperty("vbr") != null && ((Boolean) audioFormat.getProperty("vbr")).booleanValue(); - int asSize = audioFormat.getFrameSize(); - int nbOfFramesInBuffer = isVBR ? 1 : Math.max(1, 1024 / asSize); - int asDuration = (int) (audioFormat.getSampleRate() / audioFormat.getFrameRate()); - //System.out.println(" frameDuration=" + asDuration); - long count = 0; - byte[] audioBuffer = new byte[asSize * nbOfFramesInBuffer]; - for (int bytesRead = audioIn.read(audioBuffer); - bytesRead != -1; - bytesRead = audioIn.read(audioBuffer)) { - if (bytesRead != 0) { - int framesRead = bytesRead / asSize; - qtOut.writeSamples(0, framesRead, audioBuffer, 0, bytesRead, asDuration); - count += bytesRead; - p.setProgress((int) count); - } - if (isVBR) { - audioFormat = audioIn.getFormat(); - if (audioFormat == null) { - break; - } - asSize = audioFormat.getFrameSize(); - asDuration = (int) (audioFormat.getSampleRate() / audioFormat.getFrameRate()); - if (audioBuffer.length < asSize) { - audioBuffer = new byte[asSize]; - } - } - } - audioIn.close(); - audioIn = null; - if (streaming.equals("fastStart")) { - qtOut.toWebOptimizedMovie(movieFile, false); - tmpFile.delete(); - } else if (streaming.equals("fastStartCompressed")) { - qtOut.toWebOptimizedMovie(movieFile, true); - tmpFile.delete(); - } - qtOut.close(); - qtOut = null; - } catch (UnsupportedAudioFileException e) { - IOException ioe = new IOException(e.getMessage()); - ioe.initCause(e); - throw ioe; - } finally { - p.close(); - if (audioIn != null) { - audioIn.close(); - } - if (qtOut != null) { - qtOut.close(); - } - } - } - - private void writeVideoAndAudio(File movieFile, File[] imgFiles, File audioFile, - int width, int height, double fps, QuickTimeWriter.VideoFormat videoFormat, - /*boolean passThrough,*/ String streaming) throws IOException { - - File tmpFile = streaming.equals("none") ? movieFile : new File(movieFile.getPath() + ".tmp"); - ProgressMonitor p = new ProgressMonitor(MovieMaker.this, - Language.interpolate("movie_maker.progress.creating_file_name", movieFile.getName()), - Language.text("movie_maker.progress.creating_output_file"), - 0, imgFiles.length); - AudioInputStream audioIn = null; - QuickTimeWriter qtOut = null; - BufferedImage imgBuffer = null; - Graphics2D g = null; - - try { - // Determine audio format - if (audioFile.getName().toLowerCase().endsWith(".mp3")) { - audioIn = new MP3AudioInputStream(audioFile); - } else { - audioIn = AudioSystem.getAudioInputStream(audioFile); - } - AudioFormat audioFormat = audioIn.getFormat(); - boolean isVBR = audioFormat.getProperty("vbr") != null && ((Boolean) audioFormat.getProperty("vbr")).booleanValue(); - - // Determine duration of a single sample - int asDuration = (int) (audioFormat.getSampleRate() / audioFormat.getFrameRate()); - int vsDuration = 100; - // Create writer - qtOut = new QuickTimeWriter(videoFormat == QuickTimeWriter.VideoFormat.RAW ? movieFile : tmpFile); - qtOut.addAudioTrack(audioFormat); // audio in track 0 - qtOut.addVideoTrack(videoFormat, (int) (fps * vsDuration), width, height); // video in track 1 - - // Create audio buffer - int asSize; - byte[] audioBuffer; - if (isVBR) { - // => variable bit rate: create audio buffer for a single frame - asSize = audioFormat.getFrameSize(); - audioBuffer = new byte[asSize]; - } else { - // => fixed bit rate: create audio buffer for half a second - asSize = audioFormat.getChannels() * audioFormat.getSampleSizeInBits() / 8; - audioBuffer = new byte[(int) (qtOut.getMediaTimeScale(0) / 2 * asSize)]; - } - - // Create video buffer - //if (!passThrough) { - if (true) { - imgBuffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - g = imgBuffer.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); - } // Main loop - int movieTime = 0; - int imgIndex = 0; - boolean isAudioDone = false; - while ((imgIndex < imgFiles.length || !isAudioDone) && !p.isCanceled()) { - // Advance movie time by half a second (we interleave twice per second) - movieTime += qtOut.getMovieTimeScale() / 2; - - // Advance audio to movie time + 1 second (audio must be ahead of video by 1 second) - while (!isAudioDone && qtOut.getTrackDuration(0) < movieTime + qtOut.getMovieTimeScale()) { - int len = audioIn.read(audioBuffer); - if (len == -1) { - isAudioDone = true; - } else { - qtOut.writeSamples(0, len / asSize, audioBuffer, 0, len, asDuration); - } - if (isVBR) { - // => variable bit rate: format can change at any time - audioFormat = audioIn.getFormat(); - if (audioFormat == null) { - break; - } - asSize = audioFormat.getFrameSize(); - asDuration = (int) (audioFormat.getSampleRate() / audioFormat.getFrameRate()); - if (audioBuffer.length < asSize) { - audioBuffer = new byte[asSize]; - } - } - } - - // Advance video to movie time - for (; imgIndex < imgFiles.length && qtOut.getTrackDuration(1) < movieTime; ++imgIndex) { - // catch up with video time - p.setProgress(imgIndex); - File f = imgFiles[imgIndex]; - if (f == null) continue; - - p.setNote(Language.interpolate("movie_maker.progress.processing", f.getName())); - //if (passThrough) { - if (false) { - qtOut.writeSample(1, f, vsDuration); - } else { - //BufferedImage fImg = ImageIO.read(imgFiles[imgIndex]); - BufferedImage fImg = readImage(f); - if (fImg == null) continue; - - g.drawImage(fImg, 0, 0, width, height, null); - fImg.flush(); - qtOut.writeFrame(1, imgBuffer, vsDuration); - } - } - } - if (streaming.equals("fastStart")) { - qtOut.toWebOptimizedMovie(movieFile, false); - tmpFile.delete(); - } else if (streaming.equals("fastStartCompressed")) { - qtOut.toWebOptimizedMovie(movieFile, true); - tmpFile.delete(); - } - qtOut.close(); - qtOut = null; - } catch (UnsupportedAudioFileException e) { - IOException ioe = new IOException(e.getMessage()); - ioe.initCause(e); - throw ioe; - } finally { - p.close(); - if (qtOut != null) { - qtOut.close(); - } - if (audioIn != null) { - audioIn.close(); - } - if (g != null) { - g.dispose(); - } - if (imgBuffer != null) { - imgBuffer.flush(); - } - } - } - - - /** - * Targa image loader for RLE-compressed TGA files. - * Code taken from PApplet, any changes here should lead to updates there. - */ - static private BufferedImage loadImageTGA(File file) throws IOException { - InputStream is = new FileInputStream(file); - - try { - byte header[] = new byte[18]; - int offset = 0; - do { - int count = is.read(header, offset, header.length - offset); - if (count == -1) return null; - offset += count; - } while (offset < 18); - - /* - header[2] image type code - 2 (0x02) - Uncompressed, RGB images. - 3 (0x03) - Uncompressed, black and white images. - 10 (0x0A) - Run-length encoded RGB images. - 11 (0x0B) - Compressed, black and white images. (grayscale?) - - header[16] is the bit depth (8, 24, 32) - - header[17] image descriptor (packed bits) - 0x20 is 32 = origin upper-left - 0x28 is 32 + 8 = origin upper-left + 32 bits - - 7 6 5 4 3 2 1 0 - 128 64 32 16 8 4 2 1 - */ - - int format = 0; - final int RGB = 1; - final int ARGB = 2; - final int ALPHA = 4; - - if (((header[2] == 3) || (header[2] == 11)) && // B&W, plus RLE or not - (header[16] == 8) && // 8 bits - ((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32 bit - format = ALPHA; - - } else if (((header[2] == 2) || (header[2] == 10)) && // RGB, RLE or not - (header[16] == 24) && // 24 bits - ((header[17] == 0x20) || (header[17] == 0))) { // origin - format = RGB; - - } else if (((header[2] == 2) || (header[2] == 10)) && - (header[16] == 32) && - ((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32 - format = ARGB; - } - - if (format == 0) { - throw new IOException(Language.interpolate("movie_maker.error.unknown_tga_format", file.getName())); - } - - int w = ((header[13] & 0xff) << 8) + (header[12] & 0xff); - int h = ((header[15] & 0xff) << 8) + (header[14] & 0xff); - //PImage outgoing = createImage(w, h, format); - int[] pixels = new int[w * h]; - - // where "reversed" means upper-left corner (normal for most of - // the modernized world, but "reversed" for the tga spec) - //boolean reversed = (header[17] & 0x20) != 0; - // https://github.com/processing/processing/issues/1682 - boolean reversed = (header[17] & 0x20) == 0; - - if ((header[2] == 2) || (header[2] == 3)) { // not RLE encoded - if (reversed) { - int index = (h-1) * w; - switch (format) { - case ALPHA: - for (int y = h-1; y >= 0; y--) { - for (int x = 0; x < w; x++) { - pixels[index + x] = is.read(); - } - index -= w; - } - break; - case RGB: - for (int y = h-1; y >= 0; y--) { - for (int x = 0; x < w; x++) { - pixels[index + x] = - is.read() | (is.read() << 8) | (is.read() << 16) | - 0xff000000; - } - index -= w; - } - break; - case ARGB: - for (int y = h-1; y >= 0; y--) { - for (int x = 0; x < w; x++) { - pixels[index + x] = - is.read() | (is.read() << 8) | (is.read() << 16) | - (is.read() << 24); - } - index -= w; - } - } - } else { // not reversed - int count = w * h; - switch (format) { - case ALPHA: - for (int i = 0; i < count; i++) { - pixels[i] = is.read(); - } - break; - case RGB: - for (int i = 0; i < count; i++) { - pixels[i] = - is.read() | (is.read() << 8) | (is.read() << 16) | - 0xff000000; - } - break; - case ARGB: - for (int i = 0; i < count; i++) { - pixels[i] = - is.read() | (is.read() << 8) | (is.read() << 16) | - (is.read() << 24); - } - break; - } - } - - } else { // header[2] is 10 or 11 - int index = 0; - - while (index < pixels.length) { - int num = is.read(); - boolean isRLE = (num & 0x80) != 0; - if (isRLE) { - num -= 127; // (num & 0x7F) + 1 - int pixel = 0; - switch (format) { - case ALPHA: - pixel = is.read(); - break; - case RGB: - pixel = 0xFF000000 | - is.read() | (is.read() << 8) | (is.read() << 16); - break; - case ARGB: - pixel = is.read() | - (is.read() << 8) | (is.read() << 16) | (is.read() << 24); - break; - } - for (int i = 0; i < num; i++) { - pixels[index++] = pixel; - if (index == pixels.length) break; - } - } else { // write up to 127 bytes as uncompressed - num += 1; - switch (format) { - case ALPHA: - for (int i = 0; i < num; i++) { - pixels[index++] = is.read(); - } - break; - case RGB: - for (int i = 0; i < num; i++) { - pixels[index++] = 0xFF000000 | - is.read() | (is.read() << 8) | (is.read() << 16); - } - break; - case ARGB: - for (int i = 0; i < num; i++) { - pixels[index++] = is.read() | - (is.read() << 8) | (is.read() << 16) | (is.read() << 24); - } - break; - } - } - } - - if (!reversed) { - int[] temp = new int[w]; - for (int y = 0; y < h/2; y++) { - int z = (h-1) - y; - System.arraycopy(pixels, y*w, temp, 0, w); - System.arraycopy(pixels, z*w, pixels, y*w, w); - System.arraycopy(temp, 0, pixels, z*w, w); - } - } - } - //is.close(); - int type = (format == RGB) ? - BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; - BufferedImage image = new BufferedImage(w, h, type); - WritableRaster wr = image.getRaster(); - wr.setDataElements(0, 0, w, h, pixels); - return image; - - } finally { - is.close(); - } - } - - - /** - * @param args the command line arguments - */ - public static void main(String args[]) { - java.awt.EventQueue.invokeLater(new Runnable() { - public void run() { - MovieMaker m = new MovieMaker(); - m.init(null); -// m.init(); - m.setVisible(true); -// m.pack(); - } + MovieMaker m = new MovieMaker(); + m.init(null); + m.setVisible(true); }); } - private JLabel aboutLabel; - private JButton chooseImageFolderButton; - private JButton chooseSoundFileButton; private JComboBox compressionBox; private JLabel compressionLabel; -// private JRadioButton fastStartCompressedRadio; -// private JRadioButton fastStartRadio; private JTextField fpsField; private JLabel fpsLabel; private JTextField heightField; private JLabel heightLabel; private JTextField imageFolderField; - private JLabel imageFolderHelpLabel; -// private JRadioButton noPreparationRadio; private JCheckBox originalSizeCheckBox; private JTextField soundFileField; - private JLabel soundFileHelpLabel; -// private ButtonGroup streamingGroup; -// private JLabel streamingLabel; private JTextField widthField; private JLabel widthLabel; -// private JLabel copyrightLabel; private JButton createMovieButton; } diff --git a/build/shared/tools/MovieMaker/src/processing/app/tools/QuickTimeEngine.java b/build/shared/tools/MovieMaker/src/processing/app/tools/QuickTimeEngine.java new file mode 100644 index 000000000..0625f3777 --- /dev/null +++ b/build/shared/tools/MovieMaker/src/processing/app/tools/QuickTimeEngine.java @@ -0,0 +1,588 @@ +package processing.app.tools; + +import processing.app.Language; + +import javax.imageio.ImageIO; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.awt.image.WritableRaster; +import java.io.*; +import java.util.Arrays; + +import ch.randelshofer.media.mp3.MP3AudioInputStream; +import ch.randelshofer.media.quicktime.QuickTimeWriter; + + +/** + * Code that's specific to the original QuickTime writer, which no longer + * works on macOS 10.15 and later, because Apple has removed support for + * most compression types (Animation, etc) that are not MPEG. + *

+ * Originally hacked from Werner Randelshofer's QuickTimeWriter demo. + * That source code can be found here. + *

+ * A more up-to-date version of the project is + * here. + * Problem is, it's too big, so we don't want to merge it into our code. + *

+ * Broken out as a separate project because the license (CC) probably isn't + * compatible with the rest of Processing and we don't want any confusion. + *

+ * Added JAI ImageIO to support lots of other image file formats [131008]. + * Also copied the Processing TGA implementation. + *

+ * Added support for the gamma ('gama') atom [131008]. + *

+ * A few more notes on the implementation: + *

    + *
  • The dialog box is super ugly. It's a hacked up version of the previous + * interface, but I'm too scared to pull that GUI layout code apart. + *
  • The 'None' compressor seems to have bugs, so just disabled it instead. + *
  • The 'pass through' option seems to be broken, so it's been removed. + * In its place is an option to use the same width/height as the originals. + *
  • When this new 'pass through' is set, there's some nastiness with how + * the 'final' width/height variables are passed to the movie maker. + * This is an easy fix but needs a couple minutes. + *
+ * Ben Fry 2011-09-06, updated 2013-10-09, and again on 2021-06-27 + */ +class QuickTimeEngine { + Component parent; + + + QuickTimeEngine(Component parent) { + this.parent = parent; + } + + + String[] getFormats() { + return new String[] { + "Animation", "JPEG", "PNG" + }; + } + + + void write(File movieFile, File[] imgFiles, File soundFile, + int width, int height, double fps, String formatName) throws IOException { + QuickTimeWriter.VideoFormat videoFormat; + switch (formatName) { + case "JPEG": + videoFormat = QuickTimeWriter.VideoFormat.JPG; + break; + case "PNG": + videoFormat = QuickTimeWriter.VideoFormat.PNG; + break; + case "Animation": + default: + videoFormat = QuickTimeWriter.VideoFormat.RLE; + break; + } + + // preserving size, check the images for their size + if (width == 0 || height == 0) { + Dimension d = findSize(imgFiles); + if (d == null) { + // No images at all? No video then. + throw new RuntimeException(Language.text("movie_maker.error.no_images_found")); + } + width = d.width; + height = d.height; + } + + if (soundFile != null) { + writeVideoAndAudio(movieFile, imgFiles, soundFile, width, height, fps, videoFormat); + } else { + writeVideoOnlyVFR(movieFile, imgFiles, width, height, fps, videoFormat); + } + } + + + /** + * Read an image from a file. ImageIcon doesn't don't do well with some + * file types, so we use ImageIO. ImageIO doesn't handle TGA files + * created by Processing, so this calls our own loadImageTGA(). + *
Prints errors itself. + * @return null on error; image only if okay. + */ + private BufferedImage readImage(File file) { + try { + Thread current = Thread.currentThread(); + ClassLoader origLoader = Thread.currentThread().getContextClassLoader(); + current.setContextClassLoader(getClass().getClassLoader()); + + BufferedImage image; + try { + image = ImageIO.read(file); + } catch (IOException e) { + System.err.println(Language.interpolate("movie_maker.error.cannot_read", + file.getAbsolutePath())); + return null; + } + + current.setContextClassLoader(origLoader); + + /* + String[] loadImageFormats = ImageIO.getReaderFormatNames(); + if (loadImageFormats != null) { + for (String format : loadImageFormats) { + System.out.println(format); + } + } + */ + + if (image == null) { + String path = file.getAbsolutePath(); + String pathLower = path.toLowerCase(); + // Might be an incompatible TGA or TIFF created by Processing + if (pathLower.endsWith(".tga")) { + try { + return loadImageTGA(file); + } catch (IOException e) { + cannotRead(file); + return null; + } + + } else if (pathLower.endsWith(".tif") || pathLower.endsWith(".tiff")) { + cannotRead(file); + System.err.println(Language.text("movie_maker.error.avoid_tiff")); + return null; + + } else { + cannotRead(file); + return null; + } + + } else { + if (image.getWidth() <= 0 || image.getHeight() <= 0) { + System.err.println(Language.interpolate("movie_maker.error.cannot_read_maybe_bad", file.getAbsolutePath())); + return null; + } + } + return image; + + // Catch-all is sometimes needed. + } catch (RuntimeException e) { + cannotRead(file); + return null; + } + } + + + private Dimension findSize(File[] imgFiles) { + for (int i = 0; i < imgFiles.length; i++) { + BufferedImage temp = readImage(imgFiles[i]); + if (temp != null) { + return new Dimension(temp.getWidth(), temp.getHeight()); + } else { + // Nullify bad Files so we don't get errors twice. + imgFiles[i] = null; + } + } + return null; + } + + + static private void cannotRead(File file) { + String path = file.getAbsolutePath(); + String msg = Language.interpolate("movie_maker.error.cannot_read", path); + System.err.println(msg); + } + + + /** variable frame rate. */ + private void writeVideoOnlyVFR(File movieFile, File[] imgFiles, int width, int height, double fps, QuickTimeWriter.VideoFormat videoFormat) throws IOException { + File tmpFile = new File(movieFile.getPath() + ".tmp"); + ProgressMonitor p = new ProgressMonitor(parent, + Language.interpolate("movie_maker.progress.creating_file_name", movieFile.getName()), + Language.text("movie_maker.progress.creating_output_file"), + 0, imgFiles.length); + Graphics2D g = null; + BufferedImage img = null; + BufferedImage prevImg; + int[] data; + int[] prevData; + QuickTimeWriter qtOut = null; + try { + int timeScale = (int) (fps * 100.0); + int duration = 100; + + qtOut = new QuickTimeWriter(videoFormat == QuickTimeWriter.VideoFormat.RAW ? movieFile : tmpFile); + qtOut.addVideoTrack(videoFormat, timeScale, width, height); + qtOut.setSyncInterval(0, 30); + + //if (!passThrough) { + img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + data = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); + prevImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + prevData = ((DataBufferInt) prevImg.getRaster().getDataBuffer()).getData(); + g = img.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + int prevImgDuration = 0; + for (int i = 0; i < imgFiles.length && !p.isCanceled(); i++) { + File f = imgFiles[i]; + if (f == null) continue; + + p.setNote(Language.interpolate("movie_maker.progress.processing", f.getName())); + p.setProgress(i); + + //BufferedImage fImg = ImageIO.read(f); + BufferedImage fImg = readImage(f); + if (fImg == null) continue; + + g.drawImage(fImg, 0, 0, width, height, null); + if (i != 0 && Arrays.equals(data, prevData)) { + prevImgDuration += duration; + } else { + if (prevImgDuration != 0) { + qtOut.writeFrame(0, prevImg, prevImgDuration); + } + prevImgDuration = duration; + System.arraycopy(data, 0, prevData, 0, data.length); + } + } + if (prevImgDuration != 0) { + qtOut.writeFrame(0, prevImg, prevImgDuration); + } + qtOut.toWebOptimizedMovie(movieFile, true); + tmpFile.delete(); + qtOut.close(); + qtOut = null; + } finally { + p.close(); + if (g != null) { + g.dispose(); + } + if (img != null) { + img.flush(); + } + if (qtOut != null) { + qtOut.close(); + } + } + } + + + private void writeVideoAndAudio(File movieFile, File[] imgFiles, File audioFile, + int width, int height, double fps, QuickTimeWriter.VideoFormat videoFormat) throws IOException { + + File tmpFile = new File(movieFile.getPath() + ".tmp"); + ProgressMonitor p = new ProgressMonitor(parent, + Language.interpolate("movie_maker.progress.creating_file_name", movieFile.getName()), + Language.text("movie_maker.progress.creating_output_file"), + 0, imgFiles.length); + AudioInputStream audioIn = null; + QuickTimeWriter qtOut = null; + BufferedImage imgBuffer = null; + Graphics2D g = null; + + try { + // Determine audio format + if (audioFile.getName().toLowerCase().endsWith(".mp3")) { + audioIn = new MP3AudioInputStream(audioFile); + } else { + audioIn = AudioSystem.getAudioInputStream(audioFile); + } + AudioFormat audioFormat = audioIn.getFormat(); + boolean isVBR = audioFormat.getProperty("vbr") != null && (Boolean) audioFormat.getProperty("vbr"); + + // Determine duration of a single sample + int asDuration = (int) (audioFormat.getSampleRate() / audioFormat.getFrameRate()); + int vsDuration = 100; + // Create writer + qtOut = new QuickTimeWriter(videoFormat == QuickTimeWriter.VideoFormat.RAW ? movieFile : tmpFile); + qtOut.addAudioTrack(audioFormat); // audio in track 0 + qtOut.addVideoTrack(videoFormat, (int) (fps * vsDuration), width, height); // video in track 1 + + // Create audio buffer + int asSize; + byte[] audioBuffer; + if (isVBR) { + // => variable bit rate: create audio buffer for a single frame + asSize = audioFormat.getFrameSize(); + audioBuffer = new byte[asSize]; + } else { + // => fixed bit rate: create audio buffer for half a second + asSize = audioFormat.getChannels() * audioFormat.getSampleSizeInBits() / 8; + audioBuffer = new byte[(int) (qtOut.getMediaTimeScale(0) / 2 * asSize)]; + } + + // Create video buffer + imgBuffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + g = imgBuffer.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + int movieTime = 0; + int imgIndex = 0; + boolean isAudioDone = false; + while ((imgIndex < imgFiles.length || !isAudioDone) && !p.isCanceled()) { + // Advance movie time by half a second (we interleave twice per second) + movieTime += qtOut.getMovieTimeScale() / 2; + + // Advance audio to movie time + 1 second (audio must be ahead of video by 1 second) + while (!isAudioDone && qtOut.getTrackDuration(0) < movieTime + qtOut.getMovieTimeScale()) { + int len = audioIn.read(audioBuffer); + if (len == -1) { + isAudioDone = true; + } else { + qtOut.writeSamples(0, len / asSize, audioBuffer, 0, len, asDuration); + } + if (isVBR) { + // => variable bit rate: format can change at any time + audioFormat = audioIn.getFormat(); + if (audioFormat == null) { + break; + } + asSize = audioFormat.getFrameSize(); + asDuration = (int) (audioFormat.getSampleRate() / audioFormat.getFrameRate()); + if (audioBuffer.length < asSize) { + audioBuffer = new byte[asSize]; + } + } + } + + // Advance video to movie time + for (; imgIndex < imgFiles.length && qtOut.getTrackDuration(1) < movieTime; ++imgIndex) { + // catch up with video time + p.setProgress(imgIndex); + File f = imgFiles[imgIndex]; + if (f == null) continue; + + p.setNote(Language.interpolate("movie_maker.progress.processing", f.getName())); + BufferedImage fImg = readImage(f); + if (fImg == null) continue; + + g.drawImage(fImg, 0, 0, width, height, null); + fImg.flush(); + qtOut.writeFrame(1, imgBuffer, vsDuration); + } + } +// if (streaming.equals("fastStart")) { +// qtOut.toWebOptimizedMovie(movieFile, false); +// tmpFile.delete(); +// } else if (streaming.equals("fastStartCompressed")) { + qtOut.toWebOptimizedMovie(movieFile, true); + tmpFile.delete(); +// } + qtOut.close(); + qtOut = null; + } catch (UnsupportedAudioFileException e) { + throw new IOException(e.getMessage(), e); + } finally { + p.close(); + if (qtOut != null) { + qtOut.close(); + } + if (audioIn != null) { + audioIn.close(); + } + if (g != null) { + g.dispose(); + } + if (imgBuffer != null) { + imgBuffer.flush(); + } + } + } + + + /** + * Targa image loader for RLE-compressed TGA files. + * Code taken from PApplet, any changes here should lead to updates there. + */ + static private BufferedImage loadImageTGA(File file) throws IOException { + + try (InputStream is = new FileInputStream(file)) { + byte[] header = new byte[18]; + int offset = 0; + do { + int count = is.read(header, offset, header.length - offset); + if (count == -1) return null; + offset += count; + } while (offset < 18); + + /* + header[2] image type code + 2 (0x02) - Uncompressed, RGB images. + 3 (0x03) - Uncompressed, black and white images. + 10 (0x0A) - Run-length encoded RGB images. + 11 (0x0B) - Compressed, black and white images. (grayscale?) + + header[16] is the bit depth (8, 24, 32) + + header[17] image descriptor (packed bits) + 0x20 is 32 = origin upper-left + 0x28 is 32 + 8 = origin upper-left + 32 bits + + 7 6 5 4 3 2 1 0 + 128 64 32 16 8 4 2 1 + */ + + int format = 0; + final int RGB = 1; + final int ARGB = 2; + final int ALPHA = 4; + + if (((header[2] == 3) || (header[2] == 11)) && // B&W, plus RLE or not + (header[16] == 8) && // 8 bits + ((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32 bit + format = ALPHA; + + } else if (((header[2] == 2) || (header[2] == 10)) && // RGB, RLE or not + (header[16] == 24) && // 24 bits + ((header[17] == 0x20) || (header[17] == 0))) { // origin + format = RGB; + + } else if (((header[2] == 2) || (header[2] == 10)) && + (header[16] == 32) && + ((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32 + format = ARGB; + } + + if (format == 0) { + throw new IOException(Language.interpolate("movie_maker.error.unknown_tga_format", file.getName())); + } + + int w = ((header[13] & 0xff) << 8) + (header[12] & 0xff); + int h = ((header[15] & 0xff) << 8) + (header[14] & 0xff); + //PImage outgoing = createImage(w, h, format); + int[] pixels = new int[w * h]; + + // where "reversed" means upper-left corner (normal for most of + // the modernized world, but "reversed" for the tga spec) + //boolean reversed = (header[17] & 0x20) != 0; + // https://github.com/processing/processing/issues/1682 + boolean reversed = (header[17] & 0x20) == 0; + + if ((header[2] == 2) || (header[2] == 3)) { // not RLE encoded + if (reversed) { + int index = (h - 1) * w; + switch (format) { + case ALPHA: + for (int y = h - 1; y >= 0; y--) { + for (int x = 0; x < w; x++) { + pixels[index + x] = is.read(); + } + index -= w; + } + break; + case RGB: + for (int y = h - 1; y >= 0; y--) { + for (int x = 0; x < w; x++) { + pixels[index + x] = + is.read() | (is.read() << 8) | (is.read() << 16) | + 0xff000000; + } + index -= w; + } + break; + case ARGB: + for (int y = h - 1; y >= 0; y--) { + for (int x = 0; x < w; x++) { + pixels[index + x] = + is.read() | (is.read() << 8) | (is.read() << 16) | + (is.read() << 24); + } + index -= w; + } + } + } else { // not reversed + int count = w * h; + switch (format) { + case ALPHA: + for (int i = 0; i < count; i++) { + pixels[i] = is.read(); + } + break; + case RGB: + for (int i = 0; i < count; i++) { + pixels[i] = + is.read() | (is.read() << 8) | (is.read() << 16) | + 0xff000000; + } + break; + case ARGB: + for (int i = 0; i < count; i++) { + pixels[i] = + is.read() | (is.read() << 8) | (is.read() << 16) | + (is.read() << 24); + } + break; + } + } + + } else { // header[2] is 10 or 11 + int index = 0; + + while (index < pixels.length) { + int num = is.read(); + boolean isRLE = (num & 0x80) != 0; + if (isRLE) { + num -= 127; // (num & 0x7F) + 1 + int pixel = 0; + switch (format) { + case ALPHA: + pixel = is.read(); + break; + case RGB: + pixel = 0xFF000000 | + is.read() | (is.read() << 8) | (is.read() << 16); + break; + case ARGB: + pixel = is.read() | + (is.read() << 8) | (is.read() << 16) | (is.read() << 24); + break; + } + for (int i = 0; i < num; i++) { + pixels[index++] = pixel; + if (index == pixels.length) break; + } + } else { // write up to 127 bytes as uncompressed + num += 1; + switch (format) { + case ALPHA: + for (int i = 0; i < num; i++) { + pixels[index++] = is.read(); + } + break; + case RGB: + for (int i = 0; i < num; i++) { + pixels[index++] = 0xFF000000 | + is.read() | (is.read() << 8) | (is.read() << 16); + } + break; + case ARGB: + for (int i = 0; i < num; i++) { + pixels[index++] = is.read() | + (is.read() << 8) | (is.read() << 16) | (is.read() << 24); + } + break; + } + } + } + + if (!reversed) { + int[] temp = new int[w]; + for (int y = 0; y < h / 2; y++) { + int z = (h - 1) - y; + System.arraycopy(pixels, y * w, temp, 0, w); + System.arraycopy(pixels, z * w, pixels, y * w, w); + System.arraycopy(temp, 0, pixels, z * w, w); + } + } + } + //is.close(); + int type = (format == RGB) ? + BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; + BufferedImage image = new BufferedImage(w, h, type); + WritableRaster wr = image.getRaster(); + wr.setDataElements(0, 0, w, h, pixels); + return image; + + } + } +} \ No newline at end of file diff --git a/build/shared/tools/MovieMaker/tool/.gitignore b/build/shared/tools/MovieMaker/tool/.gitignore index fb9e88d2d..400139608 100644 --- a/build/shared/tools/MovieMaker/tool/.gitignore +++ b/build/shared/tools/MovieMaker/tool/.gitignore @@ -1,2 +1,3 @@ MovieMaker.jar - +ffmpeg +ffmpeg.exe diff --git a/build/shared/tools/ThemeEngine/.classpath b/build/shared/tools/ThemeEngine/.classpath new file mode 100644 index 000000000..df9fd75cd --- /dev/null +++ b/build/shared/tools/ThemeEngine/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/shared/tools/ThemeEngine/.gitignore b/build/shared/tools/ThemeEngine/.gitignore new file mode 100644 index 000000000..fbb74cb43 --- /dev/null +++ b/build/shared/tools/ThemeEngine/.gitignore @@ -0,0 +1,2 @@ +/bin +ThemeEngine.jar diff --git a/build/shared/tools/ThemeEngine/.project b/build/shared/tools/ThemeEngine/.project new file mode 100644 index 000000000..8fe62bc97 --- /dev/null +++ b/build/shared/tools/ThemeEngine/.project @@ -0,0 +1,17 @@ + + + processing4-tools-theme + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/build/shared/tools/ThemeEngine/.settings/org.eclipse.jdt.core.prefs b/build/shared/tools/ThemeEngine/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..7adc0fb9a --- /dev/null +++ b/build/shared/tools/ThemeEngine/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,10 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=11 diff --git a/build/shared/tools/ThemeEngine/build.xml b/build/shared/tools/ThemeEngine/build.xml new file mode 100644 index 000000000..5f4132a6b --- /dev/null +++ b/build/shared/tools/ThemeEngine/build.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/shared/tools/ThemeEngine/src/processing/app/tools/ThemeEngine.java b/build/shared/tools/ThemeEngine/src/processing/app/tools/ThemeEngine.java new file mode 100644 index 000000000..945129268 --- /dev/null +++ b/build/shared/tools/ThemeEngine/src/processing/app/tools/ThemeEngine.java @@ -0,0 +1,29 @@ +package processing.app.tools; + +import processing.app.Base; +import processing.app.ui.Editor; + + +public class ThemeEngine implements Tool { + Base base; + + public String getMenuTitle() { + //return Language.text("theme_engine"); + return "Theme Engine"; + } + + + public void init(Base base) { + this.base = base; + } + + + public void run() { + //setVisible(true); + //Preferences.init(); + for (Editor editor : base.getEditors()) { + System.out.println("Updating settings for " + editor.getSketch().getName()); + editor.applyPreferences(); + } + } +} diff --git a/build/windows/config.xml b/build/windows/config.xml index e9cd8be17..4866e4eb4 100755 --- a/build/windows/config.xml +++ b/build/windows/config.xml @@ -15,41 +15,67 @@ application.ico processing.app.Base - lib/pde.jar core/library/core.jar + + lib/pde.jar lib/jna.jar lib/jna-platform.jar - lib/antlr-4.7.2-complete.jar lib/ant.jar lib/ant-launcher.jar + + + java + + + + + -Djava.library.path=lib;modes/java/libraries/javafx/library/windows64 + --module-path=modes/java/libraries/javafx/library/windows64/modules + --add-modules=javafx.base,javafx.controls,javafx.fxml,javafx.graphics,javafx.media,javafx.swing + + + + + + -Djna.nosys=true - - - -Dsun.java2d.uiScale.enabled=false + directory won't be considered. And we can't specify the user's + directory here because that'll get us into encoding trouble. + (See https://github.com/processing/processing/issues/3624) + Instead, set this so that Native.loadNativeLibrary() will use + new File(path, "jnidispatch.dll") which will be relative to the + directory of processing.exe (the current working directory). --> -Djna.boot.library.path=lib -Djna.nounpack=true + -Dsun.java2d.d3d=false -Dsun.java2d.ddoffscreen=false -Dsun.java2d.noddraw=true - - 1.8.0_60 - + + + 11.0.11 + 512 diff --git a/core/.classpath b/core/.classpath index e8525451f..bce493553 100644 --- a/core/.classpath +++ b/core/.classpath @@ -1,15 +1,13 @@ - + - - diff --git a/core/build.xml b/core/build.xml index 35e743596..556b0cf75 100644 --- a/core/build.xml +++ b/core/build.xml @@ -64,15 +64,17 @@ - + + + - + diff --git a/core/done.txt b/core/done.txt index e419ecff7..94c54e969 100644 --- a/core/done.txt +++ b/core/done.txt @@ -1,3 +1,48 @@ +1274 (4.0a5) +X do a test to see if PApplet extends PSketch breaks libraries +X seems to be working + + +1273 (4.0a4) +X fix typo in extensions= arg +X update batik to 1.14 +X https://github.com/processing/processing4/issues/179 +X https://github.com/processing/processing4/issues/192 +X update the batik url +X https://github.com/processing/processing4/pull/183 +X calling unregisterMethod() on dispose from dispose() means concurrent mod +o https://github.com/processing/processing4/pull/199 +X modernized the code a bit, checked in a version that queues to avoid list issue +X Make parseJSONObject/Array return null when parsing fails +X https://github.com/processing/processing4/issues/165 +X https://github.com/processing/processing4/pull/166 +X "textMode(SHAPE) is not supported by this renderer" message +X https://github.com/processing/processing4/issues/202 +X formerly https://github.com/processing/processing/issues/6169 +X add PVector.setHeading() for parity with p5.js +X https://github.com/processing/processing4/issues/193 +o .setAngle() for PVector? +X https://github.com/processing/processing-docs/issues/744 +o Math for BLEND incorrect in the reference? +o https://github.com/processing/processing-docs/issues/762 +o How much of the attrib*() functions should be documented? +o https://github.com/processing/processing-docs/issues/172 + +get less ambitious with PImage +X cursor(PImage) broken everywhere because PImage.getNative() returns null +X https://github.com/processing/processing4/issues/180 +X PImage.resize() not working +X https://github.com/processing/processing4/issues/200 +X two simple examples added to the issue that can be used for tests +X copy() not working correctly +X https://github.com/processing/processing4/issues/169 + +false alarm +X setting which display to use does not work +X https://github.com/processing/processing4/issues/187 +X working, though display numbers might be weird + + 1272 (4.0a3) X loadJSONObject/Array() now return null if the given file was not found X https://github.com/processing/processing/pull/6081 diff --git a/core/library/export.txt b/core/library/export.txt index d15adf5ee..58ddf6078 100644 --- a/core/library/export.txt +++ b/core/library/export.txt @@ -3,19 +3,15 @@ name = Core -application.macosx=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-macosx-universal.jar,gluegen-rt-natives-macosx-universal.jar,javafx-swt.jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.swing.jar,javafx.web.jar -# application.macosx=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-macosx-universal.jar,gluegen-rt-natives-macosx-universal.jar,javafx-swt.jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.swing.jar,javafx.web.jar,libdecora_sse.dylib,libfxplugins.dylib,libglass.dylib,libglib-lite.dylib,libgstreamer-lite.dylib,libjavafx_font.dylib,libjavafx_iio.dylib,libjfxmedia.dylib,libjfxmedia_avf.dylib,libprism_common.dylib,libprism_es2.dylib,libprism_sw.dylib +# had to add the dylib files back for 4.0 alpha 5 (but then moved to separate JFX lib) +#application.macosx=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-macosx-universal.jar,gluegen-rt-natives-macosx-universal.jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.swing.jar,libdecora_sse.dylib,libfxplugins.dylib,libglass.dylib,libglib-lite.dylib,libgstreamer-lite.dylib,libjavafx_font.dylib,libjavafx_iio.dylib,libjfxmedia.dylib,libjfxmedia_avf.dylib,libprism_common.dylib,libprism_es2.dylib,libprism_sw.dylib -# application.windows32=api-ms-win-core-console-l1-1-0.dll,api-ms-win-core-datetime-l1-1-0.dll,api-ms-win-core-debug-l1-1-0.dll,api-ms-win-core-errorhandling-l1-1-0.dll,api-ms-win-core-file-l1-1-0.dll,api-ms-win-core-file-l1-2-0.dll,api-ms-win-core-file-l2-1-0.dll,api-ms-win-core-handle-l1-1-0.dll,api-ms-win-core-heap-l1-1-0.dll,api-ms-win-core-interlocked-l1-1-0.dll,api-ms-win-core-libraryloader-l1-1-0.dll,api-ms-win-core-localization-l1-2-0.dll,api-ms-win-core-memory-l1-1-0.dll,api-ms-win-core-namedpipe-l1-1-0.dll,api-ms-win-core-processenvironment-l1-1-0.dll,api-ms-win-core-processthreads-l1-1-0.dll,api-ms-win-core-processthreads-l1-1-1.dll,api-ms-win-core-profile-l1-1-0.dll,api-ms-win-core-rtlsupport-l1-1-0.dll,api-ms-win-core-string-l1-1-0.dll,api-ms-win-core-synch-l1-1-0.dll,api-ms-win-core-synch-l1-2-0.dll,api-ms-win-core-sysinfo-l1-1-0.dll,api-ms-win-core-timezone-l1-1-0.dll,api-ms-win-core-util-l1-1-0.dll,api-ms-win-crt-conio-l1-1-0.dll,api-ms-win-crt-convert-l1-1-0.dll,api-ms-win-crt-environment-l1-1-0.dll,api-ms-win-crt-filesystem-l1-1-0.dll,api-ms-win-crt-heap-l1-1-0.dll,api-ms-win-crt-locale-l1-1-0.dll,api-ms-win-crt-math-l1-1-0.dll,api-ms-win-crt-multibyte-l1-1-0.dll,api-ms-win-crt-private-l1-1-0.dll,api-ms-win-crt-process-l1-1-0.dll,api-ms-win-crt-runtime-l1-1-0.dll,api-ms-win-crt-stdio-l1-1-0.dll,api-ms-win-crt-string-l1-1-0.dll,api-ms-win-crt-time-l1-1-0.dll,api-ms-win-crt-utility-l1-1-0.dll,concrt140.dll,core.jar,decora_sse.dll,fxplugins.dll,glass.dll,glib-lite.dll,gluegen-rt-natives-linux-aarch64.jar,gluegen-rt-natives-linux-amd64.jar,gluegen-rt-natives-linux-armv6hf.jar,gluegen-rt-natives-linux-i586.jar,gluegen-rt-natives-macosx-universal.jar,gluegen-rt-natives-windows-amd64.jar,gluegen-rt-natives-windows-i586.jar,gluegen-rt.jar,gstreamer-lite.dll,javafx-swt.jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.properties,javafx.swing.jar,javafx.web.jar,javafx_font.dll,javafx_iio.dll,jfxmedia.dll,jfxwebkit.dll,jogl-all-natives-linux-aarch64.jar,jogl-all-natives-linux-amd64.jar,jogl-all-natives-linux-armv6hf.jar,jogl-all-natives-linux-i586.jar,jogl-all-natives-macosx-universal.jar,jogl-all-natives-windows-amd64.jar,jogl-all-natives-windows-i586.jar,jogl-all.jar,msvcp140.dll,prism_common.dll,prism_d3d.dll,prism_sw.dll,ucrtbase.dll,vcruntime140.dll +application.macosx=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-macosx-universal.jar,gluegen-rt-natives-macosx-universal.jar -application.windows64=core.jar,gluegen-rt-natives-windows-amd64.jar,gluegen-rt.jar,javafx-swt.jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.properties,javafx.swing.jar,javafx.web.jar,jogl-all-natives-windows-amd64.jar,jogl-all.jar -# application.windows64=api-ms-win-core-console-l1-1-0.dll,api-ms-win-core-datetime-l1-1-0.dll,api-ms-win-core-debug-l1-1-0.dll,api-ms-win-core-errorhandling-l1-1-0.dll,api-ms-win-core-file-l1-1-0.dll,api-ms-win-core-file-l1-2-0.dll,api-ms-win-core-file-l2-1-0.dll,api-ms-win-core-handle-l1-1-0.dll,api-ms-win-core-heap-l1-1-0.dll,api-ms-win-core-interlocked-l1-1-0.dll,api-ms-win-core-libraryloader-l1-1-0.dll,api-ms-win-core-localization-l1-2-0.dll,api-ms-win-core-memory-l1-1-0.dll,api-ms-win-core-namedpipe-l1-1-0.dll,api-ms-win-core-processenvironment-l1-1-0.dll,api-ms-win-core-processthreads-l1-1-0.dll,api-ms-win-core-processthreads-l1-1-1.dll,api-ms-win-core-profile-l1-1-0.dll,api-ms-win-core-rtlsupport-l1-1-0.dll,api-ms-win-core-string-l1-1-0.dll,api-ms-win-core-synch-l1-1-0.dll,api-ms-win-core-synch-l1-2-0.dll,api-ms-win-core-sysinfo-l1-1-0.dll,api-ms-win-core-timezone-l1-1-0.dll,api-ms-win-core-util-l1-1-0.dll,api-ms-win-crt-conio-l1-1-0.dll,api-ms-win-crt-convert-l1-1-0.dll,api-ms-win-crt-environment-l1-1-0.dll,api-ms-win-crt-filesystem-l1-1-0.dll,api-ms-win-crt-heap-l1-1-0.dll,api-ms-win-crt-locale-l1-1-0.dll,api-ms-win-crt-math-l1-1-0.dll,api-ms-win-crt-multibyte-l1-1-0.dll,api-ms-win-crt-private-l1-1-0.dll,api-ms-win-crt-process-l1-1-0.dll,api-ms-win-crt-runtime-l1-1-0.dll,api-ms-win-crt-stdio-l1-1-0.dll,api-ms-win-crt-string-l1-1-0.dll,api-ms-win-crt-time-l1-1-0.dll,api-ms-win-crt-utility-l1-1-0.dll,concrt140.dll,core.jar,decora_sse.dll,fxplugins.dll,glass.dll,glib-lite.dll,gluegen-rt-natives-linux-aarch64.jar,gluegen-rt-natives-linux-amd64.jar,gluegen-rt-natives-linux-armv6hf.jar,gluegen-rt-natives-linux-i586.jar,gluegen-rt-natives-macosx-universal.jar,gluegen-rt-natives-windows-amd64.jar,gluegen-rt-natives-windows-i586.jar,gluegen-rt.jar,gstreamer-lite.dll,javafx-swt.jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.properties,javafx.swing.jar,javafx.web.jar,javafx_font.dll,javafx_iio.dll,jfxmedia.dll,jfxwebkit.dll,jogl-all-natives-linux-aarch64.jar,jogl-all-natives-linux-amd64.jar,jogl-all-natives-linux-armv6hf.jar,jogl-all-natives-linux-i586.jar,jogl-all-natives-macosx-universal.jar,jogl-all-natives-windows-amd64.jar,jogl-all-natives-windows-i586.jar,jogl-all.jar,msvcp140.dll,prism_common.dll,prism_d3d.dll,prism_sw.dll,ucrtbase.dll,vcruntime140.dll +#application.windows64=core.jar,gluegen-rt-natives-windows-amd64.jar,gluegen-rt.jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.swing.jar,jogl-all-natives-windows-amd64.jar,jogl-all.jar,api-ms-win-core-console-l1-1-0.dll,api-ms-win-core-console-l1-2-0.dll,api-ms-win-core-datetime-l1-1-0.dll,api-ms-win-core-debug-l1-1-0.dll,api-ms-win-core-errorhandling-l1-1-0.dll,api-ms-win-core-file-l1-1-0.dll,api-ms-win-core-file-l1-2-0.dll,api-ms-win-core-file-l2-1-0.dll,api-ms-win-core-handle-l1-1-0.dll,api-ms-win-core-heap-l1-1-0.dll,api-ms-win-core-interlocked-l1-1-0.dll,api-ms-win-core-libraryloader-l1-1-0.dll,api-ms-win-core-localization-l1-2-0.dll,api-ms-win-core-memory-l1-1-0.dll,api-ms-win-core-namedpipe-l1-1-0.dll,api-ms-win-core-processenvironment-l1-1-0.dll,api-ms-win-core-processthreads-l1-1-0.dll,api-ms-win-core-processthreads-l1-1-1.dll,api-ms-win-core-profile-l1-1-0.dll,api-ms-win-core-rtlsupport-l1-1-0.dll,api-ms-win-core-string-l1-1-0.dll,api-ms-win-core-synch-l1-1-0.dll,api-ms-win-core-synch-l1-2-0.dll,api-ms-win-core-sysinfo-l1-1-0.dll,api-ms-win-core-timezone-l1-1-0.dll,api-ms-win-core-util-l1-1-0.dll,api-ms-win-crt-conio-l1-1-0.dll,api-ms-win-crt-convert-l1-1-0.dll,api-ms-win-crt-environment-l1-1-0.dll,api-ms-win-crt-filesystem-l1-1-0.dll,api-ms-win-crt-heap-l1-1-0.dll,api-ms-win-crt-locale-l1-1-0.dll,api-ms-win-crt-math-l1-1-0.dll,api-ms-win-crt-multibyte-l1-1-0.dll,api-ms-win-crt-private-l1-1-0.dll,api-ms-win-crt-process-l1-1-0.dll,api-ms-win-crt-runtime-l1-1-0.dll,api-ms-win-crt-stdio-l1-1-0.dll,api-ms-win-crt-string-l1-1-0.dll,api-ms-win-crt-time-l1-1-0.dll,api-ms-win-crt-utility-l1-1-0.dll,decora_sse.dll,fxplugins.dll,glass.dll,glib-lite.dll,gstreamer-lite.dll,javafx_font.dll,javafx_iio.dll,jfxmedia.dll,msvcp140.dll,prism_common.dll,prism_d3d.dll,prism_sw.dll,ucrtbase.dll,vcruntime140.dll,vcruntime140_1.dll -# application.linux32=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-linux-i586.jar,gluegen-rt-natives-linux-i586.jar,javafx-swt.jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.swing.jar,javafx.web.jar,libavplugin-54.so,libavplugin-56.so,libavplugin-57.so,libavplugin-ffmpeg-56.so,libavplugin-ffmpeg-57.so,libdecora_sse.so,libfxplugins.so,libglass.so,libglassgtk2.so,libglassgtk3.so,libgstreamer-lite.so,libjavafx_font.so,libjavafx_font_freetype.so,libjavafx_font_pango.so,libjavafx_iio.so,libjfxmedia.so,libjfxwebkit.so,libprism_common.so,libprism_es2.so,libprism_sw.so +application.windows64=core.jar,gluegen-rt-natives-windows-amd64.jar,gluegen-rt.jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.swing.jar,jogl-all-natives-windows-amd64.jar,jogl-all.jar -application.linux64=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-linux-amd64.jar,gluegen-rt-natives-linux-amd64.jar,javafx-swt.jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.swing.jar,javafx.web.jar -# application.linux64=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-linux-amd64.jar,gluegen-rt-natives-linux-amd64.jar,javafx-swt.jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.swing.jar,javafx.web.jar,libavplugin-54.so,libavplugin-56.so,libavplugin-57.so,libavplugin-ffmpeg-56.so,libavplugin-ffmpeg-57.so,libdecora_sse.so,libfxplugins.so,libglass.so,libglassgtk2.so,libglassgtk3.so,libgstreamer-lite.so,libjavafx_font.so,libjavafx_font_freetype.so,libjavafx_font_pango.so,libjavafx_iio.so,libjfxmedia.so,libjfxwebkit.so,libprism_common.so,libprism_es2.so,libprism_sw.so +application.linux64=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-linux-amd64.jar,gluegen-rt-natives-linux-amd64.jar -# application.linux-armv6hf=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-linux-armv6hf.jar,gluegen-rt-natives-linux-armv6hf.jar,javafx-swt.jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.swing.jar,javafx.web.jar,libavplugin-54.so,libavplugin-56.so,libavplugin-57.so,libavplugin-ffmpeg-56.so,libavplugin-ffmpeg-57.so,libdecora_sse.so,libfxplugins.so,libglass.so,libglassgtk2.so,libglassgtk3.so,libgstreamer-lite.so,libjavafx_font.so,libjavafx_font_freetype.so,libjavafx_font_pango.so,libjavafx_iio.so,libjfxmedia.so,libjfxwebkit.so,libprism_common.so,libprism_es2.so,libprism_sw.so - -# application.linux-arm64=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-linux-aarch64.jar,gluegen-rt-natives-linux-aarch64.jar,javafx-swt.jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.swing.jar,javafx.web.jar,libavplugin-54.so,libavplugin-56.so,libavplugin-57.so,libavplugin-ffmpeg-56.so,libavplugin-ffmpeg-57.so,libdecora_sse.so,libfxplugins.so,libglass.so,libglassgtk2.so,libglassgtk3.so,libgstreamer-lite.so,libjavafx_font.so,libjavafx_font_freetype.so,libjavafx_font_pango.so,libjavafx_iio.so,libjfxmedia.so,libjfxwebkit.so,libprism_common.so,libprism_es2.so,libprism_sw.so +# application.linux64=core.jar,jogl-all.jar,gluegen-rt.jar,jogl-all-natives-linux-amd64.jar,gluegen-rt-natives-linux-amd64.jar,jar,javafx.base.jar,javafx.controls.jar,javafx.fxml.jar,javafx.graphics.jar,javafx.media.jar,javafx.swing.jar,libavplugin-54.so,libavplugin-56.so,libavplugin-57.so,libavplugin-ffmpeg-56.so,libavplugin-ffmpeg-57.so,libdecora_sse.so,libfxplugins.so,libglass.so,libglassgtk2.so,libglassgtk3.so,libgstreamer-lite.so,libjavafx_font.so,libjavafx_font_freetype.so,libjavafx_font_pango.so,libjavafx_iio.so,libjfxmedia.so,libprism_common.so,libprism_es2.so,libprism_sw.so diff --git a/core/library/javafx-swt.jar b/core/library/javafx-swt.jar deleted file mode 100644 index e40db0ba7..000000000 Binary files a/core/library/javafx-swt.jar and /dev/null differ diff --git a/core/library/javafx.base.jar b/core/library/javafx.base.jar deleted file mode 100644 index c2e6d5cd8..000000000 Binary files a/core/library/javafx.base.jar and /dev/null differ diff --git a/core/library/javafx.controls.jar b/core/library/javafx.controls.jar deleted file mode 100644 index 90487f507..000000000 Binary files a/core/library/javafx.controls.jar and /dev/null differ diff --git a/core/library/javafx.fxml.jar b/core/library/javafx.fxml.jar deleted file mode 100644 index 5785c04e4..000000000 Binary files a/core/library/javafx.fxml.jar and /dev/null differ diff --git a/core/library/javafx.graphics.jar b/core/library/javafx.graphics.jar deleted file mode 100644 index 6ea16205a..000000000 Binary files a/core/library/javafx.graphics.jar and /dev/null differ diff --git a/core/library/javafx.media.jar b/core/library/javafx.media.jar deleted file mode 100644 index 1a375dafc..000000000 Binary files a/core/library/javafx.media.jar and /dev/null differ diff --git a/core/library/javafx.properties b/core/library/javafx.properties deleted file mode 100644 index ec30eae4a..000000000 --- a/core/library/javafx.properties +++ /dev/null @@ -1,3 +0,0 @@ -javafx.version=11.0.1 -javafx.runtime.version=11.0.1+1 -javafx.runtime.build=1 diff --git a/core/library/javafx.swing.jar b/core/library/javafx.swing.jar deleted file mode 100644 index a45fdc76f..000000000 Binary files a/core/library/javafx.swing.jar and /dev/null differ diff --git a/core/library/javafx.web.jar b/core/library/javafx.web.jar deleted file mode 100644 index cbe55be66..000000000 Binary files a/core/library/javafx.web.jar and /dev/null differ diff --git a/core/src/processing/awt/PImageAWT.java b/core/src/processing/awt/PImageAWT.java index b226fc4c6..801069567 100644 --- a/core/src/processing/awt/PImageAWT.java +++ b/core/src/processing/awt/PImageAWT.java @@ -92,11 +92,20 @@ public class PImageAWT extends PImage { } + /** Set the high bits of all pixels to opaque. */ + protected void opaque() { + for (int i = 0; i < pixels.length; i++) { + pixels[i] = 0xFF000000 | pixels[i]; + } + } + + /** * Use the getNative() method instead, which allows library interfaces to be * written in a cross-platform fashion for desktop, Android, and others. * This is still included for PGraphics objects, which may need the image. */ + @Override public Image getImage() { // ignore return (Image) getNative(); } @@ -153,7 +162,7 @@ public class PImageAWT extends PImage { // "Filthy Rich Clients" by Chet Haase and Romain Guy // Additional modifications and simplifications have been added, // plus a fix to deal with an infinite loop if images are expanded. - // http://code.google.com/p/processing/issues/detail?id=1463 + // https://github.com/processing/processing/issues/1501 static private BufferedImage shrinkImage(BufferedImage img, int targetWidth, int targetHeight) { int type = (img.getTransparency() == Transparency.OPAQUE) ? diff --git a/core/src/processing/awt/PSurfaceAWT.java b/core/src/processing/awt/PSurfaceAWT.java index 0bee141d1..b78ce362a 100644 --- a/core/src/processing/awt/PSurfaceAWT.java +++ b/core/src/processing/awt/PSurfaceAWT.java @@ -55,6 +55,7 @@ import processing.core.PConstants; import processing.core.PGraphics; import processing.core.PImage; import processing.core.PSurfaceNone; +import processing.event.Event; import processing.event.KeyEvent; import processing.event.MouseEvent; @@ -70,7 +71,7 @@ public class PSurfaceAWT extends PSurfaceNone { // Trying Frame again with a11 to see if this avoids some Swing nastiness. // In the past, AWT Frames caused some problems on Windows and Linux, // but those may not be a problem for our reworked PSurfaceAWT class. - Frame frame; + JFrame frame; // Note that x and y may not be zero, depending on the display configuration Rectangle screenRect; @@ -187,7 +188,6 @@ public class PSurfaceAWT extends PSurfaceNone { public class SmoothCanvas extends Canvas { private Dimension oldSize = new Dimension(0, 0); - private Dimension newSize = new Dimension(0, 0); // Turns out getParent() returns a JPanel on a JFrame. Yech. @@ -218,69 +218,27 @@ public class PSurfaceAWT extends PSurfaceNone { @Override public void validate() { super.validate(); - newSize.width = getWidth(); - newSize.height = getHeight(); -// if (oldSize.equals(newSize)) { -//// System.out.println("validate() return " + oldSize); -// return; -// } else { + Dimension newSize = getSize(); if (!oldSize.equals(newSize)) { -// System.out.println("validate() render old=" + oldSize + " -> new=" + newSize); oldSize = newSize; sketch.setSize(newSize.width / windowScaleFactor, newSize.height / windowScaleFactor); -// try { render(); -// } catch (IllegalStateException ise) { -// System.out.println(ise.getMessage()); -// } } } @Override public void update(Graphics g) { -// System.out.println("updating"); paint(g); } @Override public void paint(Graphics screen) { -// System.out.println("painting"); -// if (useStrategy) { render(); - /* - if (graphics != null) { - System.out.println("drawing to screen " + canvas); - screen.drawImage(graphics.image, 0, 0, sketchWidth, sketchHeight, null); - } - */ - -// } else { -//// new Exception("painting").printStackTrace(System.out); -//// if (graphics.image != null) { // && !sketch.insideDraw) { -// if (onscreen != null) { -//// synchronized (graphics.image) { -// // Needs the width/height to be set so that retina images are properly scaled down -//// screen.drawImage(graphics.image, 0, 0, sketchWidth, sketchHeight, null); -// synchronized (offscreenLock) { -// screen.drawImage(onscreen, 0, 0, sketchWidth, sketchHeight, null); -// } -// } -// } } } - /* - @Override - public void addNotify() { -// System.out.println("adding notify"); - super.addNotify(); - // prior to Java 7 on OS X, this no longer works [121222] -// createBufferStrategy(2); - } - */ - synchronized protected void render() { if (canvas.isDisplayable() && @@ -291,7 +249,6 @@ public class PSurfaceAWT extends PSurfaceNone { BufferStrategy strategy = canvas.getBufferStrategy(); if (strategy != null) { // Render single frame -// try { do { // The following loop ensures that the contents of the drawing buffer // are consistent in case the underlying surface was recreated @@ -312,65 +269,6 @@ public class PSurfaceAWT extends PSurfaceNone { } - /* - protected void blit() { - // Other folks that call render() (i.e. paint()) are already on the EDT. - // We need to be using the EDT since we're messing with the Canvas - // object and BufferStrategy and friends. - //EventQueue.invokeLater(new Runnable() { - //public void run() { - //((SmoothCanvas) canvas).render(); - //} - //}); - - if (useStrategy) { - // Not necessary to be on the EDT to update BufferStrategy - //((SmoothCanvas) canvas).render(); - render(); - } else { - if (graphics.image != null) { - BufferedImage graphicsImage = (BufferedImage) graphics.image; - if (offscreen == null || - offscreen.getWidth() != graphicsImage.getWidth() || - offscreen.getHeight() != graphicsImage.getHeight()) { - System.out.println("creating new image"); - offscreen = (BufferedImage) - canvas.createImage(graphicsImage.getWidth(), - graphicsImage.getHeight()); -// off = offscreen.getGraphics(); - } -// synchronized (offscreen) { - Graphics2D off = (Graphics2D) offscreen.getGraphics(); -// off.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1)); - off.drawImage(graphicsImage, 0, 0, null); -// } - off.dispose(); - synchronized (offscreenLock) { - BufferedImage temp = onscreen; - onscreen = offscreen; - offscreen = temp; - } - canvas.repaint(); - } - } - } - */ - - - /* - @Override - public int displayDensity() { - return shim.displayDensity(); - } - - - @Override - public int displayDensity(int display) { - return shim.displayDensity(display); - } - */ - - @Override public void selectInput(String prompt, String callback, File file, Object callbackObject) { @@ -398,28 +296,6 @@ public class PSurfaceAWT extends PSurfaceNone { this.sketch = sketch; } - /* - public Frame initOffscreen() { - Frame dummy = new Frame(); - dummy.pack(); // get legit AWT graphics - // but don't show it - return dummy; - } - */ - - /* - @Override - public Component initComponent(PApplet sketch) { - this.sketch = sketch; - - // needed for getPreferredSize() et al - sketchWidth = sketch.sketchWidth(); - sketchHeight = sketch.sketchHeight(); - - return canvas; - } - */ - @Override public void initFrame(final PApplet sketch) {/*, int backgroundColor, @@ -504,11 +380,7 @@ public class PSurfaceAWT extends PSurfaceNone { // backgroundColor = WINDOW_BGCOLOR; // } final Color windowColor = new Color(sketch.sketchWindowColor(), false); - if (frame instanceof JFrame) { - ((JFrame) frame).getContentPane().setBackground(windowColor); - } else { - frame.setBackground(windowColor); - } + frame.getContentPane().setBackground(windowColor); // Put the p5 logo in the Frame's corner to override the Java coffee cup. setProcessingIcon(frame); @@ -561,7 +433,7 @@ public class PSurfaceAWT extends PSurfaceNone { if (fullScreen) { frame.invalidate(); - } else { +// } else { // frame.pack(); } @@ -677,7 +549,7 @@ public class PSurfaceAWT extends PSurfaceNone { } frame.setIconImages(iconImages); - } catch (Exception e) { } // harmless; keep this to ourselves + } catch (Exception ignored) { } // harmless; keep this to ourselves } else { // handle OS X differently if (!dockIconSpecified()) { // don't override existing -Xdock param @@ -1147,27 +1019,24 @@ public class PSurfaceAWT extends PSurfaceNone { * in cases where frame.setResizable(true) is called. */ private void setupFrameResizeListener() { - frame.addWindowStateListener(new WindowStateListener() { - @Override - // Detecting when the frame is resized in order to handle the frame - // maximization bug in OSX: - // http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8036935 - public void windowStateChanged(WindowEvent e) { - // This seems to be firing when dragging the window on OS X + // Detecting when the frame is resized in order to handle the frame +// maximization bug in OSX: +// http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8036935 + frame.addWindowStateListener(e -> { + // This seems to be firing when dragging the window on OS X + // https://github.com/processing/processing/issues/3092 + if (Frame.MAXIMIZED_BOTH == e.getNewState()) { + // Supposedly, sending the frame to back and then front is a + // workaround for this bug: + // http://stackoverflow.com/a/23897602 + // but is not working for me... + //frame.toBack(); + //frame.toFront(); + // Packing the frame works, but that causes the window to collapse + // on OS X when the window is dragged. Changing to addNotify() for // https://github.com/processing/processing/issues/3092 - if (Frame.MAXIMIZED_BOTH == e.getNewState()) { - // Supposedly, sending the frame to back and then front is a - // workaround for this bug: - // http://stackoverflow.com/a/23897602 - // but is not working for me... - //frame.toBack(); - //frame.toFront(); - // Packing the frame works, but that causes the window to collapse - // on OS X when the window is dragged. Changing to addNotify() for - // https://github.com/processing/processing/issues/3092 - //frame.pack(); - frame.addNotify(); - } + //frame.pack(); + frame.addNotify(); } }); @@ -1319,21 +1188,39 @@ public class PSurfaceAWT extends PSurfaceNone { // Switching to getModifiersEx() for 4.0a2 because of Java 9 deprecation. // Had trouble with this in the past and rolled it back because it was // optional at the time. This time around, just need to iron out the issue. - // http://code.google.com/p/processing/issues/detail?id=1294 - // http://code.google.com/p/processing/issues/detail?id=1332 + // https://github.com/processing/processing/issues/1332 + // https://github.com/processing/processing/issues/1370 int modifiers = nativeEvent.getModifiersEx(); int peButton = 0; - if ((modifiers & InputEvent.BUTTON1_DOWN_MASK) != 0) { + // Technically should be java.awt.event.MouseEvent.BUTTON1 through BUTTON3 + // but those are equal to 1, 2, and 3, and this is more readable. + if (nativeEvent.getButton() == 1) { peButton = PConstants.LEFT; - } else if ((modifiers & InputEvent.BUTTON2_DOWN_MASK) != 0) { + } else if (nativeEvent.getButton() == 2) { peButton = PConstants.CENTER; - } else if ((modifiers & InputEvent.BUTTON3_DOWN_MASK) != 0) { + } else if (nativeEvent.getButton() == 3) { peButton = PConstants.RIGHT; } + // getModifiersEx() has different constants, so need to re-map + // to the masks we're using in processing.event.Event + int peModifiers = 0; + if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0) { + peModifiers |= Event.SHIFT; + } + if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0) { + peModifiers |= Event.CTRL; + } + if ((modifiers & InputEvent.META_DOWN_MASK) != 0) { + peModifiers |= Event.META; + } + if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0) { + peModifiers |= Event.ALT; + } + sketch.postEvent(new MouseEvent(nativeEvent, nativeEvent.getWhen(), - peAction, modifiers, + peAction, peModifiers, nativeEvent.getX() / windowScaleFactor, nativeEvent.getY() / windowScaleFactor, peButton, @@ -1357,21 +1244,24 @@ public class PSurfaceAWT extends PSurfaceNone { int modifiers = event.getModifiersEx(); - /* -// int peModifiers = event.getModifiersEx() & -// (InputEvent.SHIFT_DOWN_MASK | -// InputEvent.CTRL_DOWN_MASK | -// InputEvent.META_DOWN_MASK | -// InputEvent.ALT_DOWN_MASK); - int peModifiers = event.getModifiers() & - (InputEvent.SHIFT_MASK | - InputEvent.CTRL_MASK | - InputEvent.META_MASK | - InputEvent.ALT_MASK); - */ - + // getModifiersEx() has different constants, so need to re-map + // to the masks we're using in processing.event.Event. + // If authors want more detail, they can use the native object. + int peModifiers = 0; + if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0) { + peModifiers |= Event.SHIFT; + } + if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0) { + peModifiers |= Event.CTRL; + } + if ((modifiers & InputEvent.META_DOWN_MASK) != 0) { + peModifiers |= Event.META; + } + if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0) { + peModifiers |= Event.ALT; + } sketch.postEvent(new KeyEvent(event, event.getWhen(), - peAction, modifiers, + peAction, peModifiers, event.getKeyChar(), event.getKeyCode())); } @@ -1413,12 +1303,7 @@ public class PSurfaceAWT extends PSurfaceNone { } }); - canvas.addMouseWheelListener(new MouseWheelListener() { - - public void mouseWheelMoved(MouseWheelEvent e) { - nativeMouseEvent(e); - } - }); + canvas.addMouseWheelListener(this::nativeMouseEvent); canvas.addKeyListener(new KeyListener() { @@ -1426,12 +1311,10 @@ public class PSurfaceAWT extends PSurfaceNone { nativeKeyEvent(e); } - public void keyReleased(java.awt.event.KeyEvent e) { nativeKeyEvent(e); } - public void keyTyped(java.awt.event.KeyEvent e) { nativeKeyEvent(e); } @@ -1452,37 +1335,6 @@ public class PSurfaceAWT extends PSurfaceNone { } - /* - public void addListeners(Component comp) { - comp.addMouseListener(this); - comp.addMouseWheelListener(this); - comp.addMouseMotionListener(this); - comp.addKeyListener(this); - comp.addFocusListener(this); - } - - - public void removeListeners(Component comp) { - comp.removeMouseListener(this); - comp.removeMouseWheelListener(this); - comp.removeMouseMotionListener(this); - comp.removeKeyListener(this); - comp.removeFocusListener(this); - } - */ - - -// /** -// * Call to remove, then add, listeners to a component. -// * Avoids issues with double-adding. -// */ -// public void updateListeners(Component comp) { -// removeListeners(comp); -// addListeners(comp); -// } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -1569,9 +1421,4 @@ public class PSurfaceAWT extends PSurfaceNone { } }; } - - - void debug(String format, Object ... args) { - System.out.format(format + "%n", args); - } } diff --git a/core/src/processing/awt/ShimAWT.java b/core/src/processing/awt/ShimAWT.java index 17a9d72aa..3b9582c34 100644 --- a/core/src/processing/awt/ShimAWT.java +++ b/core/src/processing/awt/ShimAWT.java @@ -1,14 +1,8 @@ package processing.awt; -import java.awt.Desktop; -import java.awt.EventQueue; -import java.awt.FileDialog; -import java.awt.Frame; -import java.awt.HeadlessException; -import java.awt.Image; -import java.awt.Toolkit; +import java.awt.*; import java.awt.color.ColorSpace; -import java.awt.image.BufferedImage; +import java.awt.image.*; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; @@ -16,10 +10,6 @@ import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.Iterator; -import java.awt.DisplayMode; -import java.awt.GraphicsConfiguration; -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; import java.awt.geom.AffineTransform; import javax.imageio.IIOImage; @@ -156,6 +146,182 @@ public class ShimAWT implements PConstants { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + static public void fromNativeImage(Image img, PImage out) { + out.format = RGB; + out.pixels = null; + + if (img instanceof BufferedImage) { + BufferedImage bi = (BufferedImage) img; + out.width = bi.getWidth(); + out.height = bi.getHeight(); + + int type = bi.getType(); + if (type == BufferedImage.TYPE_3BYTE_BGR || + type == BufferedImage.TYPE_4BYTE_ABGR) { + out.pixels = new int[out.width * out.height]; + bi.getRGB(0, 0, out.width, out.height, out.pixels, 0, out.width); + if (type == BufferedImage.TYPE_4BYTE_ABGR) { + out.format = ARGB; +// } else { +// opaque(); + } + } else { + DataBuffer db = bi.getRaster().getDataBuffer(); + if (db instanceof DataBufferInt) { + out.pixels = ((DataBufferInt) db).getData(); + if (type == BufferedImage.TYPE_INT_ARGB) { + out.format = ARGB; +// } else if (type == BufferedImage.TYPE_INT_RGB) { +// opaque(); + } + } + } + } + if ((out.pixels != null) && (out.format == RGB)) { + for (int i = 0; i < out.pixels.length; i++) { + out.pixels[i] |= 0xFF000000; + } + } + // Implements fall-through if not DataBufferInt above, or not a + // known type, or not DataBufferInt for the data itself. + if (out.pixels == null) { // go the old school Java 1.0 route + out.width = img.getWidth(null); + out.height = img.getHeight(null); + out.pixels = new int[out.width * out.height]; + PixelGrabber pg = + new PixelGrabber(img, 0, 0, out.width, out.height, out.pixels, 0, out.width); + try { + pg.grabPixels(); + } catch (InterruptedException e) { } + } + out.pixelDensity = 1; + out.pixelWidth = out.width; + out.pixelHeight = out.height; + } + + +// /** Set the high bits of all pixels to opaque. */ +// protected void opaque() { +// for (int i = 0; i < pixels.length; i++) { +// pixels[i] = 0xFF000000 | pixels[i]; +// } +// } + + + static public Object getNativeImage(PImage img) { + img.loadPixels(); + int type = (img.format == RGB) ? + BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; + BufferedImage image = + new BufferedImage(img.pixelWidth, img.pixelHeight, type); + WritableRaster wr = image.getRaster(); + wr.setDataElements(0, 0, img.pixelWidth, img.pixelHeight, img.pixels); + return image; + } + + + static public void resizeImage(PImage img, int w, int h) { // ignore + if (w <= 0 && h <= 0) { + throw new IllegalArgumentException("width or height must be > 0 for resize"); + } + + if (w == 0) { // Use height to determine relative size + float diff = (float) h / (float) img.height; + w = (int) (img.width * diff); + } else if (h == 0) { // Use the width to determine relative size + float diff = (float) w / (float) img.width; + h = (int) (img.height * diff); + } + + BufferedImage bimg = + shrinkImage((BufferedImage) img.getNative(), w*img.pixelDensity, h*img.pixelDensity); + + PImage temp = new PImageAWT(bimg); + img.pixelWidth = temp.width; + img.pixelHeight = temp.height; + + // Get the resized pixel array + img.pixels = temp.pixels; + + img.width = img.pixelWidth / img.pixelDensity; + img.height = img.pixelHeight / img.pixelDensity; + + // Mark the pixels array as altered + img.updatePixels(); + } + + + // Adapted from getFasterScaledInstance() method from page 111 of + // "Filthy Rich Clients" by Chet Haase and Romain Guy + // Additional modifications and simplifications have been added, + // plus a fix to deal with an infinite loop if images are expanded. + // https://github.com/processing/processing/issues/1501 + static private BufferedImage shrinkImage(BufferedImage img, + int targetWidth, int targetHeight) { + int type = (img.getTransparency() == Transparency.OPAQUE) ? + BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; + BufferedImage outgoing = img; + BufferedImage scratchImage = null; + Graphics2D g2 = null; + int prevW = outgoing.getWidth(); + int prevH = outgoing.getHeight(); + boolean isTranslucent = img.getTransparency() != Transparency.OPAQUE; + + // Use multi-step technique: start with original size, then scale down in + // multiple passes with drawImage() until the target size is reached + int w = img.getWidth(); + int h = img.getHeight(); + + do { + if (w > targetWidth) { + w /= 2; + // if this is the last step, do the exact size + if (w < targetWidth) { + w = targetWidth; + } + } else if (targetWidth >= w) { + w = targetWidth; + } + if (h > targetHeight) { + h /= 2; + if (h < targetHeight) { + h = targetHeight; + } + } else if (targetHeight >= h) { + h = targetHeight; + } + if (scratchImage == null || isTranslucent) { + // Use a single scratch buffer for all iterations and then copy + // to the final, correctly-sized image before returning + scratchImage = new BufferedImage(w, h, type); + g2 = scratchImage.createGraphics(); + } + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2.drawImage(outgoing, 0, 0, w, h, 0, 0, prevW, prevH, null); + prevW = w; + prevH = h; + outgoing = scratchImage; + } while (w != targetWidth || h != targetHeight); + + if (g2 != null) { + g2.dispose(); + } + + // If we used a scratch buffer that is larger than our target size, + // create an image of the right size and copy the results into it + if (targetWidth != outgoing.getWidth() || + targetHeight != outgoing.getHeight()) { + scratchImage = new BufferedImage(targetWidth, targetHeight, type); + g2 = scratchImage.createGraphics(); + g2.drawImage(outgoing, 0, 0, null); + g2.dispose(); + outgoing = scratchImage; + } + return outgoing; + } + + static protected String[] loadImageFormats; // list of ImageIO formats @@ -509,7 +675,7 @@ public class ShimAWT implements PConstants { /** - * @param display the display number to check + * display - the display number to check * (1-indexed to match the Preferences dialog box) */ /* diff --git a/core/src/processing/core/PApplet.java b/core/src/processing/core/PApplet.java index d6ec2c977..d62ea3b4c 100644 --- a/core/src/processing/core/PApplet.java +++ b/core/src/processing/core/PApplet.java @@ -81,7 +81,9 @@ import processing.opengl.*; * project of our (tiny) size, we should be focusing on the future, rather * than working around legacy Java code. */ +@SuppressWarnings({"unused", "FinalStaticMethod"}) public class PApplet implements PConstants { +//public class PApplet extends PSketch { // possible in the next alpha /** Full name of the Java version (i.e. 1.5.0_11). */ static public final String javaVersionName = System.getProperty("java.version"); @@ -294,7 +296,7 @@ public class PApplet implements PConstants { * be pixelWidth*pixelHeight, not width*height. * * @webref environment - * @webBrief The actual pixel heigh when using high resolution display + * @webBrief The actual pixel height when using high resolution display * @see PApplet#pixelWidth * @see #pixelDensity(int) * @see #displayDensity() @@ -521,7 +523,7 @@ public class PApplet implements PConstants { // been released already. Otherwise the events are inconsistent, e.g. // Left Pressed - Left Drag - CTRL Pressed - Right Drag - Right Released. // See: https://github.com/processing/processing/issues/5672 - private boolean macosxLeftButtonWithCtrlPressed; + private boolean macosCtrlClick; /** @deprecated Use a mouse event handler that passes an event instead. */ @@ -724,7 +726,7 @@ public class PApplet implements PConstants { // messages to send if attached as an external vm /** - * Position of the upper-lefthand corner of the editor window + * Position of the upper left-hand corner of the editor window * that launched this applet. */ static public final String ARGS_EDITOR_LOCATION = "--editor-location"; @@ -1342,7 +1344,7 @@ public class PApplet implements PConstants { Map registerMap = new ConcurrentHashMap<>(); - class RegisteredMethod { + static class RegisteredMethod { Object object; Method method; @@ -1394,6 +1396,7 @@ public class PApplet implements PConstants { } // Clear the entries queued for removal (if any) for (Object object : removals) { + //noinspection SuspiciousMethodCalls entries.remove(object); } removals = null; // clear this out @@ -1401,6 +1404,7 @@ public class PApplet implements PConstants { void add(Object object, Method method) { + //noinspection SuspiciousMethodCalls if (!entries.contains(object)) { entries.add(new RegisteredMethod(object, method)); } else { @@ -1417,6 +1421,7 @@ public class PApplet implements PConstants { */ public void remove(Object object) { if (removals == null) { + //noinspection SuspiciousMethodCalls entries.remove(object); } else { // Currently iterating the list of methods, remove this afterwards @@ -1533,7 +1538,7 @@ public class PApplet implements PConstants { /** * * The setup() function is run once, when the program starts. It's used - * to define initial enviroment properties such as screen size and to load media + * to define initial environment properties such as screen size and to load media * such as images and fonts as the program starts. There can only be one * setup() function for each program and it shouldn't be called again * after its initial execution.
@@ -1827,72 +1832,6 @@ public class PApplet implements PConstants { this.outputPath = path; } } - - /* - if (!renderer.equals(sketchRenderer())) { - if (external) { - // The PDE should have parsed it, but something still went wrong - final String msg = - String.format("Something bad happened when calling " + - "size(%d, %d, %s, %s)", w, h, renderer, path); - throw new RuntimeException(msg); - - } else { - System.err.println("Because you're not running from the PDE, add this to your code:"); - System.err.println("public String sketchRenderer() {"); - System.err.println(" return \"" + renderer + "\";"); - System.err.println("}"); - throw new RuntimeException("The sketchRenderer() method is not implemented."); - } - } - */ - - // size() shouldn't actually do anything here [3.0a8] -// surface.setSize(w, h); - // this won't be absolute, which will piss off PDF [3.0a8] -// g.setPath(path); // finally, a path - -// // Run this from the EDT, just cuz it's AWT stuff (or maybe later Swing) -// EventQueue.invokeLater(new Runnable() { -// public void run() { -// // Set the preferred size so that the layout managers can handle it -// setPreferredSize(new Dimension(w, h)); -// setSize(w, h); -// } -// }); -// -// // ensure that this is an absolute path -// if (path != null) path = savePath(path); -// -// String currentRenderer = g.getClass().getName(); -// if (currentRenderer.equals(renderer)) { -//// // Avoid infinite loop of throwing exception to reset renderer -//// resizeRenderer(w, h); -// surface.setSize(w, h); -// -// } else { // renderer change attempted -// // no longer kosher with 3.0a5 -// throw new RuntimeException("Y'all need to implement sketchRenderer()"); -// /* -// // otherwise ok to fall through and create renderer below -// // the renderer is changing, so need to create a new object -// g = makeGraphics(w, h, renderer, path, true); -// this.width = w; -// this.height = h; -// -// // fire resize event to make sure the applet is the proper size -//// setSize(iwidth, iheight); -// // this is the function that will run if the user does their own -// // size() command inside setup, so set defaultSize to false. -// defaultSize = false; -// -// // throw an exception so that setup() is called again -// // but with a properly sized render -// // this is for opengl, which needs a valid, properly sized -// // display before calling anything inside setup(). -// throw new RendererChangeException(); -// */ -// } } @@ -2009,24 +1948,9 @@ public class PApplet implements PConstants { public PGraphics createGraphics(int w, int h, String renderer, String path) { return makeGraphics(w, h, renderer, path, false); - /* - if (path != null) { - path = savePath(path); - } - PGraphics pg = makeGraphics(w, h, renderer, path, false); - //pg.parent = this; // why wasn't setParent() used before 3.0a6? - //pg.setParent(this); // make save() work - // Nevermind, parent is set in makeGraphics() - return pg; - */ } -// public PGraphics makePrimaryGraphics(int wide, int high) { -// return makeGraphics(wide, high, sketchRenderer(), null, true); -// } - - /** * Version of createGraphics() used internally. * @param path A path (or null if none), can be absolute or relative ({@link PApplet#savePath} will be called) @@ -2034,14 +1958,6 @@ public class PApplet implements PConstants { protected PGraphics makeGraphics(int w, int h, String renderer, String path, boolean primary) { -// String openglError = external ? -// // This first one should no longer be possible -// "Before using OpenGL, first select " + -// "Import Library > OpenGL from the Sketch menu." : -// // Welcome to Java programming! The training wheels are off. -// "The Java classpath and native library path is not " + -// "properly set for using the OpenGL library."; - if (!primary && !g.isGL()) { if (renderer.equals(P2D)) { throw new RuntimeException("createGraphics() with P2D requires size() to use P2D or P3D"); @@ -2092,12 +2008,12 @@ public class PApplet implements PConstants { } } catch (ClassNotFoundException cnfe) { -// if (cnfe.getMessage().indexOf("processing.opengl.PGraphicsOpenGL") != -1) { -// throw new RuntimeException(openglError + -// " (The library .jar file is missing.)"); -// } else { + // Clarify the error message for less confusion on 4.x + if (renderer.equals(FX2D)) { + renderer = "JavaFX"; + } if (external) { - throw new RuntimeException("You need to use \"Import Library\" " + + throw new RuntimeException("Please use Sketch \u2192 Import Library " + "to add " + renderer + " to your sketch."); } else { throw new RuntimeException("The " + renderer + @@ -2466,13 +2382,13 @@ public class PApplet implements PConstants { * overloaded to do something more useful. */ protected void handleMouseEvent(MouseEvent event) { - // http://dev.processing.org/bugs/show_bug.cgi?id=170 + // https://processing.org/bugs/bugzilla/170.html // also prevents mouseExited() on the mac from hosing the mouse // position, because x/y are bizarre values on the exit event. // see also the id check below.. both of these go together. // Not necessary to set mouseX/Y on RELEASE events because the // actual position will have been set by a PRESS or DRAG event. - // However, PRESS events might come without a preceeding move, + // However, PRESS events might come without a preceding move, // if the sketch window gains focus on that PRESS. final int action = event.getAction(); if (action == MouseEvent.DRAG || @@ -2486,20 +2402,33 @@ public class PApplet implements PConstants { int button = event.getButton(); - // If running on Mac OS, allow ctrl-click as right mouse. - if (PApplet.platform == PConstants.MACOS && event.getButton() == PConstants.LEFT) { + // If running on Mac OS, allow ctrl-click as right mouse click. + // Handled inside PApplet so that the same logic need not be redone + // for each Surface independently, since the code seems to be identical: + // no native code backing Surface objects (AWT, JavaFX, JOGL) handle it. + if (PApplet.platform == PConstants.MACOS && + button == PConstants.LEFT) { if (action == MouseEvent.PRESS && event.isControlDown()) { - macosxLeftButtonWithCtrlPressed = true; + // The ctrl key may only be down during the press, but we need to store + // it so that the drag or release still is considered a right-click. + macosCtrlClick = true; } - if (macosxLeftButtonWithCtrlPressed) { + if (macosCtrlClick) { button = PConstants.RIGHT; + // Recreate the Event object as a right-click, and unset the CTRL flag, + // since it's not a ctrl-right-click, it's just a right click. + int modifiers = event.getModifiers() & ~Event.CTRL; event = new MouseEvent(event.getNative(), event.getMillis(), - event.getAction(), event.getModifiers(), + event.getAction(), modifiers, event.getX(), event.getY(), button, event.getCount()); } - if (action == MouseEvent.RELEASE) { - macosxLeftButtonWithCtrlPressed = false; + if (action == MouseEvent.CLICK) { + // Un-set the variable for the next time around. + // (This won't affect the current event being handled.) + // Changed to CLICK instead of RELEASE for 4.0a6, because the click + // event will fire after the press/drag/release events have fired. + macosCtrlClick = false; } } @@ -3652,7 +3581,7 @@ public class PApplet implements PConstants { * Called to dispose of resources and shut down the sketch. * Destroys the thread, dispose the renderer,and notify listeners. *

- * Not to be called or overriden by users. If called multiple times, + * Not to be called or overridden by users. If called multiple times, * will only notify listeners once. Register a dispose listener instead. */ public void dispose() { @@ -3912,14 +3841,6 @@ public class PApplet implements PConstants { * (Issue * 3791). * - *

Advanced

Set a custom cursor to an image with a specific hotspot. - * Only works with JDK 1.2 and later. Currently seems to be broken on Java 1.4 - * for Mac OS X - *

- * Based on code contributed by Amit Pitaru, plus additional code to handle - * Java versions via reflection by Jonathan Feinberg. Reflection removed for - * release 0128 and later. - * * @webref environment * @webBrief Sets the cursor to a predefined symbol, an image, or makes it * visible if already hidden @@ -4048,25 +3969,13 @@ public class PApplet implements PConstants { if (o == null) { sb.append("null"); } else { - sb.append(o.toString()); + sb.append(o); } } - System.out.print(sb.toString()); + System.out.print(sb); } - /* - static public void print(Object what) { - if (what == null) { - // special case since this does fuggly things on > 1.1 - System.out.print("null"); - } else { - System.out.println(what.toString()); - } - } - */ - - /** * * The println() function writes to the console area, the black @@ -4176,7 +4085,7 @@ public class PApplet implements PConstants { } else if (what.getClass().isArray()) { printArray(what); } else { - System.out.println(what.toString()); + System.out.println(what); System.out.flush(); } } @@ -4202,7 +4111,7 @@ public class PApplet implements PConstants { */ static public void printArray(Object what) { if (what == null) { - // special case since this does fuggly things on > 1.1 + // special case since this does fugly things on > 1.1 System.out.println("null"); } else { @@ -5257,7 +5166,7 @@ public class PApplet implements PConstants { // [toxi 040903] // make perlin noise quality user controlled to allow // for different levels of detail. lower values will produce - // smoother results as higher octaves are surpressed + // smoother results as higher octaves are suppressed /** * @@ -5469,26 +5378,6 @@ public class PApplet implements PConstants { // DATA I/O -// /** -// * @webref input:files -// * @brief Creates a new XML object -// * @param name the name to be given to the root element of the new XML object -// * @return an XML object, or null -// * @see XML -// * @see PApplet#loadXML(String) -// * @see PApplet#parseXML(String) -// * @see PApplet#saveXML(XML, String) -// */ -// public XML createXML(String name) { -// try { -// return new XML(name); -// } catch (Exception e) { -// e.printStackTrace(); -// return null; -// } -// } - - /** * Reads the contents of a file or URL and creates an XML * object with its values. If a file is specified, it must @@ -5837,18 +5726,6 @@ public class PApplet implements PConstants { } - -// /** -// * @webref input:files -// * @see Table -// * @see PApplet#loadTable(String) -// * @see PApplet#saveTable(Table, String) -// */ -// public Table createTable() { -// return new Table(); -// } - - /** * Reads the contents of a file or URL and creates an Table object with its * values. If a file is specified, it must be located in the sketch's "data" @@ -6069,7 +5946,7 @@ public class PApplet implements PConstants { * distributed should be included with a sketch.
*
* The size parameter states the font size you want to generate. The - * smooth parameter specifies if the font should be antialiased or not. + * smooth parameter specifies if the font should be anti-aliased or not. * The charset parameter is an array of chars that specifies the * characters to generate.
*
@@ -6091,7 +5968,7 @@ public class PApplet implements PConstants { * @param size * point size of the font * @param smooth - * true for an antialiased font, false for aliased + * true for an anti-aliased font, false for aliased * @param charset * array containing characters to be generated * @see PFont @@ -6114,32 +5991,6 @@ public class PApplet implements PConstants { // FILE/FOLDER SELECTION - /* - private Frame selectFrame; - - private Frame selectFrame() { - if (frame != null) { - selectFrame = frame; - - } else if (selectFrame == null) { - Component comp = getParent(); - while (comp != null) { - if (comp instanceof Frame) { - selectFrame = (Frame) comp; - break; - } - comp = comp.getParent(); - } - // Who you callin' a hack? - if (selectFrame == null) { - selectFrame = new Frame(); - } - } - return selectFrame; - } - */ - - /** * Open a platform-specific file chooser dialog to select a file for input. * After the selection is made, the selected File will be passed to the @@ -6157,7 +6008,7 @@ public class PApplet implements PConstants { * if (selection == null) { * println("Window was closed or the user hit cancel."); * } else { - * println("User selected " + fileSeleted.getAbsolutePath()); + * println("User selected " + fileSelected.getAbsolutePath()); * } * } * @@ -6482,7 +6333,8 @@ public class PApplet implements PConstants { files = false; } else if (opt.equals("hidden")) { hidden = true; - } else if (opt.equals("relative")) { + } else //noinspection StatementWithEmptyBody + if (opt.equals("relative")) { // ignored } else { throw new RuntimeException(opt + " is not a listFiles() option"); @@ -6558,7 +6410,7 @@ public class PApplet implements PConstants { */ static public String checkExtension(String filename) { // Don't consider the .gz as part of the name, createInput() - // and createOuput() will take care of fixing that up. + // and createOutput() will take care of fixing that up. if (filename.toLowerCase().endsWith(".gz")) { filename = filename.substring(0, filename.length() - 3); } @@ -7756,7 +7608,7 @@ public class PApplet implements PConstants { if (platform == WINDOWS && !disableAWT) { desktopFolder = ShimAWT.getWindowsDesktop(); } else { - throw new UnsupportedOperationException("Could not find a suitable Desktop foldder"); + throw new UnsupportedOperationException("Could not find a suitable Desktop folder"); } } } @@ -8015,6 +7867,7 @@ public class PApplet implements PConstants { * number of array elements to be copied * @see PApplet#concat(boolean[], boolean[]) */ + @SuppressWarnings("SuspiciousSystemArraycopy") static public void arrayCopy(Object src, int srcPosition, Object dst, int dstPosition, int length) { @@ -8025,6 +7878,7 @@ public class PApplet implements PConstants { * Convenience method for arraycopy(). * Identical to arraycopy(src, 0, dst, 0, length); */ + @SuppressWarnings("SuspiciousSystemArraycopy") static public void arrayCopy(Object src, Object dst, int length) { System.arraycopy(src, 0, dst, 0, length); } @@ -8034,6 +7888,7 @@ public class PApplet implements PConstants { * the source into the destination array. * Identical to arraycopy(src, 0, dst, 0, src.length); */ + @SuppressWarnings("SuspiciousSystemArraycopy") static public void arrayCopy(Object src, Object dst) { System.arraycopy(src, 0, dst, 0, Array.getLength(src)); } @@ -8041,6 +7896,7 @@ public class PApplet implements PConstants { /** * Use arrayCopy() instead. */ + @SuppressWarnings("SuspiciousSystemArraycopy") @Deprecated static public void arraycopy(Object src, int srcPosition, Object dst, int dstPosition, @@ -8051,6 +7907,7 @@ public class PApplet implements PConstants { /** * Use arrayCopy() instead. */ + @SuppressWarnings("SuspiciousSystemArraycopy") @Deprecated static public void arraycopy(Object src, Object dst, int length) { System.arraycopy(src, 0, dst, 0, length); @@ -8059,6 +7916,7 @@ public class PApplet implements PConstants { /** * Use arrayCopy() instead. */ + @SuppressWarnings("SuspiciousSystemArraycopy") @Deprecated static public void arraycopy(Object src, Object dst) { System.arraycopy(src, 0, dst, 0, Array.getLength(src)); @@ -8174,6 +8032,7 @@ public class PApplet implements PConstants { return expand(array, len > 0 ? len << 1 : 1); } + @SuppressWarnings("SuspiciousSystemArraycopy") static public Object expand(Object list, int newSize) { Class type = list.getClass().getComponentType(); Object temp = Array.newInstance(type, newSize); @@ -8432,9 +8291,10 @@ public class PApplet implements PConstants { return outgoing; } + @SuppressWarnings("SuspiciousSystemArraycopy") static final public Object splice(Object list, Object value, int index) { Class type = list.getClass().getComponentType(); - Object outgoing = null; + Object outgoing; int length = Array.getLength(list); // check whether item being spliced in is an array @@ -8581,6 +8441,7 @@ public class PApplet implements PConstants { } + @SuppressWarnings("SuspiciousSystemArraycopy") static public Object subset(Object list, int start, int count) { Class type = list.getClass().getComponentType(); Object outgoing = Array.newInstance(type, count); @@ -8650,6 +8511,7 @@ public class PApplet implements PConstants { return c; } + @SuppressWarnings("SuspiciousSystemArraycopy") static public Object concat(Object a, Object b) { Class type = a.getClass().getComponentType(); int alength = Array.getLength(a); @@ -8875,7 +8737,7 @@ public class PApplet implements PConstants { * parameter divides the str parameter. Therefore, if you use * characters such parentheses and brackets that are used with regular * expressions as a part of the delim parameter, you'll need to put two - * blackslashes (\\\\) in front of the character (see example above). You can + * backslashes (\\\\) in front of the character (see example above). You can * read more about * regular * expressions and @@ -8904,7 +8766,7 @@ public class PApplet implements PConstants { } // make sure that there is something in the input string //if (chars.length > 0) { - // if the last char is a delimeter, get rid of it.. + // if the last char is a delimiter, get rid of it.. //if (chars[chars.length-1] == delim) splitCount--; // on second thought, i don't agree with this, will disable //} @@ -9092,16 +8954,6 @@ public class PApplet implements PConstants { // CASTING FUNCTIONS, INSERTED BY PREPROC - /** - * Convert a char to a boolean. 'T', 't', and '1' will become the - * boolean value true, while 'F', 'f', or '0' will become false. - */ - /* - static final public boolean parseBoolean(char what) { - return ((what == 't') || (what == 'T') || (what == '1')); - } - */ - /** *

Convert an integer to a boolean. Because of how Java handles upgrading * numbers, this will also cover byte and char (as they will upgrade to @@ -9113,13 +8965,6 @@ public class PApplet implements PConstants { return (what != 0); } - /* - // removed because this makes no useful sense - static final public boolean parseBoolean(float what) { - return (what != 0); - } - */ - /** * Convert the string "true" or "false" to a boolean. * @return true if 'what' is "true" or "TRUE", false otherwise @@ -9130,34 +8975,6 @@ public class PApplet implements PConstants { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - /* - // removed, no need to introduce strange syntax from other languages - static final public boolean[] parseBoolean(char what[]) { - boolean outgoing[] = new boolean[what.length]; - for (int i = 0; i < what.length; i++) { - outgoing[i] = - ((what[i] == 't') || (what[i] == 'T') || (what[i] == '1')); - } - return outgoing; - } - */ - - /** - * Convert a byte array to a boolean array. Each element will be - * evaluated identical to the integer case, where a byte equal - * to zero will return false, and any other value will return true. - * @return array of boolean elements - */ - /* - static final public boolean[] parseBoolean(byte what[]) { - boolean outgoing[] = new boolean[what.length]; - for (int i = 0; i < what.length; i++) { - outgoing[i] = (what[i] != 0); - } - return outgoing; - } - */ - /** * Convert an int array to a boolean array. An int equal * to zero will return false, and any other value will return true. @@ -9171,17 +8988,6 @@ public class PApplet implements PConstants { return outgoing; } - /* - // removed, not necessary... if necessary, convert to int array first - static final public boolean[] parseBoolean(float what[]) { - boolean outgoing[] = new boolean[what.length]; - for (int i = 0; i < what.length; i++) { - outgoing[i] = (what[i] != 0); - } - return outgoing; - } - */ - static final public boolean[] parseBoolean(String[] what) { boolean[] outgoing = new boolean[what.length]; for (int i = 0; i < what.length; i++) { @@ -9208,13 +9014,6 @@ public class PApplet implements PConstants { return (byte) what; } - /* - // nixed, no precedent - static final public byte[] parseByte(String what) { // note: array[] - return what.getBytes(); - } - */ - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . static final public byte[] parseByte(boolean[] what) { @@ -9249,24 +9048,8 @@ public class PApplet implements PConstants { return outgoing; } - /* - static final public byte[][] parseByte(String what[]) { // note: array[][] - byte outgoing[][] = new byte[what.length][]; - for (int i = 0; i < what.length; i++) { - outgoing[i] = what[i].getBytes(); - } - return outgoing; - } - */ - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - /* - static final public char parseChar(boolean what) { // 0/1 or T/F ? - return what ? 't' : 'f'; - } - */ - static final public char parseChar(byte what) { return (char) (what & 0xff); } @@ -9275,28 +9058,8 @@ public class PApplet implements PConstants { return (char) what; } - /* - static final public char parseChar(float what) { // nonsensical - return (char) what; - } - - static final public char[] parseChar(String what) { // note: array[] - return what.toCharArray(); - } - */ - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - /* - static final public char[] parseChar(boolean what[]) { // 0/1 or T/F ? - char outgoing[] = new char[what.length]; - for (int i = 0; i < what.length; i++) { - outgoing[i] = what[i] ? 't' : 'f'; - } - return outgoing; - } - */ - static final public char[] parseChar(byte[] what) { char[] outgoing = new char[what.length]; for (int i = 0; i < what.length; i++) { @@ -9305,15 +9068,6 @@ public class PApplet implements PConstants { return outgoing; } - static final public char[] parseChar(int what[]) { - char outgoing[] = new char[what.length]; - for (int i = 0; i < what.length; i++) { - outgoing[i] = (char) what[i]; - } - return outgoing; - } - - /* static final public char[] parseChar(int[] what) { char[] outgoing = new char[what.length]; for (int i = 0; i < what.length; i++) { @@ -9322,15 +9076,6 @@ public class PApplet implements PConstants { return outgoing; } - static final public char[][] parseChar(String what[]) { // note: array[][] - char outgoing[][] = new char[what.length][]; - for (int i = 0; i < what.length; i++) { - outgoing[i] = what[i].toCharArray(); - } - return outgoing; - } - */ - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . static final public int parseInt(boolean what) { @@ -9394,7 +9139,7 @@ public class PApplet implements PConstants { return list; } - static final public int[] parseInt(byte[] what) { // note this unsigns + static final public int[] parseInt(byte[] what) { // note this un-signs int[] list = new int[what.length]; for (int i = 0; i < what.length; i++) { list[i] = (what[i] & 0xff); @@ -9455,12 +9200,6 @@ public class PApplet implements PConstants { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - /* - static final public float parseFloat(boolean what) { - return what ? 1 : 0; - } - */ - /** * Convert an int to a float value. Also handles bytes because of * Java's rules for upgrading values. @@ -9476,7 +9215,7 @@ public class PApplet implements PConstants { static final public float parseFloat(String what, float otherwise) { try { return Float.parseFloat(what); - } catch (NumberFormatException e) { } + } catch (NumberFormatException ignored) { } return otherwise; } @@ -9574,6 +9313,7 @@ public class PApplet implements PConstants { // INT NUMBER FORMATTING + static public String nf(float num) { int inum = (int) num; if (num == inum) { @@ -9582,6 +9322,7 @@ public class PApplet implements PConstants { return str(num); } + static public String[] nf(float[] nums) { String[] outgoing = new String[nums.length]; for (int i = 0; i < nums.length; i++) { @@ -9590,16 +9331,16 @@ public class PApplet implements PConstants { return outgoing; } + /** * Integer number formatter. */ - static private NumberFormat int_nf; static private int int_nf_digits; static private boolean int_nf_commas; + /** - * * Utility function for formatting numbers into strings. There are two * versions: one for formatting floats, and one for formatting ints. The * values for the digits and right parameters should always be @@ -9623,7 +9364,6 @@ public class PApplet implements PConstants { * @see int(float) */ - static public String[] nf(int[] nums, int digits) { String[] formatted = new String[nums.length]; for (int i = 0; i < formatted.length; i++) { @@ -9632,6 +9372,7 @@ public class PApplet implements PConstants { return formatted; } + /** * @param num the number to format */ @@ -9650,8 +9391,8 @@ public class PApplet implements PConstants { return int_nf.format(num); } + /** - * * Utility function for formatting numbers into strings and placing * appropriate commas to mark units of 1000. There are four versions: one for * formatting ints, one for formatting an array of ints, one for formatting @@ -9660,8 +9401,8 @@ public class PApplet implements PConstants { * The value for the right parameter should always be a positive * integer.
*
- * For a non-US locale, this will insert periods instead of commas, or - * whatever is apprioriate for that region. + * For a non-US locale, this will insert periods instead of commas, + * or whatever is appropriate for that region. * * @webref data:string_functions * @webBrief Utility function for formatting numbers into strings and placing @@ -9701,14 +9442,6 @@ public class PApplet implements PConstants { /** - * number format signed (or space) - * Formats a number but leaves a blank space in the front - * when it's positive so that it can be properly aligned with - * numbers that have a negative sign in front of them. - */ - - /** - * * Utility function for formatting numbers into strings. Similar to * nf() but leaves a blank space in front of positive numbers so * they align with negative numbers in spite of the minus symbol. There are @@ -9716,18 +9449,19 @@ public class PApplet implements PConstants { * values for the digits, left, and right parameters * should always be positive integers. * - * @webref data:string_functions - * @webBrief Utility function for formatting numbers into strings - * @param num the number to format - * @param digits number of digits to pad with zeroes - * @see PApplet#nf(float, int, int) - * @see PApplet#nfp(float, int, int) - * @see PApplet#nfc(float, int) - */ + * @webref data:string_functions + * @webBrief Utility function for formatting numbers into strings + * @param num the number to format + * @param digits number of digits to pad with zeroes + * @see PApplet#nf(float, int, int) + * @see PApplet#nfp(float, int, int) + * @see PApplet#nfc(float, int) + */ static public String nfs(int num, int digits) { return (num < 0) ? nf(num, digits) : (' ' + nf(num, digits)); } + /** * @param nums the numbers to format */ @@ -9739,15 +9473,8 @@ public class PApplet implements PConstants { return formatted; } - // - /** - * number format positive (or plus) - * Formats a number, always placing a - or + sign - * in the front when it's negative or positive. - */ /** - * * Utility function for formatting numbers into strings. Similar to nf() * but puts a "+" in front of positive numbers and a "-" in front of negative * numbers. There are two versions: one for formatting floats, and one for @@ -9767,6 +9494,8 @@ public class PApplet implements PConstants { static public String nfp(int num, int digits) { return (num < 0) ? nf(num, digits) : ('+' + nf(num, digits)); } + + /** * @param nums the numbers to format */ @@ -10291,7 +10020,7 @@ public class PApplet implements PConstants { *

    * Parameters useful for launching or also used by the PDE:
    *
-   * --location=x,y         Upper-lefthand corner of where the applet
+   * --location=x,y         Upper left-hand corner of where the applet
    *                        should appear on screen. If not used,
    *                        the default is to center on the main screen.
    *
@@ -10330,7 +10059,7 @@ public class PApplet implements PConstants {
    *
    * --external             set when the applet is being used by the PDE
    *
-   * --editor-location=x,y  position of the upper-lefthand corner of the
+   * --editor-location=x,y  position of the upper left-hand corner of the
    *                        editor window, for placement of applet window
    *
    * All parameters *after* the sketch class name are passed to the sketch
@@ -10387,11 +10116,9 @@ public class PApplet implements PConstants {
   // also suspecting that these "not showing up" bugs might be EDT issues.
   static public void runSketch(final String[] args,
                                final PApplet constructedSketch) {
-    Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
-      public void uncaughtException(Thread t, Throwable e) {
-        e.printStackTrace();
-        uncaughtThrowable = e;
-      }
+    Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
+      e.printStackTrace();
+      uncaughtThrowable = e;
     });
 
     // This doesn't work, need to mess with Info.plist instead
@@ -10439,7 +10166,7 @@ public class PApplet implements PConstants {
 //    boolean spanDisplays = false;
     int density = -1;
 
-    String param = null, value = null;
+    String param, value;
     String folder = calcSketchPath();
 
     int argIndex = 0;
@@ -10895,15 +10622,6 @@ public class PApplet implements PConstants {
   }
 
 
-  /**
-   * Starts shape recording and returns the PShape object that will
-   * contain the geometry.
-   */
-  /*
-  public PShape beginRecord() {
-    return g.beginRecord();
-  }
-  */
 
   //////////////////////////////////////////////////////////////
 
@@ -13584,7 +13302,6 @@ public class PApplet implements PConstants {
    * @webref transform
    * @webBrief Multiplies the current matrix by the one specified through the
    * parameters
-   * @source
    * @see PGraphics#pushMatrix()
    * @see PGraphics#popMatrix()
    * @see PGraphics#resetMatrix()
diff --git a/core/src/processing/core/PGraphics.java b/core/src/processing/core/PGraphics.java
index 3da95a811..350c5b1f1 100644
--- a/core/src/processing/core/PGraphics.java
+++ b/core/src/processing/core/PGraphics.java
@@ -4237,15 +4237,20 @@ public class PGraphics extends PImage implements PConstants {
    * Used by PGraphics to remove the requirement for loading a font.
    */
   protected PFont createDefaultFont(float size) {
-    Font baseFont;
+    Font baseFont = null;
     try {
       // For 4.0 alpha 4 and later, include a built-in font
       InputStream input = getClass().getResourceAsStream("/font/ProcessingSansPro-Regular.ttf");
-      baseFont = Font.createFont(Font.TRUETYPE_FONT, input);
+      if (input != null) {
+        baseFont = Font.createFont(Font.TRUETYPE_FONT, input);
+      }
 
     } catch (Exception e) {
       // Fall back to how this was handled in 3.x, ugly!
       e.printStackTrace(); // dammit
+    }
+
+    if (baseFont == null) {
       baseFont = new Font("Lucida Sans", Font.PLAIN, 1);
     }
     // Create a PFont from this java.awt.Font
@@ -5770,7 +5775,6 @@ public class PGraphics extends PImage implements PConstants {
    * @webref transform
    * @webBrief Multiplies the current matrix by the one specified through the
    * parameters
-   * @source
    * @see PGraphics#pushMatrix()
    * @see PGraphics#popMatrix()
    * @see PGraphics#resetMatrix()
@@ -8592,6 +8596,7 @@ public class PGraphics extends PImage implements PConstants {
     public void dispose() { // ignore
       saveExecutor.shutdown();
       try {
+        //noinspection ResultOfMethodCallIgnored
         saveExecutor.awaitTermination(5000, TimeUnit.SECONDS);
       } catch (InterruptedException ignored) { }
     }
diff --git a/core/src/processing/core/PImage.java b/core/src/processing/core/PImage.java
index d353bc82a..7115905e8 100644
--- a/core/src/processing/core/PImage.java
+++ b/core/src/processing/core/PImage.java
@@ -24,6 +24,7 @@
 
 package processing.core;
 
+import java.awt.Image;
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -257,11 +258,27 @@ public class PImage implements PConstants, Cloneable {
     this.width = width;
     this.height = height;
     this.format = format;
-    this.pixelDensity = factor;
 
+    pixelDensity = factor;
     pixelWidth = width * pixelDensity;
     pixelHeight = height * pixelDensity;
-    this.pixels = new int[pixelWidth * pixelHeight];
+
+    pixels = new int[pixelWidth * pixelHeight];
+  }
+
+
+  private void init(int width, int height, int format, int factor,
+                    int[] pixels) {  // ignore
+    this.width = width;
+    this.height = height;
+    this.format = format;
+
+    pixelDensity = factor;
+    // these weren't being set in 4.0a3, why? [fry 210615]
+    pixelWidth = width * pixelDensity;
+    pixelHeight = height * pixelDensity;
+
+    this.pixels = pixels;
   }
 
 
@@ -287,7 +304,7 @@ public class PImage implements PConstants, Cloneable {
 
   public PImage(int width, int height, int[] pixels,
                 boolean requiresCheckAlpha, PApplet parent) {
-    initFromPixels(width, height, pixels, RGB,1);
+    init(width, height, RGB,1, pixels);
     this.parent = parent;
 
     if (requiresCheckAlpha) {
@@ -298,8 +315,7 @@ public class PImage implements PConstants, Cloneable {
   public PImage(int width, int height, int[] pixels,
                 boolean requiresCheckAlpha, PApplet parent,
                 int format, int factor) {
-
-    initFromPixels(width, height, pixels, format, factor);
+    init(width, height, format, factor, pixels);
     this.parent = parent;
 
     if (requiresCheckAlpha) {
@@ -307,17 +323,27 @@ public class PImage implements PConstants, Cloneable {
     }
   }
 
-  private void initFromPixels(int width, int height, int[] pixels, int format, int factor) {
-    this.width = width;
-    this.height = height;
-    this.format = format;
-    this.pixelDensity = factor;
-    this.pixels = pixels;
+
+  @Deprecated
+  public PImage(Image img) {
+    ShimAWT.fromNativeImage(img, this);
+  }
+
+
+  /**
+   * Use the getNative() method instead, which allows library interfaces to be
+   * written in a cross-platform fashion for desktop, Android, and others.
+   * This is still included for PGraphics objects, which may need the image.
+   */
+  @Deprecated
+  public Image getImage() {  // ignore
+    return (Image) getNative();
   }
 
 
   public Object getNative() {  // ignore
-    return null;
+    // TODO temporary solution for maximum backwards compatibility
+    return ShimAWT.getNativeImage(this);
   }
 
 
@@ -481,7 +507,8 @@ public class PImage implements PConstants, Cloneable {
    * @see PImage#get(int, int, int, int)
    */
   public void resize(int w, int h) {  // ignore
-    throw new RuntimeException("resize() not implemented for this PImage type");
+    //throw new RuntimeException("resize() not implemented for this PImage type");
+    ShimAWT.resizeImage(this, w, h);
   }
 
 
@@ -1033,14 +1060,6 @@ public class PImage implements PConstants, Cloneable {
   }
 
 
-  /** Set the high bits of all pixels to opaque. */
-  protected void opaque() {
-    for (int i = 0; i < pixels.length; i++) {
-      pixels[i] = 0xFF000000 | pixels[i];
-    }
-  }
-
-
   /**
    * Optimized code for building the blur kernel.
    * further optimized blur code (approx. 15% for radius=20)
@@ -3357,11 +3376,11 @@ int testFunction(int dst, int src) {
    * @param path must be a full path (not relative or simply a filename)
    */
   protected boolean saveImpl(String path) {
-    // TODO Imperfect/temporary solution for alpha 2.
+    // TODO Imperfect/temporary solution for current 4.x releases
     // https://github.com/processing/processing4/wiki/Exorcising-AWT
-    if (!PApplet.disableAWT) {
-      return ShimAWT.saveImage(this, path);
-    }
-    return false;
+    //if (!PApplet.disableAWT) {  // TODO necessary? will this trigger NEWT?
+    return ShimAWT.saveImage(this, path);
+    //}
+    //return false;
   }
 }
diff --git a/core/src/processing/core/PSketch.java b/core/src/processing/core/PSketch.java
new file mode 100644
index 000000000..0a09b33d4
--- /dev/null
+++ b/core/src/processing/core/PSketch.java
@@ -0,0 +1,6 @@
+package processing.core;
+
+
+public class PSketch implements PConstants {
+    // this is a dummy class
+}
\ No newline at end of file
diff --git a/core/src/processing/opengl/PSurfaceJOGL.java b/core/src/processing/opengl/PSurfaceJOGL.java
index 96617a272..70473327d 100644
--- a/core/src/processing/opengl/PSurfaceJOGL.java
+++ b/core/src/processing/opengl/PSurfaceJOGL.java
@@ -1092,7 +1092,10 @@ public class PSurfaceJOGL implements PSurface {
 
   protected void nativeMouseEvent(com.jogamp.newt.event.MouseEvent nativeEvent,
                                   int peAction) {
+    // SHIFT, CTRL, META, and ALT are identical to the processing.event.Event,
+    // so the modifiers are left intact here.
     int modifiers = nativeEvent.getModifiers();
+    // Could limit to just the specific modifiers, but why bother?
     /*
     int peModifiers = modifiers &
                       (InputEvent.SHIFT_MASK |
@@ -1159,6 +1162,7 @@ public class PSurfaceJOGL implements PSurface {
 
   protected void nativeKeyEvent(com.jogamp.newt.event.KeyEvent nativeEvent,
                                 int peAction) {
+    // SHIFT, CTRL, META, and ALT are identical to processing.event.Event
     int modifiers = nativeEvent.getModifiers();
 //    int peModifiers = nativeEvent.getModifiers() &
 //                      (InputEvent.SHIFT_MASK |
diff --git a/core/todo.txt b/core/todo.txt
index 82fd4c224..250f2030d 100644
--- a/core/todo.txt
+++ b/core/todo.txt
@@ -1,43 +1,22 @@
-1273 (4.0a4)
-X fix typo in extensions= arg
-X update batik to 1.14
-X   https://github.com/processing/processing4/issues/179
-X   https://github.com/processing/processing4/issues/192
-X update the batik url
-X   https://github.com/processing/processing4/pull/183
-X calling unregisterMethod() on dispose from dispose() means concurrent mod
-o   https://github.com/processing/processing4/pull/199
-X   modernized the code a bit, checked in a version that queues to avoid list issue
-X Make parseJSONObject/Array return null when parsing fails
-X   https://github.com/processing/processing4/issues/165
-X   https://github.com/processing/processing4/pull/166
-X "textMode(SHAPE) is not supported by this renderer" message
-X   https://github.com/processing/processing4/issues/202
-X   formerly https://github.com/processing/processing/issues/6169
-X add PVector.setHeading() for parity with p5.js
-X   https://github.com/processing/processing4/issues/193
-X .setAngle() for PVector?
-X   https://github.com/processing/processing-docs/issues/744
-o Math for BLEND incorrect in the reference?
-o   https://github.com/processing/processing-docs/issues/762
-o How much of the attrib*() functions should be documented?
-o   https://github.com/processing/processing-docs/issues/172
+1275 (4.0a6)
+X mouseButton not set correctly on mouseReleased() with Java2D
+X   https://github.com/processing/processing4/issues/181
+o   https://github.com/processing/processing4/pull/188
+
+cleaning
+o Skija bindings
+o   https://github.com/processing/processing4/issues/151
+o size() issues on Mojave? (3.4 works, 3.5.3 does not)
+X   https://github.com/processing/processing/issues/5791
+X   closed for now; unable to confirm anything in the thread
+o Complaints that size() is not working
+X   Windows: https://github.com/processing/processing/issues/6046
+X   Linux: https://github.com/processing/processing/issues/5912
+X   can't reproduce, may be fixed with display= changes in alpha 4
+X   reports that open and closing Preferences fixed it?
 
 
-regressions
-_ (unconfirmed) setting which display to use does not work
-_   https://github.com/processing/processing4/issues/187
-_ cursor(PImage) broken everywhere because PImage.getNative() returns null
-_   https://github.com/processing/processing4/issues/180
-_ PImage.resize() not working
-_   https://github.com/processing/processing4/issues/200
-_   two simple examples added to the issue that can be used for tests
-_ mouseButton not set correctly on mouseReleased() with Java2D
-_   https://github.com/processing/processing4/issues/181
-_   https://github.com/processing/processing4/pull/188
-_ copy() not working correctly
-_   https://github.com/processing/processing4/issues/169
-
+_ put opengl libs for core into platform-specific subfolders?
 
 _ setting surface size needs to happen outside draw()
 _   surface.setSize() sadness etc
@@ -45,13 +24,6 @@ _   https://github.com/processing/processing4/issues/162
 _   https://github.com/processing/processing4/issues/186
 _   https://github.com/processing/processing/issues/4129
 
-_ why does japplemenubar.JAppleMenuBar.hide(); still work?
-_   it calls SetSystemUIMode, which is part of Carbon (which was removed in Catalina)
-_     https://github.com/kritzikratzi/jAppleMenuBar/blob/master/src/native/jAppleMenuBar.m
-_   setPresentationOptions() seems to be the Cocoa equivalent
-_     https://www.cocoawithlove.com/2009/08/animating-window-to-fullscreen-on-mac.html
-_     https://bugzilla.mozilla.org/attachment.cgi?id=8616917&action=diff
-_   https://www.philipp.haussleiter.de/2012/09/building-native-macos-apps-with-java/
 
 
 should be fixed in 4.x (close/lock these 3.x issues with final 4.0 release)
@@ -59,11 +31,9 @@ X AppKit errors from P2D/P3D
 X   https://github.com/processing/processing/issues/5880
 X Export Application broken in Processing 3.5.4 when using P2D or P3D renderers
 X   may be a JOGL bug, fixed by the 2.4 RC (therefore fixed in 4.x already?)
-_   https://github.com/processing/processing/issues/5983
-_ Profile GL3bc is not available on X11GraphicsDevice
-_   https://github.com/processing/processing/issues/5476
+X   https://github.com/processing/processing/issues/5983
 X Cannot run rotateZ() within the PShape class
-_   https://github.com/processing/processing/issues/5770
+X   https://github.com/processing/processing/issues/5770
 _ Profile GL4bc is not available on X11GraphicsDevice
 _   https://github.com/processing/processing/issues/6160
 _   https://github.com/processing/processing/issues/6154
@@ -74,16 +44,9 @@ _   https://github.com/processing/processing/issues/5476
 _ update P2D reference to make clear about drawing order and quality
 _   https://github.com/processing/processing/issues/5880
 
-_ Complaints that size() is not working
-_   Windows: https://github.com/processing/processing/issues/6046
-_   Linux: https://github.com/processing/processing/issues/5912
-
 _ why does GL flash red when resizing?
 
 
-_ Skija bindings
-_   https://github.com/processing/processing4/issues/151
-
 load/save image
 _ https://github.com/processing/processing4/wiki/Exorcising-AWT
 _ moving loadImage() into Surface
@@ -126,13 +89,15 @@ _ implement openLink() in PSurfaceJOGL
 
 
 before final release
+_ replace japplemenubar with something that's more likely to work in the future
+_   https://github.com/processing/processing4/issues/221
 _ Warn users or provide auto-fix when `frame` is used in a sketch
 _   https://github.com/processing/processing4/issues/59
 _ Intel HD Graphics 3000 workaround is causing a big fat warning
 _   https://github.com/processing/processing4/issues/50
 _ ThinkDifferent unavailable with --disable-awt, needs workaround
 _   https://github.com/processing/processing4/issues/52
-_ Better resolution for frame/surface methods
+_ Better solution for frame/surface methods
 _   https://github.com/processing/processing4/issues/53
 _ Remove frame from PApplet
 _   https://github.com/processing/processing4/issues/54
@@ -147,9 +112,6 @@ _   https://github.com/processing/processing4/issues/56
 
 
 high-ish
-_ size() issues on Mojave? (3.4 works, 3.5.3 does not)
-_   https://github.com/processing/processing/issues/5791
-_   closed for now; unable to confirm anything in the thread
 _ add separator option to loadTable()
 _   https://github.com/processing/processing/issues/5068
 _ make setting the window icon automatic, based on files in local dirs
diff --git a/done.txt b/done.txt
index 7ac40d830..0b9392fa7 100644
--- a/done.txt
+++ b/done.txt
@@ -1,3 +1,159 @@
+1274 (4.0a5)
+X update JNA from 5.7.0 to 5.8.0
+X bump minimum system version to 10.14.6
+X remove ant binary from repo, update from 1.8.2 to 1.10.10
+X refresh appbundler code a little
+X   disable "Launchpath: /path/to/Processing.app/Contents/PlugIns/jdk-11.0.11+9/Contents/Home/lib/jli/libjli.dylib" console message on startup
+X   update macOS SDK references in appbundler code
+X   fix compilation problem in appbundler iterator
+X   walk subfolders in the export to avoid duplicated core.jar and to include JavaFX
+X fix FX2D applications on macOS
+X   they didn't properly have their library.path set
+X   dylib files were not included in the export
+X Some 3.x Tools not working because JavaFX isn't on the classpath
+X   https://github.com/processing/processing4/issues/110
+X   https://github.com/processing/processing4/pull/112
+X garbled text in JavaFX
+X   https://bugs.openjdk.java.net/browse/JDK-8234916
+X bump JavaFX to 16, no longer the LTS version, but fixes garbled text
+X   and 11.0.8 is not available
+X add more entitlements in an attempt to fix audio/video capture
+X   https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_audio-input
+X   https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_camera
+X   https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_microphone
+X   https://github.com/processing/processing4/commit/7b75acf2799f61c9c22233f38ee73c07635cea14
+X update to launch4j 3.14, fixing Export to Application on Windows
+X change defaults to get away from JFileChooser; it's awful
+X working on JavaFX on Windows, more runtime problems, needing exports
+X Export to Application with FX2D apps working on Windows
+X make sure we're not embedding webkit with all JFX apps
+X   maybe just remove it from the main download as well
+X   remove javafx-swt.jar and javafx.web.jar / also their .so files
+X more checking FX2D (and application export) on Windows
+o update list of optional JRE files for Java 8
+o   Andres provided some updates
+X   https://github.com/processing/processing/issues/3288
+o   these will change again for Java 11, so wait until then
+X   opting not to do so
+X   https://github.com/processing/processing4/issues/210
+X changes to how getFont() works for Preferences
+X   this may have been the cause of the old ghost NPEs on startup?
+X turn off chooser.files.native on macOS
+X   We were shutting this off on macOS because it broke Copy/Paste:
+o    https://github.com/processing/processing/issues/1035
+o    https://github.com/processing/processing4/issues/77
+X    But changing this for 4.0 alpha 5, because the JFileChooser is awful,
+X    and even worse on Big Sur, and worse than the Copy/Paste issue.
+X "Could not run" "For more information, read revisions.txt and Help → Troubleshooting."
+X   need to drop revisions.txt here and just reference Troubleshooting
+X NoClassDefError: processing/core/PApplet when starting 4.0a2 on Windows 10
+X   https://github.com/processing/processing4/issues/154
+X change application signature from Pde3 to Pde4
+X   also change the bundle identifier to avoid conflicts with 3
+X remove template.app from macOS build
+X   was no longer in use, and causing notarization problems
+
+javafx
+X move JavaFX to its own library, too many weird quirks that it includes
+o   build bits should be in core/build.xml or javafx/build.xml
+X   by moving it out, all the strangeness of download and import is outside core
+X add JavaFX library to IntelliJ
+X #@$*$& the JavaFX jars are ever-so-slightly different between platforms
+X Only specify --modules-path when running JavaFX apps
+X   https://github.com/processing/processing4/issues/209
+X JavaFX now throws Exception on run b/c natives aren't present
+X   why are the jars even there? how is module path getting set?
+o gonna have to cut loose running Tools from the PDE
+o   or make it possible to embed a JavaFX sketch, and the rest will work?
+X   working from the PDE, though not FX2D sketches
+X debug JavaFX and Export to Application on Windows
+X   this was working on Saturday, now broken after the move to a separate library
+/   now "Art Station" is broken because it actually creates an FX2D sketch
+X     (and FX2D isn't on the classpath by default)
+X debug JavaFX and Export to Application on Linux
+X fix modules path warning for Tools in the PDE
+X   "WARNING: Unsupported JavaFX configuration" when running Tools that use JavaFX
+X   we were probably spared the warnings because the older JARs were hanging around
+X update modules path when exporting application
+X You need to use "Import Library" to add processing.javafx.PGraphicsJavaFX
+o   automatically import JavaFX if FX2D is in sketch? or tell user?
+X   concerned about auto-import being too unreliable
+
+major font cleanup
+X JDK fonts have been removed; fonts folder too? (not seeing it in the JDK)
+X   https://www.oracle.com/java/technologies/javase/11-relnote-issues.html#JDK-8191522
+X more reliable loading of default mono fonts
+X processing.mono used in preferences.txt
+X remove Preferences.getFont(), deal with incorrect usage
+X   getFont("editor.font") was returning getFont("editor.font.size")
+
+windows/scaling
+X we're turning off automatic UI scaling in Windows, should we turn it back on?
+X   using -Dsun.java2d.uiScale.enabled=false inside config.xml for launch4j
+X   this was for Java 9, and we should have better support now
+X   also check whether this is set on Linux
+X Welcome screen doesn't size properly for HiDPI screens
+X   https://github.com/processing/processing/issues/4896
+X getSystemZoom() not available to splash screen
+X   https://github.com/processing/processing4/issues/145
+X   move all platform code out that doesn't require additional setup?
+X   i.e. all the things that rely on preferences can be inited separately (later)
+
+
+1273 (4.0a4)
+X “An error occurred while starting the application” with 4.0a3 on Windows
+X   replace about.bmp that was causing processing.exe to crash on startup
+X   https://github.com/processing/processing4/issues/156
+X update to JDK 11.0.10
+X update from JNA 5.2.0 to 5.7.0
+X   was having trouble with "java.lang.UnsatisfiedLinkError: Unable to load library 'CoreServices': Native library (darwin/libCoreServices.dylib) not found in resource path"
+X   https://github.com/fathominfo/processing-p5js-mode/issues/26
+X implement auto-download for JNA updates
+X “Exception in thread "Contribution Uninstaller" NullPointerException” during Remove
+X   https://github.com/processing/processing4/issues/174
+X catch NoClassDefError in Platform.deleteFile() (still unclear of its cause)
+X   https://github.com/processing/processing4/issues/159
+X   https://github.com/processing/processing/issues/6185
+X need to set a nicer default font
+X   increases export size, but impact is so worth it
+X Update JDK to 11.0.11+9
+X modernize the RegisteredMethods code to use collections classes w/ concurrency
+X   https://github.com/processing/processing4/pull/199
+X don't sort user's charset array when calling createFont()
+X   https://github.com/processing/processing4/issues/197
+X   https://github.com/processing/processing4/pull/198
+X automatically lock closed issues
+X   https://github.com/apps/lock
+X   https://github.com/dessant/lock-threads-app
+X Display Window doesn't remember its position
+X   seems that --external not getting passed
+X   https://github.com/processing/processing4/issues/158
+X   https://github.com/processing/processing/issues/5843
+X   https://github.com/processing/processing/issues/5781
+X store -1 as display number when using the default
+X   ran into weird situation where '1' was renumbered by adding a screen
+X   so the default was now the external display
+X a little modernizing/cleanup in Base, converting things to lambda functions
+X editor windows always open on display 1
+X   https://github.com/processing/processing/issues/1566
+X   rewrote EditorState to better handle devices and clean it up
+
+earlier
+o further streamline the downloader
+o   https://github.com/processing/processing4/issues/47
+o next video release
+o   https://github.com/processing/processing-video/milestone/1
+
+contribs
+X many updates in the docs portion of the repo
+X   https://github.com/processing/processing4/pull/191
+X fixing undo
+X   https://github.com/processing/processing4/pull/175
+X tweak the number of updates based on Akarshit's attempt
+X   https://github.com/processing/processing4/issues/201
+X   https://github.com/processing/processing/pull/4097
+
+
 1272 (4.0a3)
 X 'ant source-jar' target added to core
 X   https://github.com/processing/processing4/issues/118
diff --git a/java/.classpath b/java/.classpath
index 424f58996..fa8666d22 100644
--- a/java/.classpath
+++ b/java/.classpath
@@ -4,7 +4,7 @@
 	
 	
 	
-	
+	
 		
 			
 		
diff --git a/java/application/Info.plist.tmpl b/java/application/Info.plist.tmpl
index 68fdfef26..d86e39df2 100644
--- a/java/application/Info.plist.tmpl
+++ b/java/application/Info.plist.tmpl
@@ -38,7 +38,7 @@
     @@sketch@@
 
     LSMinimumSystemVersion
-    10.13.6
+    10.14.6
 
     NSHighResolutionCapable
     
@@ -61,7 +61,7 @@
     
 @@jvm_options_list@@
       -Xdock:icon=$APP_ROOT/Contents/Resources/sketch.icns
-      -Djava.library.path=$APP_ROOT/Contents/Java
+      -Djava.library.path=$APP_ROOT/Contents/Java:$APP_ROOT/Contents/Java/core/library
       -Dapple.laf.useScreenMenuBar=true
       -Dcom.apple.macos.use-file-dialog-packages=true
       -Dcom.apple.macos.useScreenMenuBar=true
diff --git a/java/application/launch4j/LICENSE.txt b/java/application/launch4j/LICENSE.txt
index bf03c83d7..476f9e82d 100644
--- a/java/application/launch4j/LICENSE.txt
+++ b/java/application/launch4j/LICENSE.txt
@@ -1,7 +1,7 @@
 Launch4j (http://launch4j.sourceforge.net/)
 Cross-platform Java application wrapper for creating Windows native executables.
 
-Copyright (c) 2004, 2015 Grzegorz Kowal
+Copyright (c) 2004, 2021 Grzegorz Kowal
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without modification,
diff --git a/java/application/launch4j/bin/ld-linux b/java/application/launch4j/bin/ld-linux
index 2e2983e88..0e16844ca 100755
Binary files a/java/application/launch4j/bin/ld-linux and b/java/application/launch4j/bin/ld-linux differ
diff --git a/java/application/launch4j/bin/windres-linux b/java/application/launch4j/bin/windres-linux
index 5b83b9349..76b630e63 100755
Binary files a/java/application/launch4j/bin/windres-linux and b/java/application/launch4j/bin/windres-linux differ
diff --git a/java/application/launch4j/head/consolehead.o b/java/application/launch4j/head/consolehead.o
index 60bb62f66..78593071b 100644
Binary files a/java/application/launch4j/head/consolehead.o and b/java/application/launch4j/head/consolehead.o differ
diff --git a/java/application/launch4j/head/guihead.o b/java/application/launch4j/head/guihead.o
index 7cff13221..689e7c2ab 100644
Binary files a/java/application/launch4j/head/guihead.o and b/java/application/launch4j/head/guihead.o differ
diff --git a/java/application/launch4j/head/head.o b/java/application/launch4j/head/head.o
index d5d63d666..95f051e3e 100644
Binary files a/java/application/launch4j/head/head.o and b/java/application/launch4j/head/head.o differ
diff --git a/java/application/launch4j/launch4j.jar b/java/application/launch4j/launch4j.jar
index 6daad70f7..21ba2adc1 100644
Binary files a/java/application/launch4j/launch4j.jar and b/java/application/launch4j/launch4j.jar differ
diff --git a/java/application/launch4j/lib/LICENSE.txt b/java/application/launch4j/lib/LICENSE.txt
index 4848b3e4e..9bc9763eb 100644
--- a/java/application/launch4j/lib/LICENSE.txt
+++ b/java/application/launch4j/lib/LICENSE.txt
@@ -1,6 +1,7 @@
 (BSD Style License)
 
-Copyright (c) 2003-2004, Joe Walnes
+Copyright (c) 2003-2006, Joe Walnes
+Copyright (c) 2006-2019, XStream Committers
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/java/application/launch4j/lib/xstream.jar b/java/application/launch4j/lib/xstream.jar
index 392e1c937..742311880 100644
Binary files a/java/application/launch4j/lib/xstream.jar and b/java/application/launch4j/lib/xstream.jar differ
diff --git a/java/application/launch4j/w32api/crt2.o b/java/application/launch4j/w32api/crt2.o
index f81f836cf..4f5f3be7d 100644
Binary files a/java/application/launch4j/w32api/crt2.o and b/java/application/launch4j/w32api/crt2.o differ
diff --git a/java/application/launch4j/w32api/libadvapi32.a b/java/application/launch4j/w32api/libadvapi32.a
index c471853c7..21a8d7b00 100644
Binary files a/java/application/launch4j/w32api/libadvapi32.a and b/java/application/launch4j/w32api/libadvapi32.a differ
diff --git a/java/application/launch4j/w32api/libgcc.a b/java/application/launch4j/w32api/libgcc.a
index d3f89479e..f98edb83f 100644
Binary files a/java/application/launch4j/w32api/libgcc.a and b/java/application/launch4j/w32api/libgcc.a differ
diff --git a/java/application/launch4j/w32api/libkernel32.a b/java/application/launch4j/w32api/libkernel32.a
index 5d3eb074f..03ee7cdce 100644
Binary files a/java/application/launch4j/w32api/libkernel32.a and b/java/application/launch4j/w32api/libkernel32.a differ
diff --git a/java/application/launch4j/w32api/libmingw32.a b/java/application/launch4j/w32api/libmingw32.a
index d1f7888d8..d849b85e9 100644
Binary files a/java/application/launch4j/w32api/libmingw32.a and b/java/application/launch4j/w32api/libmingw32.a differ
diff --git a/java/application/launch4j/w32api/libmingwex.a b/java/application/launch4j/w32api/libmingwex.a
new file mode 100644
index 000000000..f74249257
Binary files /dev/null and b/java/application/launch4j/w32api/libmingwex.a differ
diff --git a/java/application/launch4j/w32api/libmoldname.a b/java/application/launch4j/w32api/libmoldname.a
new file mode 100644
index 000000000..21897dae4
Binary files /dev/null and b/java/application/launch4j/w32api/libmoldname.a differ
diff --git a/java/application/launch4j/w32api/libmsvcrt.a b/java/application/launch4j/w32api/libmsvcrt.a
index 6714146b6..be8ae31e5 100644
Binary files a/java/application/launch4j/w32api/libmsvcrt.a and b/java/application/launch4j/w32api/libmsvcrt.a differ
diff --git a/java/application/launch4j/w32api/libshell32.a b/java/application/launch4j/w32api/libshell32.a
index d35fbdaf3..efdd8bcc7 100644
Binary files a/java/application/launch4j/w32api/libshell32.a and b/java/application/launch4j/w32api/libshell32.a differ
diff --git a/java/application/launch4j/w32api/libuser32.a b/java/application/launch4j/w32api/libuser32.a
index 387fb650d..cbddae69e 100644
Binary files a/java/application/launch4j/w32api/libuser32.a and b/java/application/launch4j/w32api/libuser32.a differ
diff --git a/java/application/template.app/Contents/MacOS/JavaApplicationStub b/java/application/template.app/Contents/MacOS/JavaApplicationStub
deleted file mode 100755
index 56ec300e5..000000000
Binary files a/java/application/template.app/Contents/MacOS/JavaApplicationStub and /dev/null differ
diff --git a/java/application/template.app/Contents/PkgInfo b/java/application/template.app/Contents/PkgInfo
deleted file mode 100644
index bd04210fb..000000000
--- a/java/application/template.app/Contents/PkgInfo
+++ /dev/null
@@ -1 +0,0 @@
-APPL????
\ No newline at end of file
diff --git a/java/application/template.app/Contents/Resources/sketch.icns b/java/application/template.app/Contents/Resources/sketch.icns
deleted file mode 100644
index 2bdb4dfc2..000000000
Binary files a/java/application/template.app/Contents/Resources/sketch.icns and /dev/null differ
diff --git a/java/libraries/dxf/.classpath b/java/libraries/dxf/.classpath
index 4e243c7a4..0f47de2c4 100644
--- a/java/libraries/dxf/.classpath
+++ b/java/libraries/dxf/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 		
 			
 		
diff --git a/java/libraries/io/build.xml b/java/libraries/io/build.xml
index 8d269d13b..108f43fff 100644
--- a/java/libraries/io/build.xml
+++ b/java/libraries/io/build.xml
@@ -6,24 +6,22 @@
     
   
 
+  
+
   
     
-      
+      
     
-    
+    
 
     
     
-      
-    
+	         target="11"
+	         srcdir="src" destdir="bin"
+	         encoding="UTF-8"
+	         includeAntRuntime="false"
+	         classpath="${core.path}"
+	         nowarn="true" />
   
 
   
diff --git a/java/libraries/io/library/.gitignore b/java/libraries/io/library/.gitignore
new file mode 100644
index 000000000..919d24f8b
--- /dev/null
+++ b/java/libraries/io/library/.gitignore
@@ -0,0 +1 @@
+io.jar
diff --git a/java/libraries/javafx/.classpath b/java/libraries/javafx/.classpath
new file mode 100644
index 000000000..cfc5fc9ac
--- /dev/null
+++ b/java/libraries/javafx/.classpath
@@ -0,0 +1,17 @@
+
+
+	
+		
+			
+		
+	
+	
+	
+	
+	
+	
+	
+	
+	
+	
+
diff --git a/java/libraries/javafx/.gitignore b/java/libraries/javafx/.gitignore
new file mode 100644
index 000000000..40f0c37ab
--- /dev/null
+++ b/java/libraries/javafx/.gitignore
@@ -0,0 +1,5 @@
+# ignore the sdk download files
+javafx-*-sdk-*.zip
+
+# everything is downloaded from online
+/library
diff --git a/java/libraries/javafx/.project b/java/libraries/javafx/.project
new file mode 100644
index 000000000..7dbb5da11
--- /dev/null
+++ b/java/libraries/javafx/.project
@@ -0,0 +1,17 @@
+
+
+	processing4-javafx
+	
+	
+	
+	
+		
+			org.eclipse.jdt.core.javabuilder
+			
+			
+		
+	
+	
+		org.eclipse.jdt.core.javanature
+	
+
diff --git a/java/libraries/javafx/.settings/org.eclipse.jdt.core.prefs b/java/libraries/javafx/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..cd8d089a1
--- /dev/null
+++ b/java/libraries/javafx/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,15 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=11
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=11
diff --git a/java/libraries/javafx/build.xml b/java/libraries/javafx/build.xml
new file mode 100644
index 000000000..d39d41618
--- /dev/null
+++ b/java/libraries/javafx/build.xml
@@ -0,0 +1,166 @@
+
+
+
+  
+  
+  
+
+  
+    
+    
+    
+  
+
+  
+    
+      
+      
+    
+  
+
+  
+    
+      
+    
+  
+
+  
+
+  
+
+  
+
+  
+    
+
+    
+    
+  
+
+  
+    
+    
+    
+
+    
+    
+    
+    
+      
+        
+        
+        
+        
+      
+
+      
+      
+    
+  
+
+  
+    
+
+    
+      
+        
+        
+        
+        
+        
+
+        
+        
+      
+
+      
+      
+    
+  
+
+  
+    
+    
+    
+
+    
+    
+
+    
+    
+      
+      
+    
+
+    
+      
+      
+    
+
+    
+      
+      
+    
+  
+
+  
+    
+      
+    
+    
+
+    
+    
+
+    
+    
+  
+
+  
+    
+  
+
diff --git a/java/libraries/javafx/library.properties b/java/libraries/javafx/library.properties
new file mode 100644
index 000000000..00d042f25
--- /dev/null
+++ b/java/libraries/javafx/library.properties
@@ -0,0 +1,2 @@
+name = JavaFX
+version = 1
diff --git a/core/src/processing/javafx/PGraphicsFX2D.java b/java/libraries/javafx/src/processing/javafx/PGraphicsFX2D.java
similarity index 98%
rename from core/src/processing/javafx/PGraphicsFX2D.java
rename to java/libraries/javafx/src/processing/javafx/PGraphicsFX2D.java
index 7868c7fe0..4e73a1709 100644
--- a/core/src/processing/javafx/PGraphicsFX2D.java
+++ b/java/libraries/javafx/src/processing/javafx/PGraphicsFX2D.java
@@ -433,8 +433,8 @@ public class PGraphicsFX2D extends PGraphics {
     context.beginPath();
     PathIterator pi = s.getPathIterator(null);
     while (!pi.isDone()) {
-      int pitype = pi.currentSegment(pathCoordsBuffer);
-      switch (pitype) {
+      int piType = pi.currentSegment(pathCoordsBuffer);
+      switch (piType) {
         case PathIterator.SEG_MOVETO:
           context.moveTo(pathCoordsBuffer[0], pathCoordsBuffer[1]);
           break;
@@ -454,7 +454,7 @@ public class PGraphicsFX2D extends PGraphics {
           context.closePath();
           break;
         default:
-          showWarning("Unknown segment type " + pitype);
+          showWarning("Unknown segment type " + piType);
       }
       pi.next();
     }
@@ -833,7 +833,7 @@ public class PGraphicsFX2D extends PGraphics {
     float sweep = stop - start;
 
     // The defaults, before 2.0b7, were to stroke as Arc2D.OPEN, and then fill
-    // using Arc2D.PIE. That's a little wonky, but it's here for compatability.
+    // using Arc2D.PIE. That's a little wonky, but it's here for compatibility.
     ArcType fillMode = ArcType.ROUND;  // Arc2D.PIE
     ArcType strokeMode = ArcType.OPEN;
 
@@ -1093,7 +1093,7 @@ public class PGraphicsFX2D extends PGraphics {
       int targetType = ARGB;
       boolean opaque = (tintColor & 0xFF000000) == 0xFF000000;
       if (source.format == RGB) {
-        if (!tint || (tint && opaque)) {
+        if (!tint || opaque) {
           //bufferType = BufferedImage.TYPE_INT_RGB;
           targetType = RGB;
         }
@@ -1155,8 +1155,7 @@ public class PGraphicsFX2D extends PGraphics {
 //          RescaleOp op = new RescaleOp(scales, offsets, null);
 //          op.filter(image, image);
 
-        //} else if (bufferType == BufferedImage.TYPE_INT_ARGB) {
-        } else if (targetType == ARGB) {
+        } else {  // targetType == ARGB
           if (source.format == RGB &&
               (tintColor & 0xffffff) == 0xffffff) {
             int hi = tintColor & 0xff000000;
@@ -1288,7 +1287,7 @@ public class PGraphicsFX2D extends PGraphics {
 
   //////////////////////////////////////////////////////////////
 
-  // TEXT ATTRIBTUES
+  // TEXT ATTRIBUTES
 
 
   protected FontCache fontCache = new FontCache();
@@ -1374,14 +1373,14 @@ public class PGraphicsFX2D extends PGraphics {
 
     // keeps all created fonts for reuse up to MAX_CACHE_SIZE limit
     // when the limit is reached, the least recently used font is removed
-    // TODO: this should be based on memory consumtion
+    // TODO: this should be based on memory consumption
     final LinkedHashMap cache =
-        new LinkedHashMap(16, 0.75f, true) {
-      @Override
-      protected boolean removeEldestEntry(Map.Entry eldest) {
-        return size() > MAX_CACHE_SIZE;
-      }
-    };
+      new LinkedHashMap<>(16, 0.75f, true) {
+        @Override
+        protected boolean removeEldestEntry(Map.Entry eldest) {
+          return size() > MAX_CACHE_SIZE;
+        }
+      };
 
     // key for retrieving fonts from cache; don't use for insertion,
     // every font has to have its own new Key instance
@@ -1540,7 +1539,7 @@ public class PGraphicsFX2D extends PGraphics {
 
   protected PImage getTintedGlyphImage(PFont.Glyph glyph, int tintColor) {
     if (textFontInfo.tintCache == null) {
-      textFontInfo.tintCache = new LinkedHashMap(16, 0.75f, true) {
+      textFontInfo.tintCache = new LinkedHashMap<>(16, 0.75f, true) {
         @Override
         protected boolean removeEldestEntry(Map.Entry eldest) {
           return size() > FontInfo.MAX_CACHED_COLORS_PER_FONT;
@@ -1571,14 +1570,15 @@ public class PGraphicsFX2D extends PGraphics {
     PFont.Glyph glyph = textFont.getGlyph(ch);
     if (glyph != null) {
       if (textMode == MODEL) {
-        float high    = glyph.height     / (float) textFont.getSize();
-        float bwidth  = glyph.width      / (float) textFont.getSize();
-        float lextent = glyph.leftExtent / (float) textFont.getSize();
-        float textent = glyph.topExtent  / (float) textFont.getSize();
+        float bitmapSize = textFont.getSize();
+        float high = glyph.height / bitmapSize;
+        float wide  = glyph.width / bitmapSize;
+        float leftExtent = glyph.leftExtent / bitmapSize;
+        float topExtent = glyph.topExtent / bitmapSize;
 
-        float x1 = x + lextent * textSize;
-        float y1 = y - textent * textSize;
-        float x2 = x1 + bwidth * textSize;
+        float x1 = x + leftExtent * textSize;
+        float y1 = y - topExtent * textSize;
+        float x2 = x1 + wide * textSize;
         float y2 = y1 + high * textSize;
 
         PImage glyphImage = (fillColor == 0xFFFFFFFF) ?
@@ -2033,7 +2033,7 @@ public class PGraphicsFX2D extends PGraphics {
   public void backgroundImpl() {
 
     // if pixels are modified, we don't flush them (just mark them flushed)
-    // because they would be immediatelly overwritten by the background anyway
+    // because they would be immediately overwritten by the background anyway
     modified = false;
     loaded = false;
 
diff --git a/core/src/processing/javafx/PSurfaceFX.java b/java/libraries/javafx/src/processing/javafx/PSurfaceFX.java
similarity index 99%
rename from core/src/processing/javafx/PSurfaceFX.java
rename to java/libraries/javafx/src/processing/javafx/PSurfaceFX.java
index 34d4d1f9f..470ae71bc 100644
--- a/core/src/processing/javafx/PSurfaceFX.java
+++ b/java/libraries/javafx/src/processing/javafx/PSurfaceFX.java
@@ -885,6 +885,8 @@ public class PSurfaceFX implements PSurface {
         button = PConstants.CENTER;
         break;
       case NONE:
+      case BACK:
+      case FORWARD:
         // not currently handled
         break;
     }
diff --git a/java/libraries/net/.classpath b/java/libraries/net/.classpath
index c45131f2e..f1d4249b3 100644
--- a/java/libraries/net/.classpath
+++ b/java/libraries/net/.classpath
@@ -2,7 +2,7 @@
 
 	
 	
-	
+	
 		
 			
 		
diff --git a/java/libraries/pdf/.classpath b/java/libraries/pdf/.classpath
index 3cbcec3b2..6175a6d00 100644
--- a/java/libraries/pdf/.classpath
+++ b/java/libraries/pdf/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 		
 			
 		
diff --git a/java/libraries/serial/.classpath b/java/libraries/serial/.classpath
index bc1607f28..de63ac11a 100644
--- a/java/libraries/serial/.classpath
+++ b/java/libraries/serial/.classpath
@@ -1,7 +1,7 @@
 
 
 	
-	
+	
 		
 			
 		
diff --git a/java/libraries/svg/.classpath b/java/libraries/svg/.classpath
index 2b0e67a5a..cf20983c2 100644
--- a/java/libraries/svg/.classpath
+++ b/java/libraries/svg/.classpath
@@ -2,7 +2,7 @@
 
 	
 	
-	
+	
 		
 			
 		
diff --git a/java/libraries/svg/.gitignore b/java/libraries/svg/.gitignore
index 207db9ab9..50434fd98 100644
--- a/java/libraries/svg/.gitignore
+++ b/java/libraries/svg/.gitignore
@@ -1,2 +1 @@
-batik-bin-*.zip
 library/batik-all-*.jar
diff --git a/java/libraries/svg/build.xml b/java/libraries/svg/build.xml
index db7112d41..e103b6dd7 100644
--- a/java/libraries/svg/build.xml
+++ b/java/libraries/svg/build.xml
@@ -51,6 +51,7 @@
         
       
     
+    
   
 
   
diff --git a/java/src/processing/mode/java/ASTUtils.java b/java/src/processing/mode/java/ASTUtils.java
index b60639b1d..11d39287c 100644
--- a/java/src/processing/mode/java/ASTUtils.java
+++ b/java/src/processing/mode/java/ASTUtils.java
@@ -48,38 +48,37 @@ public class ASTUtils {
     ASTNode node = getASTNodeAt(root, startJavaOffset, stopJavaOffset);
 
     SimpleName result = null;
-
-    if (node == null) {
-      result = null;
-    } else if (node.getNodeType() == ASTNode.SIMPLE_NAME) {
-      result = (SimpleName) node;
-    } else {
-      // Return SimpleName with highest coverage
-      List simpleNames = getSimpleNameChildren(node);
-      if (!simpleNames.isEmpty()) {
-        // Compute coverage 
-        int[] coverages = simpleNames.stream()
+    if (node != null) {
+      if (node.getNodeType() == ASTNode.SIMPLE_NAME) {
+        result = (SimpleName) node;
+      } else {
+        // Return SimpleName with highest coverage
+        List simpleNames = getSimpleNameChildren(node);
+        if (!simpleNames.isEmpty()) {
+          // Compute coverage 
+          int[] coverages = simpleNames.stream()
             .mapToInt(name -> {
               int start = name.getStartPosition();
               int stop = start + name.getLength();
               return Math.min(stop, stopJavaOffset) -
-                  Math.max(startJavaOffset, start);
+                Math.max(startJavaOffset, start);
             })
             .toArray();
-        // Select node with highest coverage
-        int maxIndex = IntStream.range(0, simpleNames.size())
+          // Select node with highest coverage
+          int maxIndex = IntStream.range(0, simpleNames.size())
             .filter(i -> coverages[i] >= 0)
             .reduce((i, j) -> coverages[i] > coverages[j] ? i : j)
             .orElse(-1);
-        if (maxIndex == -1) return null;
-        result = simpleNames.get(maxIndex);
+          if (maxIndex == -1) return null;
+          result = simpleNames.get(maxIndex);
+        }
       }
     }
 
     if (result == null) {
       Messages.log("no simple name found");
     } else {
-      Messages.log("found " + node.toString());
+      Messages.log("found " + node);
     }
     return result;
   }
@@ -165,19 +164,19 @@ public class ASTUtils {
 
 
   protected static List findAllOccurrences(ASTNode root, String bindingKey) {
-    List occurences = new ArrayList<>();
+    List occurrences = new ArrayList<>();
     root.getRoot().accept(new ASTVisitor() {
       @Override
       public boolean visit(SimpleName name) {
         IBinding binding = resolveBinding(name);
         if (binding != null && bindingKey.equals(binding.getKey())) {
-          occurences.add(name);
+          occurrences.add(name);
         }
         return super.visit(name);
       }
     });
 
-    return occurences;
+    return occurrences;
   }
 
 }
diff --git a/java/src/processing/mode/java/ASTViewer.java b/java/src/processing/mode/java/ASTViewer.java
index cf08d8ef1..7b5cb239a 100644
--- a/java/src/processing/mode/java/ASTViewer.java
+++ b/java/src/processing/mode/java/ASTViewer.java
@@ -69,16 +69,14 @@ class ASTViewer {
 
     tree.addTreeSelectionListener(e -> {
       if (tree.getLastSelectedPathComponent() != null) {
-        DefaultMutableTreeNode tnode =
+        DefaultMutableTreeNode treeNode =
           (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
-        if (tnode.getUserObject() instanceof ASTNode) {
-          ASTNode node = (ASTNode) tnode.getUserObject();
+        if (treeNode.getUserObject() instanceof ASTNode) {
+          ASTNode node = (ASTNode) treeNode.getUserObject();
           pps.whenDone(ps -> {
             SketchInterval si = ps.mapJavaToSketch(node);
             if (!ps.inRange(si)) return;
-            EventQueue.invokeLater(() -> {
-              editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset);
-            });
+            EventQueue.invokeLater(() -> editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset));
           });
         }
       }
diff --git a/java/src/processing/mode/java/CompileErrorMessageSimplifier.java b/java/src/processing/mode/java/CompileErrorMessageSimplifier.java
index 87d5c8038..1fe446525 100644
--- a/java/src/processing/mode/java/CompileErrorMessageSimplifier.java
+++ b/java/src/processing/mode/java/CompileErrorMessageSimplifier.java
@@ -52,7 +52,7 @@ public class CompileErrorMessageSimplifier {
   private static void prepareConstantsList() {
     constantsMap = new TreeMap<>();
     Class probClass = DefaultProblem.class;
-    Field f[] = probClass.getFields();
+    Field[] f = probClass.getFields();
     for (Field field : f) {
       if (Modifier.isStatic(field.getModifiers()))
         try {
@@ -88,7 +88,7 @@ public class CompileErrorMessageSimplifier {
   public static String getSimplifiedErrorMessage(IProblem iprob, String badCode) {
     if (iprob == null) return null;
 
-    String args[] = iprob.getArguments();
+    String[] args = iprob.getArguments();
 
     if (DEBUG) {
       Messages.log("Simplifying message: " + iprob.getMessage() +
@@ -214,10 +214,6 @@ public class CompileErrorMessageSimplifier {
           } else {
             result = Language.interpolate("editor.status.wrong_param",
                                           args[1], args[1], removePackagePrefixes(args[2]));
-//          String method = q(args[1]);
-//          String methodDef = " \"" + args[1] + "(" + getSimpleName(args[2]) + ")\"";
-//          result = result.replace("method", method);
-//          result += methodDef;
           }
         }
         break;
@@ -256,8 +252,6 @@ public class CompileErrorMessageSimplifier {
       case IProblem.TypeMismatch:
         if (args.length > 1) {
           result = Language.interpolate("editor.status.type_mismatch", args[0], args[1]);
-//        result = result.replace("typeA", q(args[0]));
-//        result = result.replace("typeB", q(args[1]));
         }
         break;
 
@@ -313,17 +307,11 @@ public class CompileErrorMessageSimplifier {
       return input;
     }
     String[] names = PApplet.split(input, ',');
-//    List names = new ArrayList();
-//    if (inp.indexOf(',') >= 0) {
-//      names.addAll(Arrays.asList(inp.split(",")));
-//    } else {
-//      names.add(inp);
-//    }
     StringList result = new StringList();
     for (String name : names) {
       int dot = name.lastIndexOf('.');
       if (dot >= 0) {
-        name = name.substring(dot + 1, name.length());
+        name = name.substring(dot + 1);
       }
       result.append(name);
     }
diff --git a/java/src/processing/mode/java/Compiler.java b/java/src/processing/mode/java/Compiler.java
index dac5f5bdc..94448bf56 100644
--- a/java/src/processing/mode/java/Compiler.java
+++ b/java/src/processing/mode/java/Compiler.java
@@ -47,9 +47,6 @@ public class Compiler {
 
   /**
    * Compile with ECJ. See http://j.mp/8paifz for documentation.
-   *
-   * @param sketch Sketch object to be compiled, used for placing exceptions
-   * @param buildPath Where the temporary files live and will be built from.
    * @return true if successful.
    * @throws SketchException Only if there's a problem. Only then.
    */
@@ -57,12 +54,12 @@ public class Compiler {
 
     // This will be filled in if anyone gets angry
     SketchException exception = null;
-    boolean success = false;
+    boolean success;
 
     String classpath = build.getClassPath();
     String classpathEmptyRemoved = classpath.replace("::", ":");
 
-    String baseCommand[] = new String[] {
+    String[] baseCommand = new String[] {
       "-g",
       "-Xemacs",
       //"-noExit",  // not necessary for ecj
@@ -130,7 +127,7 @@ public class Compiler {
         new BufferedReader(new StringReader(errorBuffer.toString()));
       //System.err.println(errorBuffer.toString());
 
-      String line = null;
+      String line;
       while ((line = reader.readLine()) != null) {
         //System.out.println("got line " + line);  // debug
 
@@ -167,8 +164,7 @@ public class Compiler {
           exception = new SketchException(errorMessage);
         }
 
-        String[] parts = null;
-
+        String[] parts;
         if (errorMessage.startsWith("The import ") &&
             errorMessage.endsWith("cannot be resolved")) {
           // The import poo cannot be resolved
@@ -297,12 +293,10 @@ public class Compiler {
             break;
           }
         }
-        if (exception != null) {
-          // The stack trace just shows that this happened inside the compiler,
-          // which is a red herring. Don't ever show it for compiler stuff.
-          exception.hideStackTrace();
-          break;
-        }
+        // The stack trace just shows that this happened inside the compiler,
+        // which is a red herring. Don't ever show it for compiler stuff.
+        exception.hideStackTrace();
+        break;
       }
     } catch (IOException e) {
       String bigSigh = "Error while compiling. (" + e.getMessage() + ")";
@@ -325,7 +319,9 @@ public class Compiler {
   }
 
 
+  /*
   protected int caretColumn(String caretLine) {
     return caretLine.indexOf("^");
   }
+  */
 }
diff --git a/java/src/processing/mode/java/CompletionCandidate.java b/java/src/processing/mode/java/CompletionCandidate.java
index 97971f519..9dd29ed75 100644
--- a/java/src/processing/mode/java/CompletionCandidate.java
+++ b/java/src/processing/mode/java/CompletionCandidate.java
@@ -33,10 +33,10 @@ import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
 
 
 // TODO when building the label in some variants in this file,
-// getReturnType2() is used instead of getReturnType().
-// need to check whether that's identical in how it performs,
-// and if so, use makeLabel() and makeCompletion() more [fry 180326]
-// https://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.jdt.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fjdt%2Fcore%2Fdom%2FMethodDeclaration.html
+//      getReturnType2() is used instead of getReturnType().
+//      need to check whether that's identical in how it performs,
+//      and if so, use makeLabel() and makeCompletion() more [fry 180326]
+//      https://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.jdt.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fjdt%2Fcore%2Fdom%2FMethodDeclaration.html
 
 public class CompletionCandidate implements Comparable {
   private final String elementName;
@@ -56,7 +56,7 @@ public class CompletionCandidate implements Comparable {
 
   CompletionCandidate(Method method) {
     // return value ignored? [fry 180326]
-    method.getDeclaringClass().getName();
+    //method.getDeclaringClass().getName();
     elementName = method.getName();
     label = makeLabel(method);
     completion = makeCompletion(method);
@@ -140,7 +140,7 @@ public class CompletionCandidate implements Comparable {
 
 
   CompletionCandidate(Field f) {
-    f.getDeclaringClass().getName();
+    //f.getDeclaringClass().getName();
     elementName = f.getName();
     type = PREDEF_FIELD;
     label = "" +
@@ -171,9 +171,11 @@ public class CompletionCandidate implements Comparable {
   }
 
 
+  /*
   Object getWrappedObject() {
     return wrappedObject;
   }
+  */
 
 
   public String getElementName() {
@@ -288,7 +290,8 @@ public class CompletionCandidate implements Comparable {
       }
       labelBuilder.append(')');
       if (method.getReturnType() != null) {
-        labelBuilder.append(" : " + method.getReturnType().getSimpleName());
+        labelBuilder.append(" : ");
+        labelBuilder.append(method.getReturnType().getSimpleName());
       }
 
       labelBuilder.append(" - ");
diff --git a/java/src/processing/mode/java/CompletionGenerator.java b/java/src/processing/mode/java/CompletionGenerator.java
index 419da2845..8b1c7c2df 100644
--- a/java/src/processing/mode/java/CompletionGenerator.java
+++ b/java/src/processing/mode/java/CompletionGenerator.java
@@ -112,26 +112,21 @@ public class CompletionGenerator {
     }
 
     if (vdfs != null) {
-      CompletionCandidate ret[] = new CompletionCandidate[vdfs.size()];
+      CompletionCandidate[] outgoing = new CompletionCandidate[vdfs.size()];
       int i = 0;
       for (VariableDeclarationFragment vdf : vdfs) {
 //        ret[i++] = new CompletionCandidate(getNodeAsString2(vdf), "", "",
 //                                           CompletionCandidate.LOCAL_VAR);
-        ret[i++] = new CompletionCandidate(vdf);
+        outgoing[i++] = new CompletionCandidate(vdf);
       }
-      return ret;
+      return outgoing;
     }
-
     return null;
   }
 
   /**
    * Find the parent of the expression in a().b, this would give me the return
-   * type of a(), so that we can find all children of a() begininng with b
-   *
-   * @param nearestNode
-   * @param expression
-   * @return
+   * type of a(), so that we can find all children of a() beginning with b
    */
   public static ASTNode resolveExpression(ASTNode nearestNode,
                                           ASTNode expression, boolean noCompare) {
@@ -191,9 +186,6 @@ public class CompletionGenerator {
    * Finds the type of the expression in foo.bar().a().b, this would give me the
    * type of b if it exists in return type of a(). If noCompare is true,
    * it'll return type of a()
-   * @param nearestNode
-   * @param astNode
-   * @return
    */
   public static ClassMember resolveExpression3rdParty(PreprocSketch ps, ASTNode nearestNode,
                                                       ASTNode astNode, boolean noCompare) {
@@ -475,12 +467,8 @@ public class CompletionGenerator {
 
   /**
    * For a().abc.a123 this would return a123
-   *
-   * @param expression
-   * @return
    */
-  public static ASTNode getChildExpression(ASTNode expression) {
-//    ASTNode anode = null;
+  static public ASTNode getChildExpression(ASTNode expression) {
     if (expression instanceof SimpleName) {
       return expression;
     } else if (expression instanceof FieldAccess) {
@@ -497,8 +485,7 @@ public class CompletionGenerator {
     return null;
   }
 
-  public static ASTNode getParentExpression(ASTNode expression) {
-//  ASTNode anode = null;
+  static public ASTNode getParentExpression(ASTNode expression) {
     if (expression instanceof SimpleName) {
       return expression;
     } else if (expression instanceof FieldAccess) {
@@ -518,18 +505,13 @@ public class CompletionGenerator {
 
   /**
    * Loads classes from .jar files in sketch classpath
-   *
-   * @param typeName
-   * @param child
-   * @param noCompare
-   * @return
    */
-  public static ArrayList getMembersForType(PreprocSketch ps,
+  public static List getMembersForType(PreprocSketch ps,
                                                                  String typeName,
                                                                  String child,
                                                                  boolean noCompare,
                                                                  boolean staticOnly) {
-    ArrayList candidates = new ArrayList<>();
+    List candidates = new ArrayList<>();
     log("In GMFT(), Looking for match " + child
         + " in class " + typeName + " noCompare " + noCompare + " staticOnly "
         + staticOnly);
@@ -561,7 +543,7 @@ public class CompletionGenerator {
       {
         FieldDeclaration[] fields = td.getFields();
         for (FieldDeclaration field : fields) {
-          if (staticOnly && !isStatic(field.modifiers())) {
+          if (staticOnly && notStatic(field.modifiers())) {
             continue;
           }
           List vdfs = field.fragments();
@@ -576,7 +558,7 @@ public class CompletionGenerator {
       {
         MethodDeclaration[] methods = td.getMethods();
         for (MethodDeclaration method : methods) {
-          if (staticOnly && !isStatic(method.modifiers())) {
+          if (staticOnly && notStatic(method.modifiers())) {
             continue;
           }
           if (noCompare) {
@@ -616,7 +598,7 @@ public class CompletionGenerator {
         log("Couldn't find class " + tehClass.getTypeAsString());
         return candidates;
       }
-      log("Loaded " + probableClass.toString());
+      log("Loaded " + probableClass);
     }
     for (Method method : probableClass.getMethods()) {
       if (!Modifier.isStatic(method.getModifiers()) && staticOnly) {
@@ -668,19 +650,17 @@ public class CompletionGenerator {
     return candidates;
   }
 
-  private static boolean isStatic(List modifiers) {
+  private static boolean notStatic(List modifiers) {
     for (org.eclipse.jdt.core.dom.Modifier m : modifiers) {
-      if (m.isStatic()) return true;
+      if (m.isStatic()) return false;
     }
-    return false;
+    return true;
   }
 
 
   /**
    * Searches for the particular class in the default list of imports as well as
    * the Sketch classpath
-   * @param className
-   * @return
    */
   protected static Class findClassIfExists(PreprocSketch ps, String className){
     if (className == null){
@@ -903,8 +883,6 @@ public class CompletionGenerator {
 
   /**
    * Fetches line number of the node in its CompilationUnit.
-   * @param node
-   * @return
    */
   public static int getLineNumber(ASTNode node) {
     return ((CompilationUnit) node.getRoot()).getLineNumber(node
@@ -933,9 +911,6 @@ public class CompletionGenerator {
    * Give this thing a {@link Name} instance - a {@link SimpleName} from the
    * ASTNode for ex, and it tries its level best to locate its declaration in
    * the AST. It really does.
-   *
-   * @param findMe
-   * @return
    */
   protected static ASTNode findDeclaration(Name findMe) {
 
@@ -1128,9 +1103,6 @@ public class CompletionGenerator {
 
   /**
    * A variation of findDeclaration() but accepts an alternate parent ASTNode
-   * @param findMe
-   * @param alternateParent
-   * @return
    */
   protected static ASTNode findDeclaration2(Name findMe, ASTNode alternateParent) {
     ASTNode declaringClass;
@@ -1361,19 +1333,12 @@ public class CompletionGenerator {
    */
   public static class ClassMember {
     private Field field;
-
     private Method method;
-
     private Constructor cons;
-
     private Class thisclass;
-
     private String stringVal;
-
     private String classType;
-
     private ASTNode astNode;
-
     private ASTNode declaringNode;
 
     public ClassMember(Class m) {
@@ -1464,9 +1429,6 @@ public class CompletionGenerator {
 
   /**
    * Find the SimpleType from FD, SVD, VDS, etc
-   *
-   * @param node
-   * @return
    */
   public static SimpleType extracTypeInfo(ASTNode node) {
     if (node == null) {
@@ -1638,22 +1600,22 @@ public class CompletionGenerator {
       value = ((MethodInvocation) node).getName().toString() + " | "
           + className;
     else if (node instanceof FieldDeclaration)
-      value = node.toString() + " FldDecl | ";
+      value = node + " FldDecl | ";
     else if (node instanceof SingleVariableDeclaration)
       value = ((SingleVariableDeclaration) node).getName() + " - "
           + ((SingleVariableDeclaration) node).getType() + " | SVD ";
     else if (node instanceof ExpressionStatement)
-      value = node.toString() + className;
+      value = node + className;
     else if (node instanceof SimpleName)
       value = ((SimpleName) node).getFullyQualifiedName() + " | " + className;
     else if (node instanceof QualifiedName)
-      value = node.toString() + " | " + className;
+      value = node + " | " + className;
     else if(node instanceof FieldAccess)
-      value = node.toString() + " | ";
+      value = node + " | ";
     else if (className.startsWith("Variable"))
-      value = node.toString() + " | " + className;
+      value = node + " | " + className;
     else if (className.endsWith("Type"))
-      value = node.toString() + " | " + className;
+      value = node + " | " + className;
     value += " [" + node.getStartPosition() + ","
         + (node.getStartPosition() + node.getLength()) + "]";
     value += " Line: "
@@ -1725,16 +1687,16 @@ public class CompletionGenerator {
 
   /**
    * The main function that calculates possible code completion candidates
-   *
-   * @param pdePhrase
-   * @param line
-   * @param lineStartNonWSOffset
    */
   public List preparePredictions(final PreprocSketch ps,
                                                       final String pdePhrase,
                                                       final int lineNumber) {
     Messages.log("* preparePredictions");
 
+    if (ps.compilationUnit.types().size() == 0) {
+      return new ArrayList<>();
+    }
+
     ASTNode astRootNode = (ASTNode) ps.compilationUnit.types().get(0);
 
     // If the parsed code contains pde enhancements, take 'em out.
@@ -1829,16 +1791,13 @@ public class CompletionGenerator {
           if (td.getStructuralProperty(TypeDeclaration.SUPERCLASS_TYPE_PROPERTY) != null) {
             SimpleType st = (SimpleType) td.getStructuralProperty(TypeDeclaration.SUPERCLASS_TYPE_PROPERTY);
             log("Superclass " + st.getName());
-            ArrayList tempCandidates =
+            List tempCandidates =
                 getMembersForType(ps, st.getName().toString(), phrase, false, false);
-            for (CompletionCandidate can : tempCandidates) {
-              candidates.add(can);
-            }
-            //findDeclaration(st.getName())
+            candidates.addAll(tempCandidates);
           }
         }
         List sprops =
-            nearestNode.structuralPropertiesForType();
+          nearestNode.structuralPropertiesForType();
         for (StructuralPropertyDescriptor sprop : sprops) {
           ASTNode cnode;
           if (!sprop.isChildListProperty()) {
@@ -1918,7 +1877,7 @@ public class CompletionGenerator {
             expr.astNode.getNodeType() == ASTNode.SIMPLE_TYPE;
         boolean isMethod = expr.method != null;
         boolean staticOnly = !isMethod && !isArray && !isSimpleType;
-        log("Expr is " + expr.toString());
+        log("Expr is " + expr);
         String lookFor = (noCompare || (childExpr == null)) ?
             "" : childExpr.toString();
         candidates = getMembersForType(ps, expr, lookFor, noCompare, staticOnly);
diff --git a/java/src/processing/mode/java/ErrorChecker.java b/java/src/processing/mode/java/ErrorChecker.java
index 8cbcc9687..d6a26f609 100644
--- a/java/src/processing/mode/java/ErrorChecker.java
+++ b/java/src/processing/mode/java/ErrorChecker.java
@@ -32,15 +32,15 @@ class ErrorChecker {
   // Delay delivering error check result after last sketch change #2677
   private final static long DELAY_BEFORE_UPDATE = 650;
 
-  private ScheduledExecutorService scheduler;
+  final private ScheduledExecutorService scheduler;
   private volatile ScheduledFuture scheduledUiUpdate = null;
   private volatile long nextUiUpdate = 0;
-  private volatile boolean enabled = true;
+  private volatile boolean enabled;
 
   private final Consumer errorHandlerListener = this::handleSketchProblems;
 
-  private JavaEditor editor;
-  private PreprocService pps;
+  final private JavaEditor editor;
+  final private PreprocService pps;
 
 
   public ErrorChecker(JavaEditor editor, PreprocService pps) {
@@ -85,8 +85,6 @@ class ErrorChecker {
     Map suggCache =
         JavaMode.importSuggestEnabled ? new HashMap<>() : Collections.emptyMap();
 
-    final List problems = new ArrayList<>();
-
     IProblem[] iproblems;
     if (ps.compilationUnit == null) {
       iproblems = new IProblem[0];
@@ -94,7 +92,7 @@ class ErrorChecker {
       iproblems = ps.compilationUnit.getProblems();
     }
 
-    problems.addAll(ps.otherProblems);
+    final List problems = new ArrayList<>(ps.otherProblems);
 
     if (problems.isEmpty()) { // Check for curly quotes
       List curlyQuoteProblems = checkForCurlyQuotes(ps);
diff --git a/java/src/processing/mode/java/JavaBuild.java b/java/src/processing/mode/java/JavaBuild.java
index 152ad14af..dba121cba 100644
--- a/java/src/processing/mode/java/JavaBuild.java
+++ b/java/src/processing/mode/java/JavaBuild.java
@@ -49,7 +49,7 @@ public class JavaBuild {
     "(?:^|\\s|;)package\\s+(\\S+)\\;";
 
   static public final String JAVA_DOWNLOAD_URL = "https://adoptopenjdk.net/";
-  static public final String MIN_JAVA_VERSION = "11.0.8";
+  static public final String MIN_JAVA_VERSION = "11.0.11";
 
   protected Sketch sketch;
   protected Mode mode;
@@ -86,7 +86,7 @@ public class JavaBuild {
   /**
    * Run the build inside a temporary build folder. Used for run/present.
    * @return null if compilation failed, main class name if not
-   * @throws SketchException
+   * @throws SketchException details of where the build choked
    */
   public String build(boolean sizeWarning) throws SketchException {
     return build(sketch.makeTempFolder(), sketch.makeTempFolder(), sizeWarning);
@@ -150,7 +150,7 @@ public class JavaBuild {
    * @param packageName null, or the package name that should be used as default
    * @param preprocessor the preprocessor object ready to do the work
    * @return main PApplet class name found during preprocess, or null if error
-   * @throws SketchException
+   * @throws SketchException details of where the preprocessing failed
    */
   public String preprocess(File srcFolder,
                            String packageName,
@@ -232,11 +232,8 @@ public class JavaBuild {
       outputFolder.mkdirs();
 //      Base.openFolder(outputFolder);
       final File java = new File(outputFolder, sketch.getName() + ".java");
-      final PrintWriter stream = PApplet.createWriter(java);
-      try {
+      try (PrintWriter stream = PApplet.createWriter(java)) {
         result = preprocessor.write(stream, bigCode.toString(), codeFolderPackages);
-      } finally {
-        stream.close();
       }
     } catch (SketchException pe) {
       // RunnerExceptions are caught here and re-thrown, so that they don't
@@ -281,7 +278,13 @@ public class JavaBuild {
       if (library != null) {
         if (!importedLibraries.contains(library)) {
           importedLibraries.add(library);
+          // don't add the JavaFX libraries to the classpath
+          // https://github.com/processing/processing4/issues/212
+          // Disabling this after all, because we need our javafx.jar (PGraphicsJavaFX)
+          // and the JavaFX .jars are safely tucked into a "modules" subfolder
+          //if (!library.getName().equals("JavaFX")) {
           classPath += library.getClassPath();
+          //}
           javaLibraryPath += File.pathSeparator + library.getNativePath();
         }
       } else {
@@ -289,9 +292,8 @@ public class JavaBuild {
         // If someone insists on unnecessarily repeating the code folder
         // import, don't show an error for it.
         if (codeFolderPackages != null) {
-          String itemPkg = entry;
           for (String pkg : codeFolderPackages) {
-            if (pkg.equals(itemPkg)) {
+            if (pkg.equals(entry)) {
               found = true;
               break;
             }
@@ -306,6 +308,11 @@ public class JavaBuild {
       }
     }
 
+    // Turning this off after 4.0 alpha 5, to see if everything still works.
+    // Including this classpath is really problematic (many possible conflicts,
+    // these are classes that won't be available on export, etc etc...)
+    // Also avoids accidentally hiding other potential classpath bugs.
+    /*
     // Finally, add the regular Java CLASSPATH. This contains everything
     // imported by the PDE itself (core.jar, pde.jar, quaqua.jar) which may
     // in fact be more of a problem.
@@ -315,6 +322,7 @@ public class JavaBuild {
       javaClassPath = javaClassPath.substring(1, javaClassPath.length() - 1);
     }
     classPath += File.pathSeparator + javaClassPath;
+    */
 
     // But make sure that there isn't anything in there that's missing,
     // otherwise ECJ will complain and die. For instance, Java 1.7 (or maybe
@@ -440,6 +448,15 @@ public class JavaBuild {
   }
 
 
+//  /** Returns the dummy "module" path so that JavaFX doesn't complain. */
+//  public String getModulePath() {
+//    // Just set this to the main core/library directory to pick up JavaFX
+//    //return mode.getCoreLibrary().getLibraryPath();
+//    File folder = new File(mode.getFolder(), "libraries/javafx/library");
+//    return folder.getAbsolutePath();
+//  }
+
+
   /** Return the java.library.path for this sketch (for all the native DLLs etc). */
   public String getJavaLibraryPath() {
     return javaLibraryPath;
@@ -822,6 +839,14 @@ public class JavaBuild {
       runOptions.append("-Djava.library.path=\"%EXEDIR%\\lib\"");
     }
 
+    Library javafx = findJavaFX();
+    if (javafx != null) {
+      String modulePath = exportPlatform == PConstants.MACOS ?
+        "$APP_ROOT/Contents/Java/modules" : "lib/modules";
+      for (String opt : getArgsJavaFX(modulePath)) {
+        runOptions.append(opt);
+      }
+    }
 
     /// macosx: write out Info.plist (template for classpath, etc)
 
@@ -834,6 +859,7 @@ public class JavaBuild {
         runOptionsXML.append('\n');
       }
 
+
       String PLIST_TEMPLATE = "Info.plist.tmpl";
       File plistTemplate = new File(sketch.getFolder(), PLIST_TEMPLATE);
       if (!plistTemplate.exists()) {
@@ -842,9 +868,9 @@ public class JavaBuild {
       File plistFile = new File(dotAppFolder, "Contents/Info.plist");
       PrintWriter pw = PApplet.createWriter(plistFile);
 
-      String lines[] = PApplet.loadStrings(plistTemplate);
+      String[] lines = PApplet.loadStrings(plistTemplate);
       for (int i = 0; i < lines.length; i++) {
-        if (lines[i].indexOf("@@") != -1) {
+        if (lines[i].contains("@@")) {
           StringBuilder sb = new StringBuilder(lines[i]);
           int index = 0;
           while ((index = sb.indexOf("@@jvm_runtime@@")) != -1) {
@@ -883,6 +909,7 @@ public class JavaBuild {
 
     } else if (exportPlatform == PConstants.WINDOWS) {
       File buildFile = new File(destFolder, "launch4j-build.xml");
+      System.out.println(buildFile);
       File configFile = new File(destFolder, "launch4j-config.xml");
 
       XML project = new XML("project");
@@ -925,6 +952,13 @@ public class JavaBuild {
       for (String opt : runOptions) {
         jre.addChild("opt").setContent(opt);
       }
+      /*
+      if (javafx != null) {
+        for (String opt : getArgsJavaFX("lib")) {
+          jre.addChild("opt").setContent(opt);
+        }
+      }
+      */
 
       config.save(configFile);
       project.save(buildFile);
@@ -1001,6 +1035,34 @@ public class JavaBuild {
   }
 
 
+  // This is a workaround until a more complete solution is found.
+  public Library findJavaFX() {
+    for (Library library : getImportedLibraries()) {
+      if (library.getName().equals("JavaFX")) {
+        return library;
+      }
+    }
+    return null;
+  }
+
+
+  static public String[] getArgsJavaFX(String modulePath) {
+    return new String[] {
+      "--module-path", modulePath,
+
+      // Full list of modules, let's not commit to all of these unless
+      // a compelling argument is made or a reason presents itself.
+      //"javafx.base,javafx.controls,javafx.fxml,javafx.graphics,javafx.media,javafx.swing,javafx.web"
+      "--add-modules", "javafx.base,javafx.graphics,javafx.swing",
+
+      // TODO Presumably, this is only because com.sun.* classes are being used?
+      // https://github.com/processing/processing4/issues/208
+      "--add-exports", "javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED",
+      "--add-exports", "javafx.graphics/com.sun.glass.ui=ALL-UNNAMED"
+    };
+  }
+
+
   static Boolean xcodeInstalled;
 
   static protected boolean isXcodeInstalled() {
@@ -1010,7 +1072,7 @@ public class JavaBuild {
       int result = -1;
       try {
         result = p.waitFor();
-      } catch (InterruptedException e) { }
+      } catch (InterruptedException ignored) { }
       // returns 0 if installed, 2 if not (-1 if exception)
       xcodeInstalled = (result == 0);
     }
@@ -1097,25 +1159,22 @@ public class JavaBuild {
 
 
   protected void addClasses(ZipOutputStream zos, File dir, String rootPath) throws IOException {
-    File files[] = dir.listFiles(new FilenameFilter() {
-      public boolean accept(File dir, String name) {
-        return (name.charAt(0) != '.');
-      }
-    });
-    for (File sub : files) {
-      String relativePath = sub.getAbsolutePath().substring(rootPath.length());
-//      System.out.println("relative path is " + relativePath);
+    File[] files = dir.listFiles((dir1, name) -> (name.charAt(0) != '.'));
+    if (files != null) {
+      for (File sub : files) {
+        String relativePath = sub.getAbsolutePath().substring(rootPath.length());
 
-      if (sub.isDirectory()) {
-        addClasses(zos, sub, rootPath);
+        if (sub.isDirectory()) {
+          addClasses(zos, sub, rootPath);
 
-      } else if (sub.getName().endsWith(".class")) {
+        } else if (sub.getName().endsWith(".class")) {
 //        System.out.println("  adding item " + relativePath);
-        ZipEntry entry = new ZipEntry(relativePath);
-        zos.putNextEntry(entry);
-        //zos.write(Base.loadBytesRaw(sub));
-        PApplet.saveStream(zos, new FileInputStream(sub));
-        zos.closeEntry();
+          ZipEntry entry = new ZipEntry(relativePath);
+          zos.putNextEntry(entry);
+          //zos.write(Base.loadBytesRaw(sub));
+          PApplet.saveStream(zos, new FileInputStream(sub));
+          zos.closeEntry();
+        }
       }
     }
   }
@@ -1157,21 +1216,18 @@ public class JavaBuild {
     throws IOException {
     String[] pieces = PApplet.split(path, File.pathSeparatorChar);
 
-    for (int i = 0; i < pieces.length; i++) {
-      if (pieces[i].length() == 0) continue;
+    for (String piece : pieces) {
+      if (piece.length() == 0) continue;
 
       // is it a jar file or directory?
-      if (pieces[i].toLowerCase().endsWith(".jar") ||
-          pieces[i].toLowerCase().endsWith(".zip")) {
+      if (piece.toLowerCase().endsWith(".jar") ||
+              piece.toLowerCase().endsWith(".zip")) {
         try {
-          ZipFile file = new ZipFile(pieces[i]);
+          ZipFile file = new ZipFile(piece);
           Enumeration entries = file.entries();
           while (entries.hasMoreElements()) {
             ZipEntry entry = (ZipEntry) entries.nextElement();
-            if (entry.isDirectory()) {
-              // actually 'continue's for all dir entries
-
-            } else {
+            if (!entry.isDirectory()) {
               String entryName = entry.getName();
               // ignore contents of the META-INF folders
               if (entryName.indexOf("META-INF") == 0) continue;
@@ -1183,7 +1239,7 @@ public class JavaBuild {
               ZipEntry entree = new ZipEntry(entryName);
 
               zos.putNextEntry(entree);
-              byte buffer[] = new byte[(int) entry.getSize()];
+              byte[] buffer = new byte[(int) entry.getSize()];
               InputStream is = file.getInputStream(entry);
 
               int offset = 0;
@@ -1202,11 +1258,11 @@ public class JavaBuild {
           file.close();
 
         } catch (IOException e) {
-          System.err.println("Error in file " + pieces[i]);
+          System.err.println("Error in file " + piece);
           e.printStackTrace();
         }
       } else {  // not a .jar or .zip, prolly a directory
-        File dir = new File(pieces[i]);
+        File dir = new File(piece);
         // but must be a dir, since it's one of several paths
         // just need to check if it exists
         if (dir.exists()) {
@@ -1226,31 +1282,35 @@ public class JavaBuild {
                                                           String sofar,
                                                           ZipOutputStream zos)
     throws IOException {
-    String files[] = dir.list();
-    for (int i = 0; i < files.length; i++) {
-      // ignore . .. and .DS_Store
-      if (files[i].charAt(0) == '.') continue;
+    String[] files = dir.list();
+    if (files != null) {
+      for (String filename : files) {
+        // ignore . .. and .DS_Store
+        if (filename.charAt(0) == '.') continue;
 
-      File sub = new File(dir, files[i]);
-      String nowfar = (sofar == null) ?
-        files[i] : (sofar + "/" + files[i]);
+        File sub = new File(dir, filename);
+        String nowfar = (sofar == null) ?
+                filename : (sofar + "/" + filename);
 
-      if (sub.isDirectory()) {
-        packClassPathIntoZipFileRecursive(sub, nowfar, zos);
+        if (sub.isDirectory()) {
+          packClassPathIntoZipFileRecursive(sub, nowfar, zos);
 
-      } else {
-        // don't add .jar and .zip files, since they only work
-        // inside the root, and they're unpacked
-        if (!files[i].toLowerCase().endsWith(".jar") &&
-            !files[i].toLowerCase().endsWith(".zip") &&
-            files[i].charAt(0) != '.') {
-          ZipEntry entry = new ZipEntry(nowfar);
-          zos.putNextEntry(entry);
-          //zos.write(Base.loadBytesRaw(sub));
-          PApplet.saveStream(zos, new FileInputStream(sub));
-          zos.closeEntry();
+        } else {
+          // don't add .jar and .zip files, since they only work
+          // inside the root, and they're unpacked
+          if (!filename.toLowerCase().endsWith(".jar") &&
+                  !filename.toLowerCase().endsWith(".zip") &&
+                  filename.charAt(0) != '.') {
+            ZipEntry entry = new ZipEntry(nowfar);
+            zos.putNextEntry(entry);
+            //zos.write(Base.loadBytesRaw(sub));
+            PApplet.saveStream(zos, new FileInputStream(sub));
+            zos.closeEntry();
+          }
         }
       }
+    } else {
+      System.err.println("Could not read from " + dir);
     }
   }
 }
diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java
index ea6667628..692a11a97 100644
--- a/java/src/processing/mode/java/JavaEditor.java
+++ b/java/src/processing/mode/java/JavaEditor.java
@@ -1687,10 +1687,8 @@ public class JavaEditor extends Editor {
   /**
    * Returns a list of AvailableContributions of those libraries that the user
    * wants imported, but that are not installed.
-   *
-   * @param importHeaders
    */
-  private List getNotInstalledAvailableLibs(ArrayList importHeadersList) {
+  private List getNotInstalledAvailableLibs(List importHeadersList) {
     Map importMap =
       ContributionListing.getInstance().getLibrariesByImportHeader();
     List libList = new ArrayList<>();
@@ -2316,7 +2314,7 @@ public class JavaEditor extends Editor {
 
 
   @Override
-  protected void applyPreferences() {
+  public void applyPreferences() {
     super.applyPreferences();
 
     if (jmode != null) {
diff --git a/java/src/processing/mode/java/JavaTextArea.java b/java/src/processing/mode/java/JavaTextArea.java
index cd5b78be6..f35e54317 100644
--- a/java/src/processing/mode/java/JavaTextArea.java
+++ b/java/src/processing/mode/java/JavaTextArea.java
@@ -32,7 +32,6 @@ import java.util.Collections;
 import java.util.List;
 
 import javax.swing.DefaultListModel;
-import javax.swing.SwingWorker;
 
 import processing.app.Messages;
 import processing.app.Platform;
@@ -170,6 +169,8 @@ public class JavaTextArea extends PdeTextArea {
   private void processCompletionKeys(final KeyEvent event) {
     char keyChar = event.getKeyChar();
     int keyCode = event.getKeyCode();
+
+    //noinspection StatementWithEmptyBody
     if (keyChar == KeyEvent.VK_ENTER ||
         keyChar == KeyEvent.VK_ESCAPE ||
         keyChar == KeyEvent.VK_TAB ||
@@ -200,7 +201,7 @@ public class JavaTextArea extends PdeTextArea {
           //}
         }
       } else {
-        hideSuggestion(); // hide on spacebar
+        hideSuggestion(); // hide on space bar
       }
     } else {
       if (JavaMode.codeCompletionsEnabled) {
@@ -224,15 +225,12 @@ public class JavaTextArea extends PdeTextArea {
 
   CompletionGenerator suggestionGenerator;
 
-  SwingWorker suggestionWorker = null;
-
   volatile boolean suggestionRunning = false;
   volatile boolean suggestionRequested = false;
 
   /**
    * Retrieves the current word typed just before the caret.
    * Then triggers code completion for that word.
-   * @param evt - the KeyEvent which triggered this method
    */
   protected void fetchPhrase() {
     if (suggestionRunning) {
@@ -291,7 +289,7 @@ public class JavaTextArea extends PdeTextArea {
     getJavaEditor().getPreprocessingService().whenDone(ps -> {
       int lineNumber = ps.tabOffsetToJavaLine(codeIndex, lineStartOffset);
 
-      String phrase = null;
+      String phrase;
       DefaultListModel defListModel = null;
 
       try {
@@ -379,7 +377,7 @@ public class JavaTextArea extends PdeTextArea {
     final int currentCharIndex = lineText.length() - 1;
 
     { // Check if the caret is in the comment
-      int commentStart = lineText.indexOf("//", 0);
+      int commentStart = lineText.indexOf("//");
       if (commentStart >= 0 && currentCharIndex > commentStart) {
         return null;
       }
@@ -470,6 +468,7 @@ public class JavaTextArea extends PdeTextArea {
         case '[':
           break parseLoop; // End of scope
         case ']': // Grab the whole region in square brackets
+        case ')': // Grab the whole region in brackets
           position = isInBrackets.previousClearBit(position-1);
           break;
         case '(':
@@ -478,9 +477,6 @@ public class JavaTextArea extends PdeTextArea {
             break;
           }
           break parseLoop; // End of scope
-        case ')': // Grab the whole region in brackets
-          position = isInBrackets.previousClearBit(position-1);
-          break;
         case '"': // Grab the whole literal and quit
           position = isInLiteral.previousClearBit(position - 1);
           break parseLoop;
@@ -499,7 +495,7 @@ public class JavaTextArea extends PdeTextArea {
     position++;
 
     // Extract phrase
-    String phrase = lineText.substring(position, lineText.length()).trim();
+    String phrase = lineText.substring(position).trim();
     Messages.log(phrase);
 
     if (phrase.length() == 0 || Character.isDigit(phrase.charAt(0))) {
diff --git a/java/src/processing/mode/java/JavaTextAreaPainter.java b/java/src/processing/mode/java/JavaTextAreaPainter.java
index b9e2ad754..9a951647d 100644
--- a/java/src/processing/mode/java/JavaTextAreaPainter.java
+++ b/java/src/processing/mode/java/JavaTextAreaPainter.java
@@ -63,9 +63,7 @@ public class JavaTextAreaPainter extends PdeTextAreaPainter {
   // TWEAK MODE
 
 
-	protected int horizontalAdjustment = 0;
-
-	public boolean tweakMode = false;
+	public boolean tweakMode;
 	public List> handles;
 	public List> colorBoxes;
 
@@ -310,7 +308,7 @@ public class JavaTextAreaPainter extends PdeTextAreaPainter {
 
 
 	static private String replaceString(String str, int start, int end, String put) {
-		return str.substring(0, start) + put + str.substring(end, str.length());
+		return str.substring(0, start) + put + str.substring(end);
 	}
 
 
diff --git a/java/src/processing/mode/java/PreprocService.java b/java/src/processing/mode/java/PreprocService.java
index ea6bd2b8f..9f67461b9 100644
--- a/java/src/processing/mode/java/PreprocService.java
+++ b/java/src/processing/mode/java/PreprocService.java
@@ -93,13 +93,11 @@ public class PreprocService {
       complete(null); // initialization block
     }};
 
-  private volatile boolean enabled = true;
+  private volatile boolean enabled;
 
   /**
    * Create a new preprocessing service to support an editor.
-   *
-   * @param editor The editor that will be supported by this service and to which issues should be
-   *    reported.
+   * @param editor The editor supported by this service and receives issues.
    */
   public PreprocService(JavaEditor editor) {
     this.editor = editor;
@@ -139,7 +137,7 @@ public class PreprocService {
           try {
             runningCallbacks.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
             runningCallbacks = null;
-          } catch (TimeoutException e) { }
+          } catch (TimeoutException ignored) { }
         }
 
         synchronized (requestLock) {
@@ -189,7 +187,7 @@ public class PreprocService {
   }
 
   /**
-   * Indicate to this service that the sketch libarries have changed.
+   * Indicate to this service that the sketch libraries have changed.
    */
   public void notifyLibrariesChanged() {
     Messages.log("PPS: notified libraries changed");
@@ -201,7 +199,7 @@ public class PreprocService {
    * Indicate to this service that the folder housing sketch code has changed.
    */
   public void notifyCodeFolderChanged() {
-    Messages.log("PPS: snotified code folder changed");
+    Messages.log("PPS: notified code folder changed");
     codeFolderChanged.set(true);
     notifySketchChanged();
   }
@@ -255,8 +253,6 @@ public class PreprocService {
    * Note that this callback will only be executed once and it is distinct from registering a
    * listener below which will receive all future updates.
    * 

- * - * @param callback */ public void whenDoneBlocking(Consumer callback) { if (!enabled) return; @@ -272,11 +268,11 @@ public class PreprocService { /// LISTENERS ---------------------------------------------------------------- - private Set> listeners = new CopyOnWriteArraySet<>(); + private final Set> listeners = new CopyOnWriteArraySet<>(); /** - * Register a consumer that will receive all {PreprocessedSketch}es produced from this service. - * + * Register a consumer that will receive all {PreprocessedSketch}es + * produced from this service. * @param listener The listener to receive all future {PreprocessedSketch}es. */ public void registerListener(Consumer listener) { @@ -423,7 +419,6 @@ public class PreprocService { .forEach(result.otherProblems::add); result.hasSyntaxErrors = true; - return result.build(); } // Save off the imports @@ -491,8 +486,8 @@ public class PreprocService { CompilationUnit compilableCU = (CompilationUnit) parser.createAST(null); // Get syntax problems from compilable AST - result.hasSyntaxErrors |= Arrays.stream(compilableCU.getProblems()) - .anyMatch(IProblem::isError); + result.hasSyntaxErrors |= + Arrays.stream(compilableCU.getProblems()).anyMatch(IProblem::isError); // Generate bindings after getting problems - avoids // 'missing type' errors when there are syntax problems @@ -507,8 +502,8 @@ public class PreprocService { // Get compilation problems List bindingsProblems = Arrays.asList(bindingsCU.getProblems()); - result.hasCompilationErrors = bindingsProblems.stream() - .anyMatch(IProblem::isError); + result.hasCompilationErrors = + bindingsProblems.stream().anyMatch(IProblem::isError); // Update builder result.offsetMapper = parsableMapper.thenMapping(compilableMapper); @@ -586,9 +581,9 @@ public class PreprocService { - /// CLASSPATHS --------------------------------------------------------------- + /// CLASS PATHS -------------------------------------------------------------- - private RuntimePathBuilder runtimePathBuilder = new RuntimePathBuilder(); + private final RuntimePathBuilder runtimePathBuilder = new RuntimePathBuilder(); /// -------------------------------------------------------------------------- diff --git a/java/src/processing/mode/java/Rename.java b/java/src/processing/mode/java/Rename.java index 80bbe846a..4b4bf1966 100644 --- a/java/src/processing/mode/java/Rename.java +++ b/java/src/processing/mode/java/Rename.java @@ -6,8 +6,6 @@ import static processing.mode.java.ASTUtils.resolveBinding; import java.awt.Dimension; import java.awt.EventQueue; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.util.ArrayList; @@ -72,12 +70,7 @@ class Rename { JRootPane rootPane = window.getRootPane(); window.setTitle("Rename"); window.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); - Toolkit.registerWindowCloseKeys(rootPane, new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - window.setVisible(false); - } - }); + Toolkit.registerWindowCloseKeys(rootPane, e -> window.setVisible(false)); Toolkit.setIcon(window); window.setModal(true); @@ -89,8 +82,6 @@ class Rename { ps = null; } }); - //window.setSize(Toolkit.zoom(250, 130)); - //window.setLayout(new BoxLayout(window.getContentPane(), BoxLayout.Y_AXIS)); Box windowBox = Box.createVerticalBox(); Toolkit.setBorder(windowBox); final int GAP = Toolkit.zoom(5); @@ -126,8 +117,7 @@ class Rename { renameButton.addActionListener(e -> { final String newName = textField.getText().trim(); if (!newName.isEmpty()) { - if (newName.length() >= 1 && - newName.chars().limit(1).allMatch(Character::isJavaIdentifierStart) && + if (newName.chars().limit(1).allMatch(Character::isJavaIdentifierStart) && newName.chars().skip(1).allMatch(Character::isJavaIdentifierPart)) { rename(ps, binding, newName); window.setVisible(false); @@ -140,11 +130,7 @@ class Rename { }); rootPane.setDefaultButton(renameButton); - //JPanel panelBottom = new JPanel(); - //panelBottom.setLayout(new BoxLayout(panelBottom, BoxLayout.X_AXIS)); Box buttonBox = Box.createHorizontalBox(); - //Toolkit.setBorder(panelBottom, 5, 5, 5, 5); - buttonBox.add(Box.createHorizontalGlue()); buttonBox.add(showUsageButton); if (!Platform.isMacOS()) { @@ -253,8 +239,8 @@ class Rename { showUsage.hide(); - List occurrences = new ArrayList<>(); - occurrences.addAll(findAllOccurrences(root, binding.getKey())); + List occurrences = + new ArrayList<>(findAllOccurrences(root, binding.getKey())); // Renaming class should rename all constructors if (binding.getKind() == IBinding.TYPE) { @@ -276,29 +262,28 @@ class Rename { editor.startCompoundEdit(); - mappedNodes.entrySet().forEach(entry -> { - int tabIndex = entry.getKey(); + mappedNodes.forEach((key, nodes) -> { + int tabIndex = key; SketchCode sketchCode = sketch.getCode(tabIndex); SyntaxDocument document = (SyntaxDocument) sketchCode.getDocument(); - List nodes = entry.getValue(); nodes.stream() - // Replace from the end so all unprocess offsets stay valid - .sorted(Comparator.comparing((SketchInterval si) -> si.startTabOffset).reversed()) - .forEach(si -> { - // Make sure offsets are in bounds - int documentLength = document.getLength(); - if (si.startTabOffset >= 0 && si.startTabOffset <= documentLength && - si.stopTabOffset >= 0 && si.stopTabOffset <= documentLength) { - // Replace the code - int length = si.stopTabOffset - si.startTabOffset; - try { - document.remove(si.startTabOffset, length); - document.insertString(si.startTabOffset, newName, null); - } catch (BadLocationException e) { /* Whatever */ } - } - }); + // Replace from the end so all unprocessed offsets stay valid + .sorted(Comparator.comparing((SketchInterval si) -> si.startTabOffset).reversed()) + .forEach(si -> { + // Make sure offsets are in bounds + int documentLength = document.getLength(); + if (si.startTabOffset >= 0 && si.startTabOffset <= documentLength && + si.stopTabOffset >= 0 && si.stopTabOffset <= documentLength) { + // Replace the code + int length = si.stopTabOffset - si.startTabOffset; + try { + document.remove(si.startTabOffset, length); + document.insertString(si.startTabOffset, newName, null); + } catch (BadLocationException e) { /* Whatever */ } + } + }); try { sketchCode.setProgram(document.getText(0, document.getLength())); diff --git a/java/src/processing/mode/java/RuntimePathBuilder.java b/java/src/processing/mode/java/RuntimePathBuilder.java index 2e04fd9d6..1ae5fafed 100644 --- a/java/src/processing/mode/java/RuntimePathBuilder.java +++ b/java/src/processing/mode/java/RuntimePathBuilder.java @@ -69,6 +69,7 @@ public class RuntimePathBuilder { /** * The modules comprising the Java standard modules. */ + @SuppressWarnings("SpellCheckingInspection") protected static final String[] STANDARD_MODULES = { "java.base.jmod", "java.compiler.jmod", @@ -445,17 +446,18 @@ public class RuntimePathBuilder { * @param sketch The sketch provided by the user. * @return List of classpath and/or module path entries. */ - protected List buildLibrarySketchPath(JavaMode mode, List imports, - Sketch sketch) { + protected List buildLibrarySketchPath(JavaMode mode, + List imports, + Sketch sketch) { StringJoiner classPathBuilder = new StringJoiner(File.pathSeparator); imports.stream() .map(ImportStatement::getPackageName) - .filter(pckg -> !isIgnorableForSketchPath(pckg)) - .map(pckg -> { + .filter(pkg -> !isIgnorableForSketchPath(pkg)) + .map(pkg -> { try { - return mode.getLibrary(pckg); + return mode.getLibrary(pkg); } catch (SketchException e) { return null; } @@ -604,7 +606,7 @@ public class RuntimePathBuilder { * * Note that these are protected so that they can be tested. The interface below defines a * strategy for determining path elements. An optional caching object which allows for path - * invalidatino is also defined below. + * invalidation is also defined below. */ /** @@ -641,8 +643,8 @@ public class RuntimePathBuilder { */ protected static class CachedRuntimePathFactory implements RuntimePathFactoryStrategy { - private AtomicReference> cachedResult; - private RuntimePathFactoryStrategy innerStrategy; + final private AtomicReference> cachedResult; + final private RuntimePathFactoryStrategy innerStrategy; /** * Create a new cache around {RuntimePathFactoryStrategy}. diff --git a/java/src/processing/mode/java/preproc/PdeParseTreeListener.java b/java/src/processing/mode/java/preproc/PdeParseTreeListener.java index f1520c43e..70918cc5e 100644 --- a/java/src/processing/mode/java/preproc/PdeParseTreeListener.java +++ b/java/src/processing/mode/java/preproc/PdeParseTreeListener.java @@ -255,9 +255,20 @@ public class PdeParseTreeListener extends ProcessingBaseListener { /** * Get the result of the last preprocessing. * + * @param issues The errors (if any) encountered. * @return The result of the last preprocessing. */ public PreprocessorResult getResult() { + return getResult(new ArrayList<>()); + } + + /** + * Get the result of the last preprocessing with optional error. + * + * @param issues The errors (if any) encountered. + * @return The result of the last preprocessing. + */ + public PreprocessorResult getResult(List issues) { List allImports = new ArrayList<>(); allImports.addAll(coreImports); @@ -277,7 +288,8 @@ public class PdeParseTreeListener extends ProcessingBaseListener { allImports, allEdits, sketchWidth, - sketchHeight + sketchHeight, + issues ); } diff --git a/java/src/processing/mode/java/preproc/PdePreprocessor.java b/java/src/processing/mode/java/preproc/PdePreprocessor.java index b7961cef4..fad1e8c43 100644 --- a/java/src/processing/mode/java/preproc/PdePreprocessor.java +++ b/java/src/processing/mode/java/preproc/PdePreprocessor.java @@ -208,20 +208,11 @@ public class PdePreprocessor { )); parser.setBuildParseTree(true); tree = parser.processingSketch(); - - if (preprocessIssues.size() > 0) { - return PreprocessorResult.reportPreprocessIssues(preprocessIssues); - } } ParseTreeWalker treeWalker = new ParseTreeWalker(); treeWalker.walk(listener, tree); - // Check for issues encountered in walk - if (treeIssues.size() > 0) { - return PreprocessorResult.reportPreprocessIssues(treeIssues); - } - // Return resulting program String outputProgram = listener.getOutputProgram(); PrintWriter outPrintWriter = new PrintWriter(outWriter); @@ -229,7 +220,13 @@ public class PdePreprocessor { foundMain = listener.foundMain(); - return listener.getResult(); + if (preprocessIssues.size() > 0) { + return listener.getResult(preprocessIssues); + } else if (treeIssues.size() > 0) { + return listener.getResult(treeIssues); + } else { + return listener.getResult(); + } } /** diff --git a/java/src/processing/mode/java/preproc/PreprocessorResult.java b/java/src/processing/mode/java/preproc/PreprocessorResult.java index 2c2d8b729..680f75bbf 100644 --- a/java/src/processing/mode/java/preproc/PreprocessorResult.java +++ b/java/src/processing/mode/java/preproc/PreprocessorResult.java @@ -69,8 +69,8 @@ public class PreprocessorResult { * @param newSketchHeight The height of the sketch in pixels or special value like displayWidth; */ public PreprocessorResult(PdePreprocessor.Mode newProgramType, int newHeaderOffset, - String newClassName, List newImportStatements, List newEdits, - String newSketchWidth, String newSketchHeight) { + String newClassName, List newImportStatements, + List newEdits, String newSketchWidth, String newSketchHeight) { if (newClassName == null) { throw new RuntimeException("Could not find main class"); @@ -87,6 +87,38 @@ public class PreprocessorResult { sketchHeight = newSketchHeight; } + /** + * Create a new preprocessing result with errors + * + * @param newProgramType The type of program that has be preprocessed. + * @param newHeaderOffset The offset (in number of chars) from the start of the program at which + * the header finishes. + * @param newClassName The name of the class containing the sketch. + * @param newImportStatements The imports required for the sketch including defaults and core imports. + * @param newEdits The edits made during preprocessing. + * @param newSketchWidth The width of the sketch in pixels or special value like displayWidth; + * @param newSketchHeight The height of the sketch in pixels or special value like displayWidth; + */ + public PreprocessorResult(PdePreprocessor.Mode newProgramType, int newHeaderOffset, + String newClassName, List newImportStatements, + List newEdits, String newSketchWidth, String newSketchHeight, + List newPreprocessIssues) { + + if (newClassName == null) { + throw new RuntimeException("Could not find main class"); + } + + headerOffset = newHeaderOffset; + className = newClassName; + importStatements = newImportStatements; + programType = newProgramType; + edits = newEdits; + preprocessIssues = newPreprocessIssues; + + sketchWidth = newSketchWidth; + sketchHeight = newSketchHeight; + } + /** * Private constructor allowing creation of result indicating preprocess issues. * diff --git a/java/src/processing/mode/java/runner/Runner.java b/java/src/processing/mode/java/runner/Runner.java index df53a3fa0..8a3f37606 100644 --- a/java/src/processing/mode/java/runner/Runner.java +++ b/java/src/processing/mode/java/runner/Runner.java @@ -116,16 +116,11 @@ public class Runner implements MessageConsumer { sketchErr.println(library.getName() + " does not run on this architecture: " + variant); int opposite = (bits == 32) ? 64 : 32; if (Platform.isMacOS()) { - //if (library.supportsArch(PConstants.MACOSX, opposite)) { // should always be true throw new SketchException("To use " + library.getName() + ", " + "switch to " + opposite + "-bit mode in Preferences."); - //} } else { throw new SketchException(library.getName() + " is only compatible " + "with the " + opposite + "-bit download of Processing."); - //throw new SketchException(library.getName() + " does not run in " + bits + "-bit mode."); - // "To use this library, switch to 32-bit mode in Preferences." (OS X) - // "To use this library, you must use the 32-bit version of Processing." } } } @@ -205,7 +200,7 @@ public class Runner implements MessageConsumer { /** * Additional access to the virtual machine. TODO: may not be needed - * @return debugge VM or null if not running + * @return debugger VM or null if not running */ public VirtualMachine vm() { return vm; @@ -317,12 +312,12 @@ public class Runner implements MessageConsumer { //params.add("-Xrunhprof:cpu=samples"); // old-style profiler // TODO change this to use run.args = true, run.args.0, run.args.1, etc. - // so that spaces can be included in the arg names + // so that spaces can be included in the arg names String options = Preferences.get("run.options"); if (options.length() > 0) { - String pieces[] = PApplet.split(options, ' '); - for (int i = 0; i < pieces.length; i++) { - String p = pieces[i].trim(); + String[] pieces = PApplet.split(options, ' '); + for (String piece : pieces) { + String p = piece.trim(); if (p.length() > 0) { params.append(p); } @@ -357,8 +352,9 @@ public class Runner implements MessageConsumer { //params.append("-Dcom.apple.mrj.application.apple.menu.about.name=" + // build.getSketchClassName()); } else if (Platform.isWindows()) { - // No scaling of swing (see #5753) on zoomed displays until some issues regarding JEP 263 - // with rendering artifacts are sorted out. + // No scaling of Swing on zoomed displays until some issues + // regarding JEP 263 with rendering artifacts are sorted out. + // https://github.com/processing/processing/issues/5753 params.append("-Dsun.java2d.uiScale=1"); } @@ -366,20 +362,29 @@ public class Runner implements MessageConsumer { // librariesClassPath will always have sep char prepended String javaLibraryPath = build.getJavaLibraryPath(); - String javaLibraryPathParam = "-Djava.library.path=" + - javaLibraryPath + - File.pathSeparator + - System.getProperty("java.library.path"); + String javaLibraryPathParam = + "-Djava.library.path=" + javaLibraryPath + + File.pathSeparator + + System.getProperty("java.library.path"); params.append(javaLibraryPathParam); + Library javafx = build.findJavaFX(); + if (javafx != null) { + // The .jar files are in the same directory as the natives for windows64 et al, + // because the .jar files are ever-so-slightly different per-platform. + File modulesFolder = new File(javafx.getNativePath(), "modules"); + for (String arg : JavaBuild.getArgsJavaFX(modulesFolder.getAbsolutePath())) { + params.append(arg); + } + } + params.append("-cp"); params.append(build.getClassPath()); // enable assertions - // http://dev.processing.org/bugs/show_bug.cgi?id=1188 + // http://processing.org/bugs/bugzilla/1188.html params.append("-ea"); - //PApplet.println(PApplet.split(sketch.classPath, ':')); return params; } @@ -417,7 +422,7 @@ public class Runner implements MessageConsumer { GraphicsDevice[] devices = ge.getScreenDevices(); // Make sure the display set in Preferences actually exists - GraphicsDevice runDevice = editorDevice; + GraphicsDevice runDevice; if (runDisplay > 0 && runDisplay <= devices.length) { runDevice = devices[runDisplay-1]; } else { @@ -508,54 +513,52 @@ public class Runner implements MessageConsumer { protected void launchJava(final String[] args) { - new Thread(new Runnable() { - public void run() { + new Thread(() -> { // PApplet.println("java starting"); - vmReturnedError = false; - process = PApplet.exec(args); - try { + vmReturnedError = false; + process = PApplet.exec(args); + try { // PApplet.println("java waiting"); - int result = process.waitFor(); + int result = process.waitFor(); // PApplet.println("java done waiting"); - if (result != 0) { - String[] errorStrings = PApplet.loadStrings(process.getErrorStream()); - String[] inputStrings = PApplet.loadStrings(process.getInputStream()); + if (result != 0) { + String[] errorStrings = PApplet.loadStrings(process.getErrorStream()); + String[] inputStrings = PApplet.loadStrings(process.getInputStream()); // PApplet.println("launchJava stderr:"); // PApplet.println(errorStrings); // PApplet.println("launchJava stdout:"); - PApplet.printArray(inputStrings); + PApplet.printArray(inputStrings); - if (errorStrings != null && errorStrings.length > 1) { - if (errorStrings[0].indexOf("Invalid maximum heap size") != -1) { - Messages.showWarning("Way Too High", - "Please lower the value for \u201Cmaximum available memory\u201D in the\n" + - "Preferences window. For more information, read Help \u2192 Troubleshooting.", null); - } else { - for (String err : errorStrings) { - sketchErr.println(err); - } - sketchErr.println("Using startup command: " + PApplet.join(args, " ")); - } + if (errorStrings != null && errorStrings.length > 1) { + if (errorStrings[0].contains("Invalid maximum heap size")) { + Messages.showWarning("Way Too High", + "Please lower the value for \u201Cmaximum available memory\u201D in the\n" + + "Preferences window. For more information, read Help \u2192 Troubleshooting.", null); } else { - //exc.printStackTrace(); - sketchErr.println("Could not run the sketch (Target VM failed to initialize)."); - if (Preferences.getBoolean("run.options.memory")) { - // Only mention this if they've even altered the memory setup - sketchErr.println("Make sure that you haven't set the maximum available memory too high."); + for (String err : errorStrings) { + sketchErr.println(err); } - sketchErr.println("For more information, read revisions.txt and Help \u2192 Troubleshooting."); + sketchErr.println("Using startup command: " + PApplet.join(args, " ")); } - // changing this to separate editor and listener [091124] - //if (editor != null) { - listener.statusError("Could not run the sketch."); - vmReturnedError = true; - //} -// return null; + } else { + //exc.printStackTrace(); + sketchErr.println("Could not run the sketch (Target VM failed to initialize)."); + if (Preferences.getBoolean("run.options.memory")) { + // Only mention this if they've even altered the memory setup + sketchErr.println("Make sure that you haven't set the maximum available memory too high."); + } + sketchErr.println("For more information, read Help \u2192 Troubleshooting."); } - } catch (InterruptedException e) { - e.printStackTrace(); + // changing this to separate editor and listener [091124] + //if (editor != null) { + listener.statusError("Could not run the sketch."); + vmReturnedError = true; + //} +// return null; } + } catch (InterruptedException e) { + e.printStackTrace(); } }).start(); } @@ -594,40 +597,38 @@ public class Runner implements MessageConsumer { return; } - Thread eventThread = new Thread() { - public void run() { - try { - boolean connected = true; - while (connected) { - EventQueue eventQueue = vm.eventQueue(); - // remove() blocks until event(s) available - EventSet eventSet = eventQueue.remove(); + Thread eventThread = new Thread(() -> { + try { + boolean connected = true; + while (connected) { + EventQueue eventQueue = vm.eventQueue(); + // remove() blocks until event(s) available + EventSet eventSet = eventQueue.remove(); // listener.vmEvent(eventSet); - for (Event event : eventSet) { + for (Event event : eventSet) { // System.out.println("EventThread.handleEvent -> " + event); - if (event instanceof VMStartEvent) { - vm.resume(); - } else if (event instanceof ExceptionEvent) { + if (event instanceof VMStartEvent) { + vm.resume(); + } else if (event instanceof ExceptionEvent) { // for (ThreadReference thread : vm.allThreads()) { // System.out.println("thread : " + thread); //// thread.suspend(); // } - exceptionEvent((ExceptionEvent) event); - } else if (event instanceof VMDisconnectEvent) { - connected = false; - } + exceptionEvent((ExceptionEvent) event); + } else if (event instanceof VMDisconnectEvent) { + connected = false; } } + } // } catch (VMDisconnectedException e) { // Logger.getLogger(VMEventReader.class.getName()).log(Level.INFO, "VMEventReader quit on VM disconnect"); - } catch (Exception e) { - System.err.println("crashed in event thread due to " + e.getMessage()); + } catch (Exception e) { + System.err.println("crashed in event thread due to " + e.getMessage()); // Logger.getLogger(VMEventReader.class.getName()).log(Level.SEVERE, "VMEventReader quit", e); - e.printStackTrace(); - } + e.printStackTrace(); } - }; + }); eventThread.start(); @@ -657,9 +658,7 @@ public class Runner implements MessageConsumer { // or the user manually closes the sketch window. // TODO this should be handled better, should it not? if (editor != null) { - java.awt.EventQueue.invokeLater(() -> { - editor.onRunnerExiting(Runner.this); - }); + java.awt.EventQueue.invokeLater(() -> editor.onRunnerExiting(Runner.this)); } } catch (InterruptedException exc) { // we don't interrupt @@ -679,13 +678,7 @@ public class Runner implements MessageConsumer { // System.out.println("connector name is " + connector.name()); // } - for (Object c : connectors) { - Connector connector = (Connector) c; -// System.out.println(connector.name()); -// } -// Iterator iter = connectors.iterator(); -// while (iter.hasNext()) { -// Connector connector = (Connector)iter.next(); + for (Connector connector : connectors) { if (connector.name().equals(connectorName)) { return connector; } @@ -726,9 +719,7 @@ public class Runner implements MessageConsumer { handleCommonErrors(exceptionName, message, listener, sketchErr); if (editor != null) { - java.awt.EventQueue.invokeLater(() -> { - editor.onRunnerExiting(Runner.this); - }); + java.awt.EventQueue.invokeLater(() -> editor.onRunnerExiting(Runner.this)); } } @@ -822,8 +813,7 @@ public class Runner implements MessageConsumer { for (StackFrame frame : frames) { try { Location location = frame.location(); - String filename = null; - filename = location.sourceName(); + String filename = location.sourceName(); int lineNumber = location.lineNumber() - 1; SketchException rex = build.placeException(message, filename, lineNumber); @@ -847,7 +837,7 @@ public class Runner implements MessageConsumer { } catch (Exception e) { // stack overflows seem to trip in frame.location() above // ignore this case so that the actual error gets reported to the user - if ("StackOverflowError".equals(message) == false) { + if (!"StackOverflowError".equals(message)) { e.printStackTrace(sketchErr); } } @@ -856,15 +846,15 @@ public class Runner implements MessageConsumer { try { // assume object reference is Throwable, get stack trace Method method = ((ClassType) or.referenceType()).concreteMethodByName("getStackTrace", "()[Ljava/lang/StackTraceElement;"); - ArrayReference result = (ArrayReference) or.invokeMethod(thread, method, new ArrayList(), ObjectReference.INVOKE_SINGLE_THREADED); + ArrayReference result = (ArrayReference) or.invokeMethod(thread, method, new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); // iterate through stack frames and pull filename and line number for each for (Value val: result.getValues()) { ObjectReference ref = (ObjectReference)val; method = ((ClassType) ref.referenceType()).concreteMethodByName("getFileName", "()Ljava/lang/String;"); - StringReference strref = (StringReference) ref.invokeMethod(thread, method, new ArrayList(), ObjectReference.INVOKE_SINGLE_THREADED); + StringReference strref = (StringReference) ref.invokeMethod(thread, method, new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); String filename = strref == null ? "Unknown Source" : strref.value(); method = ((ClassType) ref.referenceType()).concreteMethodByName("getLineNumber", "()I"); - IntegerValue intval = (IntegerValue) ref.invokeMethod(thread, method, new ArrayList(), ObjectReference.INVOKE_SINGLE_THREADED); + IntegerValue intval = (IntegerValue) ref.invokeMethod(thread, method, new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); int lineNumber = intval.intValue() - 1; SketchException rex = build.placeException(message, filename, lineNumber); @@ -878,12 +868,12 @@ public class Runner implements MessageConsumer { // Implemented for 2.0b9, writes a stack trace when there's an internal error inside core. method = ((ClassType) or.referenceType()).concreteMethodByName("printStackTrace", "()V"); // System.err.println("got method " + method); - or.invokeMethod(thread, method, new ArrayList(), ObjectReference.INVOKE_SINGLE_THREADED); + or.invokeMethod(thread, method, new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); } catch (Exception e) { // stack overflows will make the exception handling above trip again // ignore this case so that the actual error gets reported to the user - if ("StackOverflowError".equals(message) == false) { + if (!"StackOverflowError".equals(message)) { e.printStackTrace(sketchErr); } } diff --git a/todo.txt b/todo.txt index a03481c6e..02a4e8bdb 100755 --- a/todo.txt +++ b/todo.txt @@ -1,54 +1,108 @@ -1273 (4.0a4) -X “An error occurred while starting the application” with 4.0a3 on Windows -X replace about.bmp that was causing processing.exe to crash on startup -X https://github.com/processing/processing4/issues/156 -X update to JDK 11.0.10 -X update from JNA 5.2.0 to 5.7.0 -X was having trouble with "java.lang.UnsatisfiedLinkError: Unable to load library 'CoreServices': Native library (darwin/libCoreServices.dylib) not found in resource path" -X https://github.com/fathominfo/processing-p5js-mode/issues/26 -X implement auto-download for JNA updates -X “Exception in thread "Contribution Uninstaller" NullPointerException” during Remove -X https://github.com/processing/processing4/issues/174 -X catch NoClassDefError in Platform.deleteFile() (still unclear of its cause) -X https://github.com/processing/processing4/issues/159 -_ https://github.com/processing/processing/issues/6185 -X need to set a nicer default font -X increases export size, but impact is so worth it -X Update JDK to 11.0.11+9 -X modernize the RegisteredMethods code to use collections classes w/ concurrency -X https://github.com/processing/processing4/pull/199 -X don't sort user's charset array when calling createFont() -X https://github.com/processing/processing4/issues/197 -X https://github.com/processing/processing4/pull/198 -X automatically lock closed issues -X https://github.com/apps/lock -X Display Window doesn't remember its position -X seems that --external not getting passed -X https://github.com/processing/processing4/issues/158 -X https://github.com/processing/processing/issues/5843 -X https://github.com/processing/processing/issues/5781 +1275 (4.0a6) +X remove java.class.path when launching code from inside the PDE +X should prevent conflicts, avoid papering over other bugs +X remove the PDE classpath from sketches +X causing it to include incomplete JFX, other misc; too confusing +X add ui.font.family and ui.font.size as preferences +X Editor.applyPreferences() was protected, now public +X remove compound key actions (which were undocumented and not in use) +X clears up a lot of complexity in DefaultInputHandler +X if someone wants this, they could recreate it in a subclass +X remove jdt.compiler.jar from subprojects +X no longer using JRE (doesn't exist), so it's not needed +o can we remove it altogether, or is it used by the PDE? +X remove lots of dead/unused parts of javafx/build.xml +X fix up modifiers for key events (after completing mouse) in Java2D +X set args for JavaFX and OpenGL for the mouse and key events +X make sure that everything is set properly, also for keys +X move ISSUE_TEMPLATE to .github subfolder +X https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/manually-creating-a-single-issue-template-for-your-repository +o ability to switch mode in p5 w/o saving/closing/etc +X trying to save the user from themselves here is just messier than needed +X opt to open a new editor window rather than weird error messages +X https://github.com/processing/processing4/issues/189 -earlier -o further streamline the downloader -o https://github.com/processing/processing4/issues/47 -o next video release -o https://github.com/processing/processing-video/milestone/1 +readme +X was fixed in the source for 4.0a5, but may not have been included in the dist +X NoClassDefError: processing/core/PApplet when starting 4.0a2 on Windows 10 +X https://github.com/processing/processing4/issues/154 + +moviemaker +X MovieMaker .mov not compatible with QuicktTime Player +X https://github.com/processing/processing/issues/6110 +X notes in the bug about what's going on +X confirmed that after Mojave, the QTMovieModernizer went away (was 32-bit) +_ https://support.apple.com/en-us/HT202884 +X the modernizer would convert things to ProRes +_ https://support.apple.com/en-us/HT202410 +o possible other library: http://jcodec.org/ +o https://github.com/jcodec/jcodec +o listing: https://search.maven.org/artifact/org.jcodec/jcodec/0.2.5/jar +o jars: https://repo.maven.apache.org/maven2/org/jcodec/jcodec/0.2.5/ +o demo: https://github.com/jcodec/jcodec/blob/master/samples/main/java/org/jcodec/samples/gen/SequenceEncoderDemo.java +X incorporating ffmpeg +X the essentials build for macos64, just one file: +X https://evermeet.cx/ffmpeg/ffmpeg-4.4.7z +X https://evermeet.cx/ffmpeg/ffmpeg-4.4.zip (!) +X via https://evermeet.cx/ffmpeg/ +X release essentials for windows64, multiple files: +X https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.7z +X https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip +X ffmpeg-4.4-essentials_build/bin/ffmpeg.exe is our man +X via https://www.gyan.dev/ffmpeg/builds/ +X linux64 +X https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz +X via https://johnvansickle.com/ffmpeg/ also has arm and others contribs -X many updates in the docs portion of the repo -X https://github.com/processing/processing4/pull/191 -X fixing undo -X fix? https://github.com/processing/processing4/pull/175 +X Module javafx.base not found on Linux +X https://github.com/processing/processing4/issues/214 +X https://github.com/processing/processing4/pull/215 +X After selecting a font other than Source Code Pro, font went to a default +X https://github.com/processing/processing4/pull/216 + +sam +X Code completion not working +X https://github.com/processing/processing4/issues/177 +X confirmed not working from Sam's repo either +X https://github.com/processing/processing4/pull/219 + + +for next release +_ only export 'tool' subfolder for Movie Maker (and Theme Engine?) + + +_ startup is so incredibly slow +_ the splash screen comes up fairly quickly, so what gives? + +_ replace bug numbers +_ http://dev.processing.org/bugs/show_bug.cgi?id=1188 +_ with http://processing.org/bugs/bugzilla/1188.html +_ also code.google.com URLs with Github URLs (numbers are sorta in sync) + +ui is ugly on macOS +_ fix height of font size dropdown +_ better default fonts for Swing; argh +_ file an issue with the images +https://www.pushing-pixels.org/2017/01/17/using-san-francisco-font-in-swing-applications-on-a-mac.html + +_ macosx vs macosx64 in JavaFX +_ the latter is making the export fail because it won't embed a Java VM +_ may be because it's exporting twice and overwriting? or 64 takes precedence? +_ what should macos-aarch64 be called? + +_ Remove usage of com.sun.* in JavaFX library +_ https://github.com/processing/processing4/issues/208 +_ Implement support for Java “modules” and clean up JavaFX-specific workarounds +_ https://github.com/processing/processing4/issues/212 + +may be fixed +_ Undo feature may have undesired results (4.0a4) _ https://github.com/processing/processing/issues/4775 -X tweak the number of updates based on Akarshit's attempt -X https://github.com/processing/processing4/issues/201 -X https://github.com/processing/processing/pull/4097 - - -regressions -_ Code completion not working -_ https://github.com/processing/processing4/issues/177 +_ HDPI support GNOME desktop +_ https://github.com/processing/processing/issues/6059 +_ add a Tool for removing extended attributes? xattr -cr /path/to/Something.app _ when exporting an app, run xattr on it to handle "app is damaged" errors? _ https://osxdaily.com/2019/02/13/fix-app-damaged-cant-be-opened-trash-error-mac/ _ https://github.com/processing/processing/issues/4214 @@ -60,58 +114,49 @@ _ when lib downloads (batik) go dead, fallback to the download.processing.org ve _ or for now, tell users how to do it manually windows/scaling -_ we're turning off automatic UI scaling in Windows, should we turn it back on? -_ using -Dsun.java2d.uiScale.enabled=false inside config.xml for launch4j -_ this was for Java 9, and we should have better support now -_ also check whether this is set on Linux -_ Welcome screen doesn't size properly for HiDPI screens -_ https://github.com/processing/processing/issues/4896 -_ getSystemZoom() not available to splash screen -_ https://github.com/processing/processing4/issues/145 -_ move all platform code out that doesn't require additional setup? -_ i.e. all the things that rely on preferences can be inited separately (later) _ include JNA so that sketches can also scale properly? _ what happens re: getting scaled/high-res graphics? _ make that a preference? (and double the size by default?) _ pixelDensity() not working in exported Windows applications _ https://github.com/processing/processing/issues/5414#issuecomment-841088518 -already fixed in 4.x? (confirm/close on release) -_ HDPI support GNOME desktop -_ https://github.com/processing/processing/issues/6059 +should be in for 4.x +_ jeditsyntax is a mess of old-style getModifiers() +_ would like to switch this over, but needs to be tested a lot +_ Switch to getModifiersEx() in `processing.app` +_ https://github.com/processing/processing4/issues/67 +_ command line complaints +_ https://github.com/processing/processing/issues/6129 _ remove the JRE Downloader _ https://github.com/processing/processing4/issues/155 _ editor breakpoints out of the .pde files _ https://github.com/processing/processing/issues/5848 _ or at least avoid the multiple - -_ MovieMaker .mov not compatible with QuicktTime Player -_ https://github.com/processing/processing/issues/6110 -_ confirmed that after Mojave, the QTMovieModernizer went away (was 32-bit) -_ https://support.apple.com/en-us/HT202884 -_ the modernizer would convert things to ProRes -_ https://support.apple.com/en-us/HT202410 -_ possible other library: http://jcodec.org/ -_ https://github.com/jcodec/jcodec -_ listing: https://search.maven.org/artifact/org.jcodec/jcodec/0.2.5/jar -_ jars: https://repo.maven.apache.org/maven2/org/jcodec/jcodec/0.2.5/ -_ demo: https://github.com/jcodec/jcodec/blob/master/samples/main/java/org/jcodec/samples/gen/SequenceEncoderDemo.java +_ Add ability to move ~/.processing directory +_ use ~/.config as parent, or $XDG_CONFIG_HOME +o https://github.com/processing/processing/issues/6115 (moved) +_ https://github.com/processing/processing4/issues/203 +_ Move doclet to separate repository +_ https://github.com/processing/processing4/issues/218 decisions before final 4.0 release -_ Add ability to move ~/.processing directory -_ use ~/.config as parent, or $XDG_CONFIG_HOME -_ https://github.com/processing/processing/issues/6115 X Shutting off VAqua due to interface ugliness and Contribution Manager freezing _ https://github.com/processing/processing4/issues/129 _ now with a release 9 to cover Big Sur _ https://violetlib.org/vaqua/downloads.html _ make the final call to remove, or put the libs on download.processing.org -_ Some 3.x Tools not working because JavaFX isn't on the classpath -_ https://github.com/processing/processing4/issues/110 _ Friendly Names for new Sketches (includes UI for switching it back) _ https://github.com/processing/processing/pull/6048 +_ add language support to Modes (request from Andres) +_ https://github.com/processing/processing4/pull/14 +_ this was a small change; rebase not really needed since needs rewrite anyway +_ change the license from GPL +_ then pass through the source to update licenses +_ add Processing Foundation as 2012-15 +_ update license info to state gplv2 not v3 +_ run through that online license checker discuss with Sam @@ -129,32 +174,18 @@ _ better for git, etc _ single file thing is long gone _ introduce the idea of 'scraps' (ala gist) that are just single page blobs _ launch/psk files/import from web editor (more details below) -_ ability to switch mode in p5 w/o saving/closing/etc -_ trying to save the user from themselves here is just messier than needed -_ https://github.com/processing/processing4/issues/189 -_ 'show sketch folder' weird when in temp folder -_ ask to save first (sketch has not been saved yet) -_ or make the temp folder part of the sketchbook -_ same with adding files to an unsaved sketch, do we block that? - - -_ add language support to Modes (request from Andres) -_ https://github.com/processing/processing4/pull/14 -_ this was a small change; rebase not really needed since needs rewrite anyway +_ cleaning up the temp file handling +_ 'show sketch folder' weird when in temp folder +_ ask to save first (sketch has not been saved yet) +_ or make the temp folder part of the sketchbook +_ same with adding files to an unsaved sketch, do we block that? macos _ disable "notifications" prompt on startup for macOS -_ (we're not posting any notifications, at least for now) +_ we're not posting any, can we suppress the "allow notifications" message? _ https://developer.apple.com/documentation/usernotifications _ https://developer.apple.com/documentation/usernotifications/asking_permission_to_use_notifications -_ Apple Silicon release -_ http://openjdk.java.net/jeps/391 -_ https://github.com/AdoptOpenJDK/openjdk-support/issues/146 -_ https://github.com/microsoft/openjdk-aarch64/releases -_ https://www.azul.com/downloads/zulu-community/?version=java-11-lts&os=macos&architecture=arm-64-bit&package=jdk -_ OpenGL available but deprecated on Apple Si -_ https://developer.apple.com/documentation/xcode/porting_your_macos_apps_to_apple_silicon windows @@ -223,10 +254,6 @@ _ reliable getLibraryFolder() and getDocumentsFolder() methods in MacPlatform _ https://github.com/processing/processing4/issues/9 _ i18n support for Modes _ https://github.com/processing/processing/commit/0ed2fc139c3c5dfe0a1702ed8348987b3c6a5c9d -_ Switch to getModifiersEx() in `processing.app` -_ https://github.com/processing/processing4/issues/67 -_ command line complaints -_ https://github.com/processing/processing/issues/6129 _ update installation guide for Linux _ https://github.com/processing/processing-docs/issues/645 @@ -238,7 +265,6 @@ _ https://theia-ide.org/ _ https://medium.com/ballerina-techblog/implementing-a-language-server-how-hard-can-it-be-part-2-fa65a741aa23 - _ "Could not get the settings folder" message could be more helpful _ https://github.com/processing/processing/issues/5744 _ need to check the locations it'd be writing to, and see if available @@ -327,9 +353,6 @@ _ library compilations not ordered properly w/ sorting _ do we still support library compilations? that was from 2016 _ https://github.com/processing/processing/issues/4630 -_ editor windows always open on display 1 -_ https://github.com/processing/processing/issues/1566 - needs review _ createPreprocessor() added to JavaEditor, creating a mess @@ -390,10 +413,6 @@ _ https://github.com/processing/processing/issues/5023 pde/build -_ update list of optional JRE files for Java 8 -_ Andres provided some updates -_ https://github.com/processing/processing/issues/3288 -_ these will change again for Java 11, so wait until then _ fix appbundler problems due to rollback _ https://github.com/processing/processing/issues/3790 _ the rollback re-introduces two bugs (serial export and scrolling) @@ -416,10 +435,6 @@ _ break out Mode-specific options to their own panels in prefs _ Mode should just provide a panel for their prefs _ make the build fail if git pull on processing-docs fails _ remove "save before running" message -_ pass through the source to update licenses -_ add Processing Foundation as 2012-15 -_ update license info to state gplv2 not v3 -_ run through that online license checker _ save() and saveAs() need to be refactored _ https://github.com/processing/processing/issues/3843 _ clean out the repo @@ -440,7 +455,6 @@ _ right now it's generic, based on "a file exists" _ don't allow users to create 'blah.java' when 'blah.pde' already in sketch - sketchbook _ Mode.rebuildLibraryList() called too many times on startup? _ and when sketches saved as well? @@ -1141,40 +1155,6 @@ _ need to make sure that it's ok to write to logs dir.. _ probably being removed from future OS X versions _ Exiting a sketch with Command-Q or File > Quit doesn't call stop() on OS X _ https://github.com/processing/processing/issues/186 -_ investigate the sandboxing situation on OS X -_ http://developer.apple.com/library/mac/#documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW1 -most sandbox violations are triggered by attempts to read/write to the -filesystem without user intervention (eg. without using an open/save -dialog) and/or from places outside the container (eg. -~/Library/Containers/yourapp/...). -A violation is also triggered by trying to execute an external process -from Java (ex. using Runtime.exec()). -If you look at the list of entitlements your application can have on -the Apple site(*) you can think whether your application is performing -an operation that would require enabling a specific entitlement, like -connecting to a network, printing, interacting with a usb or bluetooth -device, etc.. -I encountered this problem too, I forgot to add it to my guide.... -If you sign all the files in the bundle it won't work as codesign -doesn't follow the symlinks. -First sign your bundle: -codesign --verbose -f -s "$SIGNATURE_APP" \ - --entitlements $ENTITLEMENTS \ - $YOUR_APP.app -Then sign all the libraries: -find $YOUR_APP/Contents/ -type f \ - \( -name "*.jar" -or -name "*.dylib"\) \ - -exec codesign --verbose -f -s "$SIGNATURE_APP" \ - --entitlements $ENTITLEMENTS {} \; -Finally you can create the package: -productbuild --component YOUR_APP.app /Applications \ - --sign "$SIGNATURE_INST" YOUR_APP.pkg -You can test if the package work with this command: -sudo installer -store -pkg $YOUR_APP.pkg -target / -You can also verify all libraries have been signed -find YOUR_APP/Contents/ -type f \ - \( -name "*.jar" -or -name "*.dylib"\) \ - -exec codesign --verbose --verify {} \; DIST / Linux